@praxisjs/composables 0.1.5 → 1.0.1
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 +57 -0
- package/dist/__tests__/browser.test.js +289 -81
- package/dist/__tests__/browser.test.js.map +1 -1
- package/dist/__tests__/dom.test.js +282 -118
- package/dist/__tests__/dom.test.js.map +1 -1
- package/dist/__tests__/utilities.test.js +198 -193
- package/dist/__tests__/utilities.test.js.map +1 -1
- package/dist/browser.d.ts +48 -12
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +130 -49
- package/dist/browser.js.map +1 -1
- package/dist/dom.d.ts +52 -25
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +123 -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 +72 -26
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +117 -94
- package/dist/utilities.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/browser.test.ts +329 -89
- package/src/__tests__/dom.test.ts +325 -132
- package/src/__tests__/utilities.test.ts +249 -215
- package/src/browser.ts +161 -53
- package/src/dom.ts +145 -82
- package/src/index.ts +11 -18
- package/src/utilities.ts +162 -111
package/src/browser.ts
CHANGED
|
@@ -1,63 +1,171 @@
|
|
|
1
|
+
import { Composable } from "@praxisjs/core";
|
|
1
2
|
import { signal, computed } from "@praxisjs/core/internal";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
|
|
4
|
+
export class MediaQuery extends Composable {
|
|
5
|
+
declare matches: boolean;
|
|
6
|
+
|
|
7
|
+
private _mql?: MediaQueryList;
|
|
8
|
+
private _handler?: (e: MediaQueryListEvent) => void;
|
|
9
|
+
private _view?: Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
constructor(private readonly query: string) {
|
|
12
|
+
super();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setup() {
|
|
16
|
+
if (this._view) return this._view;
|
|
17
|
+
this._mql = window.matchMedia(this.query);
|
|
18
|
+
const matches = signal(this._mql.matches);
|
|
19
|
+
this._handler = (e) => { matches.set(e.matches); };
|
|
20
|
+
this._mql.addEventListener("change", this._handler);
|
|
21
|
+
this._view = { matches };
|
|
22
|
+
return this._view;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onUnmount() {
|
|
26
|
+
if (this._mql && this._handler) {
|
|
27
|
+
this._mql.removeEventListener("change", this._handler);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
11
30
|
}
|
|
12
31
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
export class ColorScheme extends Composable {
|
|
33
|
+
declare isDark: boolean;
|
|
34
|
+
declare isLight: boolean;
|
|
35
|
+
|
|
36
|
+
private _mql?: MediaQueryList;
|
|
37
|
+
private _handler?: (e: MediaQueryListEvent) => void;
|
|
38
|
+
private _view?: Record<string, unknown>;
|
|
39
|
+
|
|
40
|
+
setup() {
|
|
41
|
+
if (this._view) return this._view;
|
|
42
|
+
this._mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
43
|
+
const isDark = signal(this._mql.matches);
|
|
44
|
+
const isLight = computed(() => !isDark());
|
|
45
|
+
this._handler = (e) => { isDark.set(e.matches); };
|
|
46
|
+
this._mql.addEventListener("change", this._handler);
|
|
47
|
+
this._view = { isDark, isLight };
|
|
48
|
+
return this._view;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onUnmount() {
|
|
52
|
+
if (this._mql && this._handler) {
|
|
53
|
+
this._mql.removeEventListener("change", this._handler);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
17
56
|
}
|
|
18
57
|
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
58
|
+
export class Mouse extends Composable {
|
|
59
|
+
declare x: number;
|
|
60
|
+
declare y: number;
|
|
61
|
+
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
63
|
+
private _handler = (_e: MouseEvent) => {};
|
|
64
|
+
private _view?: Record<string, unknown>;
|
|
65
|
+
|
|
66
|
+
setup() {
|
|
67
|
+
if (this._view) return this._view;
|
|
68
|
+
const x = signal(0);
|
|
69
|
+
const y = signal(0);
|
|
70
|
+
this._handler = (e) => { x.set(e.clientX); y.set(e.clientY); };
|
|
71
|
+
window.addEventListener("mousemove", this._handler);
|
|
72
|
+
this._view = { x, y };
|
|
73
|
+
return this._view;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
onUnmount() {
|
|
77
|
+
window.removeEventListener("mousemove", this._handler);
|
|
78
|
+
}
|
|
27
79
|
}
|
|
28
80
|
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
81
|
+
export class KeyCombo extends Composable {
|
|
82
|
+
declare pressed: boolean;
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
85
|
+
private _keydownHandler = (_e: KeyboardEvent) => {};
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
87
|
+
private _keyupHandler = () => {};
|
|
88
|
+
private _view?: Record<string, unknown>;
|
|
89
|
+
|
|
90
|
+
constructor(private readonly combo: string) {
|
|
91
|
+
super();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setup() {
|
|
95
|
+
if (this._view) return this._view;
|
|
96
|
+
const parts = this.combo.toLowerCase().split("+").map((p) => p.trim());
|
|
97
|
+
const pressed = signal(false);
|
|
98
|
+
|
|
99
|
+
const modifiers = ["ctrl", "shift", "alt", "meta"];
|
|
100
|
+
const key = parts.find((p) => !modifiers.includes(p));
|
|
101
|
+
|
|
102
|
+
// A combo with no non-modifier key is invalid and should never fire
|
|
103
|
+
if (!key) {
|
|
104
|
+
this._keyupHandler = () => { pressed.set(false); };
|
|
105
|
+
window.addEventListener("keydown", this._keydownHandler);
|
|
106
|
+
window.addEventListener("keyup", this._keyupHandler);
|
|
107
|
+
this._view = { pressed };
|
|
108
|
+
return this._view;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this._keydownHandler = (e) => {
|
|
112
|
+
const ctrl = parts.includes("ctrl") ? e.ctrlKey : true;
|
|
113
|
+
const shift = parts.includes("shift") ? e.shiftKey : true;
|
|
114
|
+
const alt = parts.includes("alt") ? e.altKey : true;
|
|
115
|
+
const meta = parts.includes("meta") ? e.metaKey : true;
|
|
116
|
+
if (ctrl && shift && alt && meta && e.key.toLowerCase() === key) {
|
|
117
|
+
pressed.set(true);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
this._keyupHandler = () => { pressed.set(false); };
|
|
121
|
+
|
|
122
|
+
window.addEventListener("keydown", this._keydownHandler);
|
|
123
|
+
window.addEventListener("keyup", this._keyupHandler);
|
|
124
|
+
this._view = { pressed };
|
|
125
|
+
return this._view;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onUnmount() {
|
|
129
|
+
window.removeEventListener("keydown", this._keydownHandler);
|
|
130
|
+
window.removeEventListener("keyup", this._keyupHandler);
|
|
131
|
+
}
|
|
46
132
|
}
|
|
47
133
|
|
|
48
|
-
export
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
134
|
+
export class Idle extends Composable {
|
|
135
|
+
declare idle: boolean;
|
|
136
|
+
|
|
137
|
+
private _listeners: Array<[string, () => void]> = [];
|
|
138
|
+
private _timer?: ReturnType<typeof setTimeout>;
|
|
139
|
+
private _view?: Record<string, unknown>;
|
|
140
|
+
|
|
141
|
+
constructor(private readonly timeout = 60_000) {
|
|
142
|
+
super();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setup() {
|
|
146
|
+
if (this._view) return this._view;
|
|
147
|
+
const idle = signal(false);
|
|
148
|
+
const reset = () => {
|
|
149
|
+
idle.set(false);
|
|
150
|
+
clearTimeout(this._timer);
|
|
151
|
+
this._timer = setTimeout(() => { idle.set(true); }, this.timeout);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
["mousemove", "keydown", "click", "scroll", "touchstart"].forEach((event) => {
|
|
155
|
+
window.addEventListener(event, reset, { passive: true });
|
|
156
|
+
this._listeners.push([event, reset]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
reset();
|
|
160
|
+
this._view = { idle };
|
|
161
|
+
return this._view;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
onUnmount() {
|
|
165
|
+
clearTimeout(this._timer);
|
|
166
|
+
this._listeners.forEach(([event, handler]) => {
|
|
167
|
+
window.removeEventListener(event, handler);
|
|
168
|
+
});
|
|
169
|
+
this._listeners = [];
|
|
170
|
+
}
|
|
63
171
|
}
|
package/src/dom.ts
CHANGED
|
@@ -1,93 +1,156 @@
|
|
|
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
|
+
private _view?: Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
setup() {
|
|
13
|
+
if (this._view) return this._view;
|
|
14
|
+
const width = signal(window.innerWidth);
|
|
15
|
+
const height = signal(window.innerHeight);
|
|
16
|
+
this._handler = () => {
|
|
17
|
+
width.set(window.innerWidth);
|
|
18
|
+
height.set(window.innerHeight);
|
|
19
|
+
};
|
|
20
|
+
window.addEventListener("resize", this._handler);
|
|
21
|
+
this._view = { width, height };
|
|
22
|
+
return this._view;
|
|
23
|
+
}
|
|
7
24
|
|
|
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
|
-
};
|
|
25
|
+
onUnmount() {
|
|
26
|
+
window.removeEventListener("resize", this._handler);
|
|
27
|
+
}
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
30
|
+
export class ScrollPosition extends Composable {
|
|
31
|
+
declare x: number;
|
|
32
|
+
declare y: number;
|
|
33
|
+
|
|
34
|
+
private readonly _target: HTMLElement | Window;
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
36
|
+
private _handler = () => {};
|
|
37
|
+
private _view?: Record<string, unknown>;
|
|
38
|
+
|
|
39
|
+
constructor(target: HTMLElement | Window = window) {
|
|
40
|
+
super();
|
|
41
|
+
this._target = target;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setup() {
|
|
45
|
+
if (this._view) return this._view;
|
|
46
|
+
const x = signal(0);
|
|
47
|
+
const y = signal(0);
|
|
48
|
+
const t = this._target;
|
|
49
|
+
this._handler = () => {
|
|
50
|
+
x.set(t === window ? window.scrollX : (t as HTMLElement).scrollLeft);
|
|
51
|
+
y.set(t === window ? window.scrollY : (t as HTMLElement).scrollTop);
|
|
52
|
+
};
|
|
53
|
+
t.addEventListener("scroll", this._handler);
|
|
54
|
+
this._view = { x, y };
|
|
55
|
+
return this._view;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onUnmount() {
|
|
59
|
+
this._target.removeEventListener("scroll", this._handler);
|
|
60
|
+
}
|
|
39
61
|
}
|
|
40
62
|
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
export class ElementSize extends Composable {
|
|
64
|
+
declare width: number;
|
|
65
|
+
declare height: number;
|
|
66
|
+
|
|
67
|
+
private _observer?: ResizeObserver;
|
|
68
|
+
private _view?: Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
constructor(private readonly ref: { current: HTMLElement | null }) {
|
|
71
|
+
super();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setup() {
|
|
75
|
+
if (this._view) return this._view;
|
|
76
|
+
const width = signal(0);
|
|
77
|
+
const height = signal(0);
|
|
78
|
+
this._observer = new ResizeObserver(([entry]) => {
|
|
79
|
+
width.set(entry.contentRect.width);
|
|
80
|
+
height.set(entry.contentRect.height);
|
|
81
|
+
});
|
|
82
|
+
effect(() => {
|
|
83
|
+
if (this.ref.current) {
|
|
84
|
+
this._observer?.observe(this.ref.current);
|
|
85
|
+
width.set(this.ref.current.offsetWidth);
|
|
86
|
+
height.set(this.ref.current.offsetHeight);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
this._view = { width, height };
|
|
90
|
+
return this._view;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onUnmount() {
|
|
94
|
+
this._observer?.disconnect();
|
|
95
|
+
}
|
|
53
96
|
}
|
|
54
97
|
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
export class Intersection extends Composable {
|
|
99
|
+
declare visible: boolean;
|
|
100
|
+
|
|
101
|
+
private _observer?: IntersectionObserver;
|
|
102
|
+
private _view?: Record<string, unknown>;
|
|
103
|
+
|
|
104
|
+
constructor(
|
|
105
|
+
private readonly ref: { current: HTMLElement | null },
|
|
106
|
+
private readonly options?: IntersectionObserverInit,
|
|
107
|
+
) {
|
|
108
|
+
super();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setup() {
|
|
112
|
+
if (this._view) return this._view;
|
|
113
|
+
const visible = signal(false);
|
|
114
|
+
this._observer = new IntersectionObserver(([entry]) => {
|
|
115
|
+
visible.set(entry.isIntersecting);
|
|
116
|
+
}, this.options);
|
|
117
|
+
effect(() => {
|
|
118
|
+
if (this.ref.current) this._observer?.observe(this.ref.current);
|
|
119
|
+
});
|
|
120
|
+
this._view = { visible };
|
|
121
|
+
return this._view;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
onUnmount() {
|
|
125
|
+
this._observer?.disconnect();
|
|
126
|
+
}
|
|
70
127
|
}
|
|
71
128
|
|
|
72
|
-
export
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
el.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
export class Focus extends Composable {
|
|
130
|
+
declare focused: boolean;
|
|
131
|
+
|
|
132
|
+
private _view?: Record<string, unknown>;
|
|
133
|
+
|
|
134
|
+
constructor(private readonly ref: { current: HTMLElement | null }) {
|
|
135
|
+
super();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setup() {
|
|
139
|
+
if (this._view) return this._view;
|
|
140
|
+
const focused = signal(false);
|
|
141
|
+
effect(() => {
|
|
142
|
+
const el = this.ref.current;
|
|
143
|
+
if (!el) return;
|
|
144
|
+
const onFocus = () => { focused.set(true); };
|
|
145
|
+
const onBlur = () => { focused.set(false); };
|
|
146
|
+
el.addEventListener("focus", onFocus);
|
|
147
|
+
el.addEventListener("blur", onBlur);
|
|
148
|
+
return () => {
|
|
149
|
+
el.removeEventListener("focus", onFocus);
|
|
150
|
+
el.removeEventListener("blur", onBlur);
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
this._view = { focused };
|
|
154
|
+
return this._view;
|
|
155
|
+
}
|
|
93
156
|
}
|
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";
|