@praxisjs/composables 1.2.1 → 1.2.3
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 +25 -0
- package/dist/browser.d.ts +41 -18
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +114 -114
- package/dist/browser.js.map +1 -1
- package/dist/dom.d.ts +24 -16
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +75 -84
- package/dist/dom.js.map +1 -1
- package/dist/list.d.ts +4 -1
- package/dist/list.d.ts.map +1 -1
- package/dist/list.js +29 -11
- package/dist/list.js.map +1 -1
- package/dist/upload.d.ts.map +1 -1
- package/dist/upload.js +32 -12
- package/dist/upload.js.map +1 -1
- package/dist/utilities.d.ts +13 -1
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +88 -26
- package/dist/utilities.js.map +1 -1
- package/package.json +14 -5
- package/src/browser.ts +129 -115
- package/src/dom.ts +77 -85
- package/src/list.ts +29 -10
- package/src/upload.ts +30 -11
- package/src/utilities.ts +101 -24
- package/dist/__tests__/browser.test.d.ts +0 -2
- package/dist/__tests__/browser.test.d.ts.map +0 -1
- package/dist/__tests__/browser.test.js +0 -350
- package/dist/__tests__/browser.test.js.map +0 -1
- package/dist/__tests__/dom.test.d.ts +0 -2
- package/dist/__tests__/dom.test.d.ts.map +0 -1
- package/dist/__tests__/dom.test.js +0 -463
- package/dist/__tests__/dom.test.js.map +0 -1
- package/dist/__tests__/list.test.d.ts +0 -2
- package/dist/__tests__/list.test.d.ts.map +0 -1
- package/dist/__tests__/list.test.js +0 -156
- package/dist/__tests__/list.test.js.map +0 -1
- package/dist/__tests__/upload.test.d.ts +0 -2
- package/dist/__tests__/upload.test.d.ts.map +0 -1
- package/dist/__tests__/upload.test.js +0 -531
- package/dist/__tests__/upload.test.js.map +0 -1
- package/dist/__tests__/utilities.test.d.ts +0 -2
- package/dist/__tests__/utilities.test.d.ts.map +0 -1
- package/dist/__tests__/utilities.test.js +0 -251
- package/dist/__tests__/utilities.test.js.map +0 -1
- package/src/__tests__/browser.test.ts +0 -414
- package/src/__tests__/dom.test.ts +0 -567
- package/src/__tests__/list.test.ts +0 -210
- package/src/__tests__/upload.test.ts +0 -639
- package/src/__tests__/utilities.test.ts +0 -327
- package/tsconfig.json +0 -8
package/dist/utilities.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Composable } from "@praxisjs/core";
|
|
2
|
-
import { signal,
|
|
2
|
+
import { computed, createEffect, disposeEffect, endBatch, signal, startBatch, } from "@praxisjs/core/internal";
|
|
3
3
|
export class Clipboard extends Composable {
|
|
4
4
|
resetDelay;
|
|
5
5
|
_resetTimer;
|
|
@@ -14,8 +14,10 @@ export class Clipboard extends Composable {
|
|
|
14
14
|
const copy = async (text) => {
|
|
15
15
|
try {
|
|
16
16
|
await navigator.clipboard.writeText(text);
|
|
17
|
+
startBatch();
|
|
17
18
|
content.set(text);
|
|
18
19
|
copied.set(true);
|
|
20
|
+
endBatch();
|
|
19
21
|
clearTimeout(this._resetTimer);
|
|
20
22
|
this._resetTimer = setTimeout(() => { copied.set(false); }, resetDelay);
|
|
21
23
|
}
|
|
@@ -31,66 +33,126 @@ export class Clipboard extends Composable {
|
|
|
31
33
|
}
|
|
32
34
|
export class Geolocation extends Composable {
|
|
33
35
|
options;
|
|
34
|
-
|
|
36
|
+
_lat = signal(null);
|
|
37
|
+
_lng = signal(null);
|
|
38
|
+
_error = signal(null);
|
|
39
|
+
_loading = signal(true);
|
|
40
|
+
_active = false;
|
|
35
41
|
constructor(options) {
|
|
36
42
|
super();
|
|
37
43
|
this.options = options;
|
|
38
44
|
}
|
|
39
45
|
setup() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
return { lat: this._lat, lng: this._lng, error: this._error, loading: this._loading };
|
|
47
|
+
}
|
|
48
|
+
// Requested on mount rather than in setup(), which also runs for components that never mount.
|
|
49
|
+
onMount() {
|
|
50
|
+
if (this._active)
|
|
51
|
+
return;
|
|
52
|
+
this._active = true;
|
|
44
53
|
navigator.geolocation.getCurrentPosition((pos) => {
|
|
45
54
|
if (!this._active)
|
|
46
55
|
return;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
startBatch();
|
|
57
|
+
this._lat.set(pos.coords.latitude);
|
|
58
|
+
this._lng.set(pos.coords.longitude);
|
|
59
|
+
this._loading.set(false);
|
|
60
|
+
endBatch();
|
|
50
61
|
}, (err) => {
|
|
51
62
|
if (!this._active)
|
|
52
63
|
return;
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
startBatch();
|
|
65
|
+
this._error.set(err);
|
|
66
|
+
this._loading.set(false);
|
|
67
|
+
endBatch();
|
|
55
68
|
}, this.options);
|
|
56
|
-
return { lat, lng, error, loading };
|
|
57
69
|
}
|
|
58
70
|
onUnmount() {
|
|
59
71
|
this._active = false;
|
|
60
72
|
}
|
|
61
73
|
}
|
|
74
|
+
const SECOND = 1_000;
|
|
75
|
+
const MINUTE = 60_000;
|
|
76
|
+
const HOUR = 3_600_000;
|
|
77
|
+
const DAY = 86_400_000;
|
|
78
|
+
const MONTH = 2_592_000_000;
|
|
79
|
+
// setTimeout fires immediately for delays above 2^31 - 1 ms.
|
|
80
|
+
const MAX_DELAY = 2_147_483_647;
|
|
81
|
+
const UNITS = [
|
|
82
|
+
{ below: MINUTE, size: SECOND, name: "second" },
|
|
83
|
+
{ below: HOUR, size: MINUTE, name: "minute" },
|
|
84
|
+
{ below: DAY, size: HOUR, name: "hour" },
|
|
85
|
+
{ below: MONTH, size: DAY, name: "day" },
|
|
86
|
+
{ below: Infinity, size: MONTH, name: "month" },
|
|
87
|
+
];
|
|
88
|
+
function unitIndex(diff) {
|
|
89
|
+
const abs = Math.abs(diff);
|
|
90
|
+
let i = 0;
|
|
91
|
+
while (abs >= UNITS[i].below)
|
|
92
|
+
i++;
|
|
93
|
+
return i;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Milliseconds until the text for `diff` (target − now, shrinking as time passes) changes: the
|
|
97
|
+
* next rounding step within its unit, or the moment it crosses into another unit.
|
|
98
|
+
*/
|
|
99
|
+
export function msUntilChange(diff) {
|
|
100
|
+
const i = unitIndex(diff);
|
|
101
|
+
const { size, below } = UNITS[i];
|
|
102
|
+
// Math.round(x) changes once x falls below its value minus one half.
|
|
103
|
+
let wait = diff - (Math.round(diff / size) - 0.5) * size + 1;
|
|
104
|
+
if (diff > 0) {
|
|
105
|
+
if (i > 0)
|
|
106
|
+
wait = Math.min(wait, diff - UNITS[i - 1].below + 1);
|
|
107
|
+
}
|
|
108
|
+
else if (below !== Infinity) {
|
|
109
|
+
wait = Math.min(wait, diff + below);
|
|
110
|
+
}
|
|
111
|
+
return Math.min(Math.max(Math.ceil(wait), 1), MAX_DELAY);
|
|
112
|
+
}
|
|
62
113
|
export class TimeAgo extends Composable {
|
|
63
114
|
source;
|
|
64
115
|
locale;
|
|
65
|
-
|
|
116
|
+
_tick = signal(0);
|
|
117
|
+
_effect;
|
|
66
118
|
constructor(source, locale = "pt-BR") {
|
|
67
119
|
super();
|
|
68
120
|
this.source = source;
|
|
69
121
|
this.locale = locale;
|
|
70
122
|
}
|
|
71
123
|
setup() {
|
|
72
|
-
const tick = signal(Date.now());
|
|
73
|
-
this._interval = setInterval(() => { tick.set(Date.now()); }, 60_000);
|
|
74
124
|
const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: "auto" });
|
|
75
125
|
const read = this.source;
|
|
126
|
+
const tick = this._tick;
|
|
76
127
|
const value = computed(() => {
|
|
77
128
|
void tick();
|
|
78
129
|
const diff = new Date(read()).getTime() - Date.now();
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
return rtf.format(Math.round(diff / 1000), "second");
|
|
82
|
-
if (abs < 3_600_000)
|
|
83
|
-
return rtf.format(Math.round(diff / 60_000), "minute");
|
|
84
|
-
if (abs < 86_400_000)
|
|
85
|
-
return rtf.format(Math.round(diff / 3_600_000), "hour");
|
|
86
|
-
if (abs < 2_592_000_000)
|
|
87
|
-
return rtf.format(Math.round(diff / 86_400_000), "day");
|
|
88
|
-
return rtf.format(Math.round(diff / 2_592_000_000), "month");
|
|
130
|
+
const { size, name } = UNITS[unitIndex(diff)];
|
|
131
|
+
return rtf.format(Math.round(diff / size), name);
|
|
89
132
|
});
|
|
90
133
|
return { value };
|
|
91
134
|
}
|
|
135
|
+
// One timer for the exact moment the text changes, instead of polling: re-armed whenever it
|
|
136
|
+
// fires or the source changes.
|
|
137
|
+
onMount() {
|
|
138
|
+
if (this._effect)
|
|
139
|
+
return;
|
|
140
|
+
const read = this.source;
|
|
141
|
+
const tick = this._tick;
|
|
142
|
+
this._effect = createEffect(() => {
|
|
143
|
+
tick();
|
|
144
|
+
const diff = new Date(read()).getTime() - Date.now();
|
|
145
|
+
// An invalid date has no next change; a NaN delay would fire at once, over and over.
|
|
146
|
+
if (Number.isNaN(diff))
|
|
147
|
+
return;
|
|
148
|
+
const timer = setTimeout(() => { tick.update((n) => n + 1); }, msUntilChange(diff));
|
|
149
|
+
return () => { clearTimeout(timer); };
|
|
150
|
+
});
|
|
151
|
+
}
|
|
92
152
|
onUnmount() {
|
|
93
|
-
|
|
153
|
+
if (this._effect)
|
|
154
|
+
disposeEffect(this._effect);
|
|
155
|
+
this._effect = undefined;
|
|
94
156
|
}
|
|
95
157
|
}
|
|
96
158
|
export class Pagination extends Composable {
|
package/dist/utilities.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/composables",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
@@ -10,15 +11,23 @@
|
|
|
10
11
|
"types": "./dist/index.d.ts"
|
|
11
12
|
}
|
|
12
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"!dist/**/__tests__",
|
|
18
|
+
"!src/**/__tests__",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
13
21
|
"devDependencies": {
|
|
14
22
|
"typescript": "^6.0.3"
|
|
15
23
|
},
|
|
16
24
|
"dependencies": {
|
|
17
|
-
"@praxisjs/core": "2.
|
|
18
|
-
"@praxisjs/shared": "0.3.
|
|
25
|
+
"@praxisjs/core": "^2.3.1",
|
|
26
|
+
"@praxisjs/shared": "^0.3.2"
|
|
19
27
|
},
|
|
20
28
|
"scripts": {
|
|
21
|
-
"build": "tsc",
|
|
22
|
-
"dev": "tsc --watch"
|
|
29
|
+
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
23
32
|
}
|
|
24
33
|
}
|
package/src/browser.ts
CHANGED
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
import { Composable } from "@praxisjs/core";
|
|
2
|
-
import {
|
|
2
|
+
import { computed, endBatch, signal, startBatch } from "@praxisjs/core/internal";
|
|
3
|
+
|
|
4
|
+
const hasWindow = typeof window !== "undefined";
|
|
5
|
+
|
|
6
|
+
// Listeners and timers start in onMount and stop in onUnmount: setup() runs when the component is
|
|
7
|
+
// constructed, which also happens for components that never mount (and on the server).
|
|
3
8
|
|
|
4
9
|
export class MediaQuery extends Composable {
|
|
5
10
|
declare matches: boolean;
|
|
6
11
|
|
|
7
12
|
private _mql?: MediaQueryList;
|
|
8
|
-
private
|
|
9
|
-
|
|
13
|
+
private readonly _matches = signal(false);
|
|
14
|
+
|
|
15
|
+
private readonly _onChange = (e: MediaQueryListEvent) => {
|
|
16
|
+
this._matches.set(e.matches);
|
|
17
|
+
};
|
|
10
18
|
|
|
11
19
|
constructor(private readonly query: string) {
|
|
12
20
|
super();
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
setup() {
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
if (hasWindow) {
|
|
25
|
+
this._mql ??= window.matchMedia(this.query);
|
|
26
|
+
this._matches.set(this._mql.matches);
|
|
27
|
+
}
|
|
28
|
+
return { matches: this._matches };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onMount() {
|
|
32
|
+
const mql = this._mql;
|
|
33
|
+
if (!mql) return;
|
|
34
|
+
this._matches.set(mql.matches);
|
|
35
|
+
mql.addEventListener("change", this._onChange);
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
onUnmount() {
|
|
28
|
-
|
|
29
|
-
this._mql.removeEventListener("change", this._handler);
|
|
30
|
-
}
|
|
39
|
+
this._mql?.removeEventListener("change", this._onChange);
|
|
31
40
|
}
|
|
32
41
|
}
|
|
33
42
|
|
|
@@ -35,27 +44,20 @@ export class ColorScheme extends Composable {
|
|
|
35
44
|
declare isDark: boolean;
|
|
36
45
|
declare isLight: boolean;
|
|
37
46
|
|
|
38
|
-
private
|
|
39
|
-
private _handler?: (e: MediaQueryListEvent) => void;
|
|
40
|
-
private _view?: Record<string, unknown>;
|
|
47
|
+
private readonly _query = new MediaQuery("(prefers-color-scheme: dark)");
|
|
41
48
|
|
|
42
49
|
setup() {
|
|
43
|
-
|
|
44
|
-
this._mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
45
|
-
const isDark = signal(this._mql.matches);
|
|
50
|
+
const isDark = this._query.setup().matches;
|
|
46
51
|
const isLight = computed(() => !isDark());
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.
|
|
52
|
-
return this._view;
|
|
52
|
+
return { isDark, isLight };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onMount() {
|
|
56
|
+
this._query.onMount();
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
onUnmount() {
|
|
56
|
-
|
|
57
|
-
this._mql.removeEventListener("change", this._handler);
|
|
58
|
-
}
|
|
60
|
+
this._query.onUnmount();
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -63,127 +65,139 @@ export class Mouse extends Composable {
|
|
|
63
65
|
declare x: number;
|
|
64
66
|
declare y: number;
|
|
65
67
|
|
|
66
|
-
private
|
|
67
|
-
private
|
|
68
|
+
private readonly _x = signal(0);
|
|
69
|
+
private readonly _y = signal(0);
|
|
70
|
+
|
|
71
|
+
private readonly _onMove = (e: MouseEvent) => {
|
|
72
|
+
startBatch();
|
|
73
|
+
this._x.set(e.clientX);
|
|
74
|
+
this._y.set(e.clientY);
|
|
75
|
+
endBatch();
|
|
76
|
+
};
|
|
68
77
|
|
|
69
78
|
setup() {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
x.set(e.clientX);
|
|
76
|
-
y.set(e.clientY);
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
window.addEventListener("mousemove", this._handler);
|
|
80
|
-
this._view = { x, y };
|
|
81
|
-
return this._view;
|
|
79
|
+
return { x: this._x, y: this._y };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onMount() {
|
|
83
|
+
window.addEventListener("mousemove", this._onMove);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
onUnmount() {
|
|
85
|
-
window.removeEventListener("mousemove", this.
|
|
87
|
+
window.removeEventListener("mousemove", this._onMove);
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
const MODIFIERS = ["ctrl", "shift", "alt", "meta"];
|
|
92
|
+
|
|
89
93
|
export class KeyCombo extends Composable {
|
|
90
94
|
declare pressed: boolean;
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
private
|
|
95
|
-
private
|
|
96
|
+
private readonly _pressed = signal(false);
|
|
97
|
+
// Parsed once: the keydown handler runs for every key press on the page.
|
|
98
|
+
private readonly _key: string | undefined;
|
|
99
|
+
private readonly _ctrl: boolean;
|
|
100
|
+
private readonly _shift: boolean;
|
|
101
|
+
private readonly _alt: boolean;
|
|
102
|
+
private readonly _meta: boolean;
|
|
103
|
+
|
|
104
|
+
private readonly _onKeyDown = (e: KeyboardEvent) => {
|
|
105
|
+
if (
|
|
106
|
+
(!this._ctrl || e.ctrlKey) &&
|
|
107
|
+
(!this._shift || e.shiftKey) &&
|
|
108
|
+
(!this._alt || e.altKey) &&
|
|
109
|
+
(!this._meta || e.metaKey) &&
|
|
110
|
+
e.key.toLowerCase() === this._key
|
|
111
|
+
) {
|
|
112
|
+
this._pressed.set(true);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
private readonly _onKeyUp = () => {
|
|
117
|
+
this._pressed.set(false);
|
|
118
|
+
};
|
|
96
119
|
|
|
97
|
-
constructor(
|
|
120
|
+
constructor(combo: string) {
|
|
98
121
|
super();
|
|
122
|
+
const parts = combo.toLowerCase().split("+").map((p) => p.trim());
|
|
123
|
+
this._key = parts.find((p) => !MODIFIERS.includes(p));
|
|
124
|
+
this._ctrl = parts.includes("ctrl");
|
|
125
|
+
this._shift = parts.includes("shift");
|
|
126
|
+
this._alt = parts.includes("alt");
|
|
127
|
+
this._meta = parts.includes("meta");
|
|
99
128
|
}
|
|
100
129
|
|
|
101
130
|
setup() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.toLowerCase()
|
|
105
|
-
.split("+")
|
|
106
|
-
.map((p) => p.trim());
|
|
107
|
-
const pressed = signal(false);
|
|
108
|
-
|
|
109
|
-
const modifiers = ["ctrl", "shift", "alt", "meta"];
|
|
110
|
-
const key = parts.find((p) => !modifiers.includes(p));
|
|
111
|
-
|
|
112
|
-
// A combo with no non-modifier key is invalid and should never fire
|
|
113
|
-
if (!key) {
|
|
114
|
-
this._keyupHandler = () => {
|
|
115
|
-
pressed.set(false);
|
|
116
|
-
};
|
|
117
|
-
window.addEventListener("keydown", this._keydownHandler);
|
|
118
|
-
window.addEventListener("keyup", this._keyupHandler);
|
|
119
|
-
this._view = { pressed };
|
|
120
|
-
return this._view;
|
|
121
|
-
}
|
|
131
|
+
return { pressed: this._pressed };
|
|
132
|
+
}
|
|
122
133
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const meta = parts.includes("meta") ? e.metaKey : true;
|
|
128
|
-
if (ctrl && shift && alt && meta && e.key.toLowerCase() === key) {
|
|
129
|
-
pressed.set(true);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
this._keyupHandler = () => {
|
|
133
|
-
pressed.set(false);
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
window.addEventListener("keydown", this._keydownHandler);
|
|
137
|
-
window.addEventListener("keyup", this._keyupHandler);
|
|
138
|
-
this._view = { pressed };
|
|
139
|
-
return this._view;
|
|
134
|
+
onMount() {
|
|
135
|
+
// A combo with no non-modifier key is invalid and never fires.
|
|
136
|
+
if (this._key !== undefined) window.addEventListener("keydown", this._onKeyDown);
|
|
137
|
+
window.addEventListener("keyup", this._onKeyUp);
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
onUnmount() {
|
|
143
|
-
window.removeEventListener("keydown", this.
|
|
144
|
-
window.removeEventListener("keyup", this.
|
|
141
|
+
window.removeEventListener("keydown", this._onKeyDown);
|
|
142
|
+
window.removeEventListener("keyup", this._onKeyUp);
|
|
145
143
|
}
|
|
146
144
|
}
|
|
147
145
|
|
|
146
|
+
const ACTIVITY_EVENTS = ["mousemove", "keydown", "click", "scroll", "touchstart"];
|
|
147
|
+
|
|
148
148
|
export class Idle extends Composable {
|
|
149
149
|
declare idle: boolean;
|
|
150
150
|
|
|
151
|
-
private
|
|
151
|
+
private readonly _idle = signal(false);
|
|
152
152
|
private _timer?: ReturnType<typeof setTimeout>;
|
|
153
|
-
private
|
|
153
|
+
private _lastActivity = 0;
|
|
154
|
+
|
|
155
|
+
// Activity only records a timestamp: rescheduling a timer on every mousemove or scroll event was
|
|
156
|
+
// the expensive part. The single pending timer checks the timestamp when it fires.
|
|
157
|
+
private readonly _onActivity = () => {
|
|
158
|
+
this._lastActivity = Date.now();
|
|
159
|
+
if (this._timer === undefined) {
|
|
160
|
+
this._idle.set(false);
|
|
161
|
+
this._schedule(this.timeout);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
private readonly _check = () => {
|
|
166
|
+
const remaining = this.timeout - (Date.now() - this._lastActivity);
|
|
167
|
+
if (remaining > 0) {
|
|
168
|
+
this._schedule(remaining);
|
|
169
|
+
} else {
|
|
170
|
+
this._timer = undefined;
|
|
171
|
+
this._idle.set(true);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
154
174
|
|
|
155
175
|
constructor(private readonly timeout = 60_000) {
|
|
156
176
|
super();
|
|
157
177
|
}
|
|
158
178
|
|
|
179
|
+
private _schedule(delay: number) {
|
|
180
|
+
this._timer = setTimeout(this._check, delay);
|
|
181
|
+
}
|
|
182
|
+
|
|
159
183
|
setup() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
["mousemove", "keydown", "click", "scroll", "touchstart"].forEach(
|
|
171
|
-
(event) => {
|
|
172
|
-
window.addEventListener(event, reset, { passive: true });
|
|
173
|
-
this._listeners.push([event, reset]);
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
reset();
|
|
178
|
-
this._view = { idle };
|
|
179
|
-
return this._view;
|
|
184
|
+
return { idle: this._idle };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
onMount() {
|
|
188
|
+
for (const event of ACTIVITY_EVENTS) {
|
|
189
|
+
window.addEventListener(event, this._onActivity, { passive: true });
|
|
190
|
+
}
|
|
191
|
+
clearTimeout(this._timer);
|
|
192
|
+
this._timer = undefined;
|
|
193
|
+
this._onActivity();
|
|
180
194
|
}
|
|
181
195
|
|
|
182
196
|
onUnmount() {
|
|
183
197
|
clearTimeout(this._timer);
|
|
184
|
-
this.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
this._timer = undefined;
|
|
199
|
+
for (const event of ACTIVITY_EVENTS) {
|
|
200
|
+
window.removeEventListener(event, this._onActivity);
|
|
201
|
+
}
|
|
188
202
|
}
|
|
189
203
|
}
|