@voquill/desktop-utils 0.3.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/dist/activation.d.ts +28 -0
- package/dist/activation.d.ts.map +1 -0
- package/dist/activation.js +128 -0
- package/dist/hotkey.d.ts +45 -0
- package/dist/hotkey.d.ts.map +1 -0
- package/dist/hotkey.js +179 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/package.json +32 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class ActivationController {
|
|
2
|
+
private _isActive;
|
|
3
|
+
private _isLocked;
|
|
4
|
+
private ignoreNextActivation;
|
|
5
|
+
private deactivateTimer;
|
|
6
|
+
private pressTimestamp;
|
|
7
|
+
private lastReleaseTimestamp;
|
|
8
|
+
private toggleInProgress;
|
|
9
|
+
private onActivateRef;
|
|
10
|
+
private onDeactivateRef;
|
|
11
|
+
constructor(onActivate: () => void, onDeactivate: () => void);
|
|
12
|
+
setCallbacks(onActivate: () => void, onDeactivate: () => void): void;
|
|
13
|
+
get isActive(): boolean;
|
|
14
|
+
get isLocked(): boolean;
|
|
15
|
+
get shouldIgnoreActivation(): boolean;
|
|
16
|
+
get hasHadRelease(): boolean;
|
|
17
|
+
private clearPendingDeactivation;
|
|
18
|
+
private doActivate;
|
|
19
|
+
private doDeactivate;
|
|
20
|
+
handlePress(): void;
|
|
21
|
+
handleRelease(): void;
|
|
22
|
+
toggle(): void;
|
|
23
|
+
reset(): void;
|
|
24
|
+
forceReset(): void;
|
|
25
|
+
clearIgnore(): void;
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=activation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activation.d.ts","sourceRoot":"","sources":["../src/activation.ts"],"names":[],"mappings":"AAEA,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,eAAe,CAA6B;gBAExC,UAAU,EAAE,MAAM,IAAI,EAAE,YAAY,EAAE,MAAM,IAAI;IAK5D,YAAY,CAAC,UAAU,EAAE,MAAM,IAAI,EAAE,YAAY,EAAE,MAAM,IAAI,GAAG,IAAI;IAKpE,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,sBAAsB,IAAI,OAAO,CAEpC;IAED,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,YAAY;IAcpB,WAAW,IAAI,IAAI;IAenB,aAAa,IAAI,IAAI;IAuBrB,MAAM,IAAI,IAAI;IAkBd,KAAK,IAAI,IAAI;IAOb,UAAU,IAAI,IAAI;IAQlB,WAAW,IAAI,IAAI;IAInB,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const TAP_THRESHOLD_MS = 500;
|
|
2
|
+
export class ActivationController {
|
|
3
|
+
constructor(onActivate, onDeactivate) {
|
|
4
|
+
this._isActive = false;
|
|
5
|
+
this._isLocked = false;
|
|
6
|
+
this.ignoreNextActivation = false;
|
|
7
|
+
this.deactivateTimer = null;
|
|
8
|
+
this.pressTimestamp = null;
|
|
9
|
+
this.lastReleaseTimestamp = null;
|
|
10
|
+
this.toggleInProgress = false;
|
|
11
|
+
this.onActivateRef = null;
|
|
12
|
+
this.onDeactivateRef = null;
|
|
13
|
+
this.onActivateRef = onActivate;
|
|
14
|
+
this.onDeactivateRef = onDeactivate;
|
|
15
|
+
}
|
|
16
|
+
setCallbacks(onActivate, onDeactivate) {
|
|
17
|
+
this.onActivateRef = onActivate;
|
|
18
|
+
this.onDeactivateRef = onDeactivate;
|
|
19
|
+
}
|
|
20
|
+
get isActive() {
|
|
21
|
+
return this._isActive;
|
|
22
|
+
}
|
|
23
|
+
get isLocked() {
|
|
24
|
+
return this._isLocked;
|
|
25
|
+
}
|
|
26
|
+
get shouldIgnoreActivation() {
|
|
27
|
+
return this.ignoreNextActivation;
|
|
28
|
+
}
|
|
29
|
+
get hasHadRelease() {
|
|
30
|
+
return this.lastReleaseTimestamp !== null;
|
|
31
|
+
}
|
|
32
|
+
clearPendingDeactivation() {
|
|
33
|
+
if (this.deactivateTimer) {
|
|
34
|
+
clearTimeout(this.deactivateTimer);
|
|
35
|
+
this.deactivateTimer = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
doActivate(timestamp) {
|
|
39
|
+
if (this._isActive)
|
|
40
|
+
return;
|
|
41
|
+
this.clearPendingDeactivation();
|
|
42
|
+
this._isActive = true;
|
|
43
|
+
this.pressTimestamp = timestamp;
|
|
44
|
+
this.onActivateRef?.();
|
|
45
|
+
}
|
|
46
|
+
doDeactivate() {
|
|
47
|
+
const wasActive = this._isActive;
|
|
48
|
+
this.clearPendingDeactivation();
|
|
49
|
+
this._isActive = false;
|
|
50
|
+
this._isLocked = false;
|
|
51
|
+
this.ignoreNextActivation = false;
|
|
52
|
+
this.pressTimestamp = null;
|
|
53
|
+
if (wasActive) {
|
|
54
|
+
this.onDeactivateRef?.();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
handlePress() {
|
|
58
|
+
if (this.ignoreNextActivation) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
this.clearPendingDeactivation();
|
|
63
|
+
this.pressTimestamp = now;
|
|
64
|
+
if (!this._isActive) {
|
|
65
|
+
this.doActivate(now);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
handleRelease() {
|
|
69
|
+
this.ignoreNextActivation = false;
|
|
70
|
+
this.lastReleaseTimestamp = Date.now();
|
|
71
|
+
if (!this._isActive)
|
|
72
|
+
return;
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
const pressedAt = this.pressTimestamp ?? now;
|
|
75
|
+
const elapsed = now - pressedAt;
|
|
76
|
+
if (elapsed < TAP_THRESHOLD_MS) {
|
|
77
|
+
if (this._isLocked) {
|
|
78
|
+
this.doDeactivate();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this._isLocked = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (!this._isLocked) {
|
|
86
|
+
this.doDeactivate();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
toggle() {
|
|
91
|
+
if (this.toggleInProgress) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.toggleInProgress = true;
|
|
95
|
+
try {
|
|
96
|
+
if (this._isActive) {
|
|
97
|
+
this.doDeactivate();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this._isLocked = true;
|
|
101
|
+
this.lastReleaseTimestamp = Date.now();
|
|
102
|
+
this.doActivate(Date.now());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
this.toggleInProgress = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
reset() {
|
|
110
|
+
this.ignoreNextActivation = false;
|
|
111
|
+
this.lastReleaseTimestamp = null;
|
|
112
|
+
this.clearPendingDeactivation();
|
|
113
|
+
this.doDeactivate();
|
|
114
|
+
}
|
|
115
|
+
forceReset() {
|
|
116
|
+
this._isActive = false;
|
|
117
|
+
this._isLocked = false;
|
|
118
|
+
this.ignoreNextActivation = false;
|
|
119
|
+
this.pressTimestamp = null;
|
|
120
|
+
this.clearPendingDeactivation();
|
|
121
|
+
}
|
|
122
|
+
clearIgnore() {
|
|
123
|
+
this.ignoreNextActivation = false;
|
|
124
|
+
}
|
|
125
|
+
dispose() {
|
|
126
|
+
this.clearPendingDeactivation();
|
|
127
|
+
}
|
|
128
|
+
}
|
package/dist/hotkey.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ActivationController } from "./activation";
|
|
2
|
+
type HoldAction = {
|
|
3
|
+
controller: ActivationController;
|
|
4
|
+
combos: string[][];
|
|
5
|
+
triggerCount: number;
|
|
6
|
+
};
|
|
7
|
+
export type UseHotkeyHoldManyArgs = {
|
|
8
|
+
actions: HoldAction[];
|
|
9
|
+
keysHeld: string[];
|
|
10
|
+
isDisabled?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type UseHotkeyHoldArgs = {
|
|
13
|
+
controller: ActivationController;
|
|
14
|
+
combos: string[][];
|
|
15
|
+
triggerCount: number;
|
|
16
|
+
keysHeld: string[];
|
|
17
|
+
isDisabled?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type UseHotkeyFireArgs = {
|
|
20
|
+
combos: string[][];
|
|
21
|
+
triggerCount: number;
|
|
22
|
+
keysHeld: string[];
|
|
23
|
+
isDisabled?: boolean;
|
|
24
|
+
onFire?: () => void;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Drives one or more {@link ActivationController}s from a press/release model
|
|
28
|
+
* over the supplied keys. The consumer owns all state (held keys, combos per
|
|
29
|
+
* action, trigger counts) and feeds it in; this hook is pure behavior.
|
|
30
|
+
*
|
|
31
|
+
* Controller lifetimes are the consumer's responsibility — this hook does not
|
|
32
|
+
* call `.dispose()`.
|
|
33
|
+
*/
|
|
34
|
+
export declare const useHotkeyHoldMany: (args: UseHotkeyHoldManyArgs) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Single-controller variant of {@link useHotkeyHoldMany}.
|
|
37
|
+
*/
|
|
38
|
+
export declare const useHotkeyHold: (args: UseHotkeyHoldArgs) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Fires `onFire` on a press-then-release (tap) that matches one of the combos,
|
|
41
|
+
* and also when `triggerCount` increments. The consumer owns all state.
|
|
42
|
+
*/
|
|
43
|
+
export declare const useHotkeyFire: (args: UseHotkeyFireArgs) => void;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=hotkey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotkey.d.ts","sourceRoot":"","sources":["../src/hotkey.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD,KAAK,UAAU,GAAG;IAChB,UAAU,EAAE,oBAAoB,CAAC;IACjC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,oBAAoB,CAAC;IACjC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,qBAAqB,KAAG,IAgG/D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,iBAAiB,KAAG,IAgBvD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,iBAAiB,KAAG,IAiHvD,CAAC"}
|
package/dist/hotkey.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Drives one or more {@link ActivationController}s from a press/release model
|
|
4
|
+
* over the supplied keys. The consumer owns all state (held keys, combos per
|
|
5
|
+
* action, trigger counts) and feeds it in; this hook is pure behavior.
|
|
6
|
+
*
|
|
7
|
+
* Controller lifetimes are the consumer's responsibility — this hook does not
|
|
8
|
+
* call `.dispose()`.
|
|
9
|
+
*/
|
|
10
|
+
export const useHotkeyHoldMany = (args) => {
|
|
11
|
+
const isDisabled = Boolean(args.isDisabled);
|
|
12
|
+
const { actions, keysHeld } = args;
|
|
13
|
+
const combosSignature = useMemo(() => actions.map((a) => JSON.stringify(a.combos)).join("|"), [actions]);
|
|
14
|
+
const wasPressedRef = useRef(new Map());
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const normalize = (key) => key.toLowerCase();
|
|
17
|
+
const matchesCombo = (held, combo) => {
|
|
18
|
+
if (combo.length === 0) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const uniqueHeld = Array.from(new Set(held.map((key) => normalize(key))));
|
|
22
|
+
const required = Array.from(new Set(combo.map((key) => normalize(key))));
|
|
23
|
+
if (uniqueHeld.length !== required.length) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const heldSet = new Set(uniqueHeld);
|
|
27
|
+
return required.every((key) => heldSet.has(key));
|
|
28
|
+
};
|
|
29
|
+
for (const action of actions) {
|
|
30
|
+
const availableCombos = action.combos;
|
|
31
|
+
const wasPressed = wasPressedRef.current.get(action.controller) ?? false;
|
|
32
|
+
const isPressed = availableCombos.some((combo) => matchesCombo(keysHeld, combo));
|
|
33
|
+
if (isDisabled) {
|
|
34
|
+
wasPressedRef.current.set(action.controller, isPressed);
|
|
35
|
+
action.controller.reset();
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (action.controller.isActive &&
|
|
39
|
+
!wasPressed &&
|
|
40
|
+
!action.controller.hasHadRelease) {
|
|
41
|
+
action.controller.forceReset();
|
|
42
|
+
}
|
|
43
|
+
if (availableCombos.length === 0) {
|
|
44
|
+
wasPressedRef.current.set(action.controller, false);
|
|
45
|
+
action.controller.reset();
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (isPressed && !wasPressed) {
|
|
49
|
+
if (action.controller.shouldIgnoreActivation) {
|
|
50
|
+
wasPressedRef.current.set(action.controller, isPressed);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
action.controller.handlePress();
|
|
54
|
+
}
|
|
55
|
+
else if (!isPressed && wasPressed) {
|
|
56
|
+
action.controller.clearIgnore();
|
|
57
|
+
action.controller.handleRelease();
|
|
58
|
+
}
|
|
59
|
+
wasPressedRef.current.set(action.controller, isPressed);
|
|
60
|
+
}
|
|
61
|
+
}, [keysHeld, combosSignature, actions, isDisabled]);
|
|
62
|
+
const triggerSignature = useMemo(() => actions.map((a) => a.triggerCount).join(","), [actions]);
|
|
63
|
+
const prevTriggerCountsRef = useRef(new Map());
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!isDisabled) {
|
|
66
|
+
for (const action of actions) {
|
|
67
|
+
const prev = prevTriggerCountsRef.current.get(action.controller) ?? 0;
|
|
68
|
+
const curr = action.triggerCount;
|
|
69
|
+
if (curr > prev) {
|
|
70
|
+
action.controller.toggle();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const action of actions) {
|
|
75
|
+
prevTriggerCountsRef.current.set(action.controller, action.triggerCount);
|
|
76
|
+
}
|
|
77
|
+
}, [triggerSignature, isDisabled, actions]);
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Single-controller variant of {@link useHotkeyHoldMany}.
|
|
81
|
+
*/
|
|
82
|
+
export const useHotkeyHold = (args) => {
|
|
83
|
+
const actions = useMemo(() => [
|
|
84
|
+
{
|
|
85
|
+
controller: args.controller,
|
|
86
|
+
combos: args.combos,
|
|
87
|
+
triggerCount: args.triggerCount,
|
|
88
|
+
},
|
|
89
|
+
], [args.controller, args.combos, args.triggerCount]);
|
|
90
|
+
useHotkeyHoldMany({
|
|
91
|
+
actions,
|
|
92
|
+
keysHeld: args.keysHeld,
|
|
93
|
+
isDisabled: args.isDisabled,
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Fires `onFire` on a press-then-release (tap) that matches one of the combos,
|
|
98
|
+
* and also when `triggerCount` increments. The consumer owns all state.
|
|
99
|
+
*/
|
|
100
|
+
export const useHotkeyFire = (args) => {
|
|
101
|
+
const isDisabled = Boolean(args.isDisabled);
|
|
102
|
+
const { combos, triggerCount, keysHeld, onFire } = args;
|
|
103
|
+
const previousKeysHeldRef = useRef([]);
|
|
104
|
+
const comboStateRef = useRef(new Map());
|
|
105
|
+
const wasDisabledRef = useRef(false);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (isDisabled) {
|
|
108
|
+
previousKeysHeldRef.current = keysHeld;
|
|
109
|
+
comboStateRef.current.clear();
|
|
110
|
+
wasDisabledRef.current = true;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const wasDisabled = wasDisabledRef.current;
|
|
114
|
+
wasDisabledRef.current = false;
|
|
115
|
+
const normalize = (key) => key.toLowerCase();
|
|
116
|
+
const toNormalizedSet = (keys) => new Set(keys.map((key) => normalize(key)));
|
|
117
|
+
const getComboId = (requiredKeys) => Array.from(requiredKeys).sort().join("+");
|
|
118
|
+
const previousSet = toNormalizedSet(previousKeysHeldRef.current);
|
|
119
|
+
const currentSet = toNormalizedSet(keysHeld);
|
|
120
|
+
const activeComboIds = new Set();
|
|
121
|
+
let shouldFire = false;
|
|
122
|
+
for (const combo of combos) {
|
|
123
|
+
if (combo.length === 0) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const requiredSet = toNormalizedSet(combo);
|
|
127
|
+
if (requiredSet.size === 0) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const comboId = getComboId(requiredSet);
|
|
131
|
+
activeComboIds.add(comboId);
|
|
132
|
+
const comboState = comboStateRef.current.get(comboId) ?? {
|
|
133
|
+
contaminated: false,
|
|
134
|
+
};
|
|
135
|
+
const previousIncludesAll = Array.from(requiredSet).every((key) => previousSet.has(key));
|
|
136
|
+
const currentIncludesAll = Array.from(requiredSet).every((key) => currentSet.has(key));
|
|
137
|
+
const previousExact = previousIncludesAll && previousSet.size === requiredSet.size;
|
|
138
|
+
const currentExact = currentIncludesAll && currentSet.size === requiredSet.size;
|
|
139
|
+
if (wasDisabled && currentIncludesAll) {
|
|
140
|
+
comboState.contaminated = true;
|
|
141
|
+
}
|
|
142
|
+
if (!previousIncludesAll && currentIncludesAll) {
|
|
143
|
+
comboState.contaminated = false;
|
|
144
|
+
}
|
|
145
|
+
if (currentIncludesAll && !currentExact) {
|
|
146
|
+
comboState.contaminated = true;
|
|
147
|
+
}
|
|
148
|
+
if (previousExact &&
|
|
149
|
+
!currentExact &&
|
|
150
|
+
!currentIncludesAll &&
|
|
151
|
+
!comboState.contaminated) {
|
|
152
|
+
shouldFire = true;
|
|
153
|
+
}
|
|
154
|
+
if (!currentIncludesAll) {
|
|
155
|
+
comboState.contaminated = false;
|
|
156
|
+
}
|
|
157
|
+
comboStateRef.current.set(comboId, comboState);
|
|
158
|
+
if (shouldFire) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const comboId of comboStateRef.current.keys()) {
|
|
163
|
+
if (!activeComboIds.has(comboId)) {
|
|
164
|
+
comboStateRef.current.delete(comboId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (shouldFire) {
|
|
168
|
+
onFire?.();
|
|
169
|
+
}
|
|
170
|
+
previousKeysHeldRef.current = keysHeld;
|
|
171
|
+
}, [keysHeld, combos, isDisabled, onFire]);
|
|
172
|
+
const prevTriggerCountRef = useRef(triggerCount);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (!isDisabled && triggerCount > prevTriggerCountRef.current) {
|
|
175
|
+
onFire?.();
|
|
176
|
+
}
|
|
177
|
+
prevTriggerCountRef.current = triggerCount;
|
|
178
|
+
}, [triggerCount, isDisabled, onFire]);
|
|
179
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voquill/desktop-utils",
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "Shared React hooks and logic for Voquill desktop-style clients.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/voquill/voquill.git",
|
|
8
|
+
"directory": "packages/desktop-utils"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"react": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/react": "^19.0.0",
|
|
22
|
+
"@voquill/typescript-config": "workspace:*",
|
|
23
|
+
"react": "^19.2.4",
|
|
24
|
+
"typescript": "5.9.2"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|