@fivem-nui/svelte 0.0.0-stage → 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/README.md +284 -2
- package/dist/actions/clickOutside/clickOutside.d.ts +24 -0
- package/dist/actions/clickOutside/clickOutside.d.ts.map +1 -0
- package/dist/actions/clickOutside/clickOutside.js +62 -0
- package/dist/actions/clickOutside/clickOutside.js.map +1 -0
- package/dist/actions/clickOutside/handler.d.ts +11 -0
- package/dist/actions/clickOutside/handler.d.ts.map +1 -0
- package/dist/actions/clickOutside/handler.js +45 -0
- package/dist/actions/clickOutside/handler.js.map +1 -0
- package/dist/actions/clickOutside/index.d.ts +4 -0
- package/dist/actions/clickOutside/index.d.ts.map +1 -0
- package/dist/actions/clickOutside/index.js +4 -0
- package/dist/actions/clickOutside/index.js.map +1 -0
- package/dist/actions/clickOutside/types.d.ts +36 -0
- package/dist/actions/clickOutside/types.d.ts.map +1 -0
- package/dist/actions/clickOutside/types.js +6 -0
- package/dist/actions/clickOutside/types.js.map +1 -0
- package/dist/actions/clickOutside.d.ts +63 -0
- package/dist/actions/clickOutside.d.ts.map +1 -0
- package/dist/actions/clickOutside.js +106 -0
- package/dist/actions/clickOutside.js.map +1 -0
- package/dist/actions/escape/escape.d.ts +26 -0
- package/dist/actions/escape/escape.d.ts.map +1 -0
- package/dist/actions/escape/escape.js +74 -0
- package/dist/actions/escape/escape.js.map +1 -0
- package/dist/actions/escape/handler.d.ts +11 -0
- package/dist/actions/escape/handler.d.ts.map +1 -0
- package/dist/actions/escape/handler.js +50 -0
- package/dist/actions/escape/handler.js.map +1 -0
- package/dist/actions/escape/index.d.ts +4 -0
- package/dist/actions/escape/index.d.ts.map +1 -0
- package/dist/actions/escape/index.js +4 -0
- package/dist/actions/escape/index.js.map +1 -0
- package/dist/actions/escape/types.d.ts +70 -0
- package/dist/actions/escape/types.d.ts.map +1 -0
- package/dist/actions/escape/types.js +12 -0
- package/dist/actions/escape/types.js.map +1 -0
- package/dist/actions/escape.d.ts +99 -0
- package/dist/actions/escape.d.ts.map +1 -0
- package/dist/actions/escape.js +128 -0
- package/dist/actions/escape.js.map +1 -0
- package/dist/actions/focusTrap/focusTrap.d.ts +19 -0
- package/dist/actions/focusTrap/focusTrap.d.ts.map +1 -0
- package/dist/actions/focusTrap/focusTrap.js +198 -0
- package/dist/actions/focusTrap/focusTrap.js.map +1 -0
- package/dist/actions/focusTrap/focusables.d.ts +39 -0
- package/dist/actions/focusTrap/focusables.d.ts.map +1 -0
- package/dist/actions/focusTrap/focusables.js +142 -0
- package/dist/actions/focusTrap/focusables.js.map +1 -0
- package/dist/actions/focusTrap/index.d.ts +5 -0
- package/dist/actions/focusTrap/index.d.ts.map +1 -0
- package/dist/actions/focusTrap/index.js +5 -0
- package/dist/actions/focusTrap/index.js.map +1 -0
- package/dist/actions/focusTrap/trapStack.d.ts +33 -0
- package/dist/actions/focusTrap/trapStack.d.ts.map +1 -0
- package/dist/actions/focusTrap/trapStack.js +84 -0
- package/dist/actions/focusTrap/trapStack.js.map +1 -0
- package/dist/actions/focusTrap/types.d.ts +50 -0
- package/dist/actions/focusTrap/types.d.ts.map +1 -0
- package/dist/actions/focusTrap/types.js +10 -0
- package/dist/actions/focusTrap/types.js.map +1 -0
- package/dist/actions/focusTrap.d.ts +24 -0
- package/dist/actions/focusTrap.d.ts.map +1 -0
- package/dist/actions/focusTrap.js +201 -0
- package/dist/actions/focusTrap.js.map +1 -0
- package/dist/actions/focusTrap.types.d.ts +50 -0
- package/dist/actions/focusTrap.types.d.ts.map +1 -0
- package/dist/actions/focusTrap.types.js +10 -0
- package/dist/actions/focusTrap.types.js.map +1 -0
- package/dist/actions/focusables.d.ts +39 -0
- package/dist/actions/focusables.d.ts.map +1 -0
- package/dist/actions/focusables.js +142 -0
- package/dist/actions/focusables.js.map +1 -0
- package/dist/actions/index.d.ts +5 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +5 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/layer/index.d.ts +4 -0
- package/dist/actions/layer/index.d.ts.map +1 -0
- package/dist/actions/layer/index.js +4 -0
- package/dist/actions/layer/index.js.map +1 -0
- package/dist/actions/layer/layer.d.ts +28 -0
- package/dist/actions/layer/layer.d.ts.map +1 -0
- package/dist/actions/layer/layer.js +118 -0
- package/dist/actions/layer/layer.js.map +1 -0
- package/dist/actions/layer/layerStack.d.ts +10 -0
- package/dist/actions/layer/layerStack.d.ts.map +1 -0
- package/dist/actions/layer/layerStack.js +153 -0
- package/dist/actions/layer/layerStack.js.map +1 -0
- package/dist/actions/layer/types.d.ts +105 -0
- package/dist/actions/layer/types.d.ts.map +1 -0
- package/dist/actions/layer/types.js +14 -0
- package/dist/actions/layer/types.js.map +1 -0
- package/dist/actions/layer.d.ts +133 -0
- package/dist/actions/layer.d.ts.map +1 -0
- package/dist/actions/layer.js +269 -0
- package/dist/actions/layer.js.map +1 -0
- package/dist/actions/trapStack.d.ts +33 -0
- package/dist/actions/trapStack.d.ts.map +1 -0
- package/dist/actions/trapStack.js +84 -0
- package/dist/actions/trapStack.js.map +1 -0
- package/dist/client.d.ts +179 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +176 -0
- package/dist/client.js.map +1 -0
- package/dist/context/index.d.ts +68 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +112 -0
- package/dist/context/index.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/mock/index.d.ts +27 -0
- package/dist/mock/index.d.ts.map +1 -0
- package/dist/mock/index.js +137 -0
- package/dist/mock/index.js.map +1 -0
- package/dist/mock/interceptor.d.ts +19 -0
- package/dist/mock/interceptor.d.ts.map +1 -0
- package/dist/mock/interceptor.js +128 -0
- package/dist/mock/interceptor.js.map +1 -0
- package/dist/mock/types.d.ts +100 -0
- package/dist/mock/types.d.ts.map +1 -0
- package/dist/mock/types.js +27 -0
- package/dist/mock/types.js.map +1 -0
- package/dist/mock/url.d.ts +14 -0
- package/dist/mock/url.d.ts.map +1 -0
- package/dist/mock/url.js +38 -0
- package/dist/mock/url.js.map +1 -0
- package/dist/runes/clickOutside.svelte.d.ts +29 -0
- package/dist/runes/clickOutside.svelte.d.ts.map +1 -0
- package/dist/runes/clickOutside.svelte.js +42 -0
- package/dist/runes/clickOutside.svelte.js.map +1 -0
- package/dist/runes/escape.svelte.d.ts +34 -0
- package/dist/runes/escape.svelte.d.ts.map +1 -0
- package/dist/runes/escape.svelte.js +44 -0
- package/dist/runes/escape.svelte.js.map +1 -0
- package/dist/runes/focusTrap.svelte.d.ts +21 -0
- package/dist/runes/focusTrap.svelte.d.ts.map +1 -0
- package/dist/runes/focusTrap.svelte.js +37 -0
- package/dist/runes/focusTrap.svelte.js.map +1 -0
- package/dist/runes/index.svelte.d.ts +8 -0
- package/dist/runes/index.svelte.d.ts.map +1 -0
- package/dist/runes/index.svelte.js +8 -0
- package/dist/runes/index.svelte.js.map +1 -0
- package/dist/runes/layer.svelte.d.ts +36 -0
- package/dist/runes/layer.svelte.d.ts.map +1 -0
- package/dist/runes/layer.svelte.js +48 -0
- package/dist/runes/layer.svelte.js.map +1 -0
- package/dist/runes/query.svelte.d.ts +104 -0
- package/dist/runes/query.svelte.d.ts.map +1 -0
- package/dist/runes/query.svelte.js +179 -0
- package/dist/runes/query.svelte.js.map +1 -0
- package/dist/runes/state.svelte.d.ts +72 -0
- package/dist/runes/state.svelte.d.ts.map +1 -0
- package/dist/runes/state.svelte.js +98 -0
- package/dist/runes/state.svelte.js.map +1 -0
- package/dist/runes/window.svelte.d.ts +120 -0
- package/dist/runes/window.svelte.d.ts.map +1 -0
- package/dist/runes/window.svelte.js +196 -0
- package/dist/runes/window.svelte.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/nui.d.ts +34 -0
- package/dist/types/nui.d.ts.map +1 -0
- package/dist/types/nui.js +17 -0
- package/dist/types/nui.js.map +1 -0
- package/dist/utils/env.d.ts +21 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +36 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/error.d.ts +11 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +30 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/keyboard.d.ts +127 -0
- package/dist/utils/keyboard.d.ts.map +1 -0
- package/dist/utils/keyboard.js +152 -0
- package/dist/utils/keyboard.js.map +1 -0
- package/package.json +76 -4
- package/src/actions/clickOutside/clickOutside.ts +78 -0
- package/src/actions/clickOutside/handler.ts +49 -0
- package/src/actions/clickOutside/index.ts +11 -0
- package/src/actions/clickOutside/types.ts +54 -0
- package/src/actions/escape/escape.ts +90 -0
- package/src/actions/escape/handler.ts +52 -0
- package/src/actions/escape/index.ts +11 -0
- package/src/actions/escape/types.ts +88 -0
- package/src/actions/focusTrap/focusTrap.ts +228 -0
- package/src/actions/focusTrap/focusables.ts +155 -0
- package/src/actions/focusTrap/index.ts +22 -0
- package/src/actions/focusTrap/trapStack.ts +109 -0
- package/src/actions/focusTrap/types.ts +64 -0
- package/src/actions/index.ts +35 -0
- package/src/actions/layer/index.ts +13 -0
- package/src/actions/layer/layer.ts +140 -0
- package/src/actions/layer/layerStack.ts +169 -0
- package/src/actions/layer/types.ts +132 -0
- package/src/client.ts +323 -0
- package/src/context/index.ts +127 -0
- package/src/index.ts +53 -0
- package/src/mock/index.ts +176 -0
- package/src/mock/interceptor.ts +177 -0
- package/src/mock/types.ts +125 -0
- package/src/mock/url.ts +37 -0
- package/src/runes/clickOutside.svelte.ts +53 -0
- package/src/runes/escape.svelte.ts +71 -0
- package/src/runes/focusTrap.svelte.ts +56 -0
- package/src/runes/index.svelte.ts +38 -0
- package/src/runes/layer.svelte.ts +74 -0
- package/src/runes/query.svelte.ts +313 -0
- package/src/runes/state.svelte.ts +175 -0
- package/src/runes/window.svelte.ts +308 -0
- package/src/utils/env.ts +36 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/keyboard.ts +244 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { onDestroy } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* Keyboard modifier keys.
|
|
4
|
+
*/
|
|
5
|
+
export var NuiKeyboardModifier;
|
|
6
|
+
(function (NuiKeyboardModifier) {
|
|
7
|
+
NuiKeyboardModifier["Ctrl"] = "ctrl";
|
|
8
|
+
NuiKeyboardModifier["Alt"] = "alt";
|
|
9
|
+
NuiKeyboardModifier["Shift"] = "shift";
|
|
10
|
+
NuiKeyboardModifier["Meta"] = "meta";
|
|
11
|
+
})(NuiKeyboardModifier || (NuiKeyboardModifier = {}));
|
|
12
|
+
/**
|
|
13
|
+
* Standard special key names commonly used in FiveM NUI shortcuts.
|
|
14
|
+
*/
|
|
15
|
+
export var NuiSpecialKey;
|
|
16
|
+
(function (NuiSpecialKey) {
|
|
17
|
+
NuiSpecialKey["Escape"] = "Escape";
|
|
18
|
+
NuiSpecialKey["Enter"] = "Enter";
|
|
19
|
+
NuiSpecialKey["Tab"] = "Tab";
|
|
20
|
+
NuiSpecialKey["Space"] = " ";
|
|
21
|
+
NuiSpecialKey["Backspace"] = "Backspace";
|
|
22
|
+
NuiSpecialKey["ArrowUp"] = "ArrowUp";
|
|
23
|
+
NuiSpecialKey["ArrowDown"] = "ArrowDown";
|
|
24
|
+
NuiSpecialKey["ArrowLeft"] = "ArrowLeft";
|
|
25
|
+
NuiSpecialKey["ArrowRight"] = "ArrowRight";
|
|
26
|
+
})(NuiSpecialKey || (NuiSpecialKey = {}));
|
|
27
|
+
function isTextInputActive() {
|
|
28
|
+
if (typeof document === 'undefined')
|
|
29
|
+
return false;
|
|
30
|
+
const el = document.activeElement;
|
|
31
|
+
if (!el)
|
|
32
|
+
return false;
|
|
33
|
+
const tag = el.tagName.toLowerCase();
|
|
34
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return el.hasAttribute('contenteditable') && el.getAttribute('contenteditable') !== 'false';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Registers a keyboard shortcut with automatic lifecycle management.
|
|
41
|
+
*
|
|
42
|
+
* Designed for FiveM NUI with support for:
|
|
43
|
+
* - `ignoreInputs`: prevents hotkeys from firing while typing in input fields.
|
|
44
|
+
* - Modifier keys (Ctrl, Alt, Shift, Meta).
|
|
45
|
+
* - Automatic cleanup via `onDestroy` and `AbortController`.
|
|
46
|
+
*
|
|
47
|
+
* @param keys - Key or array of keys (case-insensitive) to listen for.
|
|
48
|
+
* @param handler - Callback function invoked when matching keydown occurs.
|
|
49
|
+
* @param options - Optional shortcut configuration including modifiers and lifecycle strategy.
|
|
50
|
+
* @returns Cleanup function to remove event listeners.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```svelte
|
|
54
|
+
* <script>
|
|
55
|
+
* import { useNuiKey } from '@fivem-nui/svelte';
|
|
56
|
+
* useNuiKey('e', () => interact(), { ignoreInputs: true });
|
|
57
|
+
* </script>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function useNuiKey(keys, handler, options = {}) {
|
|
61
|
+
const targetKeys = (Array.isArray(keys) ? keys : [keys]).map((k) => k.toLowerCase());
|
|
62
|
+
const { capture = false, preventDefault = false, stopPropagation = false, enabled, ignoreInputs = false, ignoreRepeat = false, ctrl, alt, shift, meta } = options;
|
|
63
|
+
let controller = null;
|
|
64
|
+
function isAllowed() {
|
|
65
|
+
if (enabled === undefined)
|
|
66
|
+
return true;
|
|
67
|
+
return typeof enabled === 'function' ? enabled() : enabled;
|
|
68
|
+
}
|
|
69
|
+
function onKeyDown(event) {
|
|
70
|
+
if (!isAllowed())
|
|
71
|
+
return;
|
|
72
|
+
if (ignoreRepeat && event.repeat)
|
|
73
|
+
return;
|
|
74
|
+
if (ignoreInputs && isTextInputActive())
|
|
75
|
+
return;
|
|
76
|
+
if (ctrl !== undefined && event.ctrlKey !== ctrl)
|
|
77
|
+
return;
|
|
78
|
+
if (alt !== undefined && event.altKey !== alt)
|
|
79
|
+
return;
|
|
80
|
+
if (shift !== undefined && event.shiftKey !== shift)
|
|
81
|
+
return;
|
|
82
|
+
if (meta !== undefined && event.metaKey !== meta)
|
|
83
|
+
return;
|
|
84
|
+
if (targetKeys.includes(event.key.toLowerCase())) {
|
|
85
|
+
if (preventDefault) {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
}
|
|
88
|
+
if (stopPropagation) {
|
|
89
|
+
event.stopPropagation();
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
handler(event);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.error('[NuiKey] Error in key handler callback:', err);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (typeof window !== 'undefined') {
|
|
100
|
+
controller = new AbortController();
|
|
101
|
+
window.addEventListener('keydown', onKeyDown, {
|
|
102
|
+
capture,
|
|
103
|
+
signal: controller.signal
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
let isCleanedUp = false;
|
|
107
|
+
function cleanup() {
|
|
108
|
+
if (isCleanedUp)
|
|
109
|
+
return;
|
|
110
|
+
isCleanedUp = true;
|
|
111
|
+
if (controller) {
|
|
112
|
+
controller.abort();
|
|
113
|
+
controller = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (options.autoDestroy !== false) {
|
|
117
|
+
try {
|
|
118
|
+
onDestroy(cleanup);
|
|
119
|
+
}
|
|
120
|
+
catch (lifecycleError) {
|
|
121
|
+
if (options.autoDestroy === true) {
|
|
122
|
+
cleanup();
|
|
123
|
+
throw lifecycleError;
|
|
124
|
+
}
|
|
125
|
+
// Best-effort fallback when autoDestroy is undefined and called outside component
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return cleanup;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Listens for the `Escape` key to close or dismiss NUI panels.
|
|
132
|
+
* In FiveM, ESC is the universal standard for closing NUI windows.
|
|
133
|
+
*
|
|
134
|
+
* @param onEscape - Callback function invoked when the Escape key is pressed.
|
|
135
|
+
* @param options - Optional configuration overriding default key options (`preventDefault` defaults to `true`).
|
|
136
|
+
* @returns Cleanup function to remove the Escape listener.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```svelte
|
|
140
|
+
* <script>
|
|
141
|
+
* import { useNuiEscape, nui } from '@fivem-nui/svelte';
|
|
142
|
+
* useNuiEscape(() => nui.notify('close'));
|
|
143
|
+
* </script>
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export function useNuiEscape(onEscape, options = {}) {
|
|
147
|
+
return useNuiKey(NuiSpecialKey.Escape, onEscape, {
|
|
148
|
+
preventDefault: true,
|
|
149
|
+
...options
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=keyboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../../src/utils/keyboard.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fivem-nui/svelte",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Svelte adapter and toolkit for FiveM NUI communication",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"svelte": "src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"svelte": "./src/index.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./mock": {
|
|
16
|
+
"types": "./dist/mock/index.d.ts",
|
|
17
|
+
"default": "./dist/mock/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./actions": {
|
|
20
|
+
"types": "./dist/actions/index.d.ts",
|
|
21
|
+
"default": "./dist/actions/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"watch": "tsc --watch",
|
|
31
|
+
"check": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"coverage": "vitest run --coverage",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"fivem",
|
|
39
|
+
"nui",
|
|
40
|
+
"svelte",
|
|
41
|
+
"runes",
|
|
42
|
+
"typescript",
|
|
43
|
+
"bridge"
|
|
44
|
+
],
|
|
45
|
+
"author": "Rainbe",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/rian-be/FiveM.Nui.git",
|
|
50
|
+
"directory": "packages/svelte/bridge"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/rian-be/FiveM.Nui/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/rian-be/FiveM.Nui/tree/main/packages/svelte/bridge#readme",
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22.12.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"svelte": ">=5.0.0"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@fivem-nui/core": "^1.0.0",
|
|
67
|
+
"@fivem-nui/error": "^1.0.0"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
71
|
+
"@vitest/coverage-v8": "^5.0.4",
|
|
72
|
+
"happy-dom": "^21.0.3",
|
|
73
|
+
"svelte": "^5.0.0",
|
|
74
|
+
"typescript": "^7.0.2",
|
|
75
|
+
"vite": "^6.0.0",
|
|
76
|
+
"vitest": "^5.0.4"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Action } from 'svelte/action';
|
|
2
|
+
import {
|
|
3
|
+
type NuiClickOutsideInput,
|
|
4
|
+
toClickOutsideOptions
|
|
5
|
+
} from './types';
|
|
6
|
+
import { createClickOutsideHandler } from './handler';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Svelte Action for reliable outside click detection in FiveM CEF.
|
|
10
|
+
*
|
|
11
|
+
* Uses `event.composedPath()` to eliminate the detached DOM node bug when
|
|
12
|
+
* inner elements mutate during click events.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* **Multiple Overlays in CEF**:
|
|
16
|
+
* Because `pointerdown` is used with `capture: true`, clicking into another open NUI panel
|
|
17
|
+
* will trigger an outside click unless that other panel is explicitly included in `ignore`.
|
|
18
|
+
*
|
|
19
|
+
* @param node - The DOM element to monitor for outside clicks.
|
|
20
|
+
* @param parameter - Callback function or configuration options object.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```svelte
|
|
24
|
+
* <div use:nuiClickOutside={() => (isOpen = false)}>...</div>
|
|
25
|
+
* <div use:nuiClickOutside={{ onClickOutside: close, ignore: [triggerEl, '.header-btn'] }}>...</div>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const nuiClickOutside: Action<HTMLElement, NuiClickOutsideInput> = (
|
|
29
|
+
node,
|
|
30
|
+
parameter
|
|
31
|
+
) => {
|
|
32
|
+
let currentParam = parameter;
|
|
33
|
+
let controller: AbortController | null = null;
|
|
34
|
+
|
|
35
|
+
const handler = createClickOutsideHandler(
|
|
36
|
+
() => node,
|
|
37
|
+
() => toClickOutsideOptions(currentParam)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
function getCapture(): boolean {
|
|
41
|
+
const opts = toClickOutsideOptions(currentParam);
|
|
42
|
+
return opts?.capture ?? true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setup() {
|
|
46
|
+
teardown();
|
|
47
|
+
controller = new AbortController();
|
|
48
|
+
document.addEventListener('pointerdown', handler, {
|
|
49
|
+
capture: getCapture(),
|
|
50
|
+
signal: controller.signal
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function teardown() {
|
|
55
|
+
if (controller) {
|
|
56
|
+
controller.abort();
|
|
57
|
+
controller = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setup();
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
update(newParam) {
|
|
65
|
+
const prevCapture = getCapture();
|
|
66
|
+
currentParam = newParam;
|
|
67
|
+
// Only changes to `capture` require re-binding the document listener.
|
|
68
|
+
// All other options (`enabled`, `onClickOutside`, `ignore`)
|
|
69
|
+
// are read lazily on each pointerdown event via getOptions().
|
|
70
|
+
if (getCapture() !== prevCapture) {
|
|
71
|
+
setup();
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
destroy() {
|
|
75
|
+
teardown();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ClickOutsideTarget, NuiClickOutsideParameter } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a DOM Element from an Element reference, CSS selector string, or getter function.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveElement(target: ClickOutsideTarget): Element | null {
|
|
7
|
+
if (typeof target === 'string') {
|
|
8
|
+
return document.querySelector(target);
|
|
9
|
+
}
|
|
10
|
+
if (typeof target === 'function') {
|
|
11
|
+
return target() ?? null;
|
|
12
|
+
}
|
|
13
|
+
return target ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a reusable pointerdown event handler for outside click detection.
|
|
18
|
+
* Shared between Svelte action and composable to eliminate duplication.
|
|
19
|
+
*/
|
|
20
|
+
export function createClickOutsideHandler(
|
|
21
|
+
getNode: () => Element | null | undefined,
|
|
22
|
+
getOptions: () => NuiClickOutsideParameter | undefined
|
|
23
|
+
) {
|
|
24
|
+
return (event: PointerEvent) => {
|
|
25
|
+
const node = getNode();
|
|
26
|
+
const opts = getOptions();
|
|
27
|
+
if (!node?.isConnected || !opts) return;
|
|
28
|
+
|
|
29
|
+
const enabled = typeof opts.enabled === 'function' ? opts.enabled() : opts.enabled ?? true;
|
|
30
|
+
if (!enabled || event.button !== 0) return;
|
|
31
|
+
|
|
32
|
+
const path = event.composedPath();
|
|
33
|
+
if (path.includes(node)) return;
|
|
34
|
+
|
|
35
|
+
for (const item of opts.ignore ?? []) {
|
|
36
|
+
const el = resolveElement(item);
|
|
37
|
+
if (el && path.includes(el)) return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const callback = opts.onClickOutside ?? opts.callback;
|
|
41
|
+
if (callback) {
|
|
42
|
+
try {
|
|
43
|
+
callback(event);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('[NuiClickOutside] Error in onClickOutside callback:', err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { nuiClickOutside } from './clickOutside';
|
|
2
|
+
export {
|
|
3
|
+
type ClickOutsideTarget,
|
|
4
|
+
type NuiClickOutsideParameter,
|
|
5
|
+
type NuiClickOutsideInput,
|
|
6
|
+
toClickOutsideOptions
|
|
7
|
+
} from './types';
|
|
8
|
+
export {
|
|
9
|
+
resolveElement,
|
|
10
|
+
createClickOutsideHandler
|
|
11
|
+
} from './handler';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type ClickOutsideTarget =
|
|
2
|
+
| Element
|
|
3
|
+
| string
|
|
4
|
+
| null
|
|
5
|
+
| undefined
|
|
6
|
+
| (() => Element | null | undefined);
|
|
7
|
+
|
|
8
|
+
export interface NuiClickOutsideParameter {
|
|
9
|
+
/**
|
|
10
|
+
* Callback invoked when a click or touch occurs outside the target node.
|
|
11
|
+
*/
|
|
12
|
+
onClickOutside?: (event: PointerEvent) => void;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated Use `onClickOutside` instead.
|
|
16
|
+
*/
|
|
17
|
+
callback?: (event: PointerEvent) => void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether the outside click detector is active.
|
|
21
|
+
* Can be a static boolean or a reactive getter `() => boolean`.
|
|
22
|
+
* @defaultValue true
|
|
23
|
+
*/
|
|
24
|
+
enabled?: boolean | (() => boolean);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Elements, selectors, or getters to exclude from triggering outside click.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* **Trigger / Toggle Buttons**:
|
|
31
|
+
* Because `capture: true` listens on the capturing phase (`pointerdown` fires before `click`),
|
|
32
|
+
* clicking a toggle/trigger button without adding it to `ignore` will cause an immediate
|
|
33
|
+
* flicker (the outside click closes the popover during `pointerdown`, and the button's
|
|
34
|
+
* subsequent `click` immediately re-opens it). Always pass your toggle button in `ignore`.
|
|
35
|
+
*/
|
|
36
|
+
ignore?: ClickOutsideTarget[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Listen during capture phase to intercept before bubbling mutations.
|
|
40
|
+
* @defaultValue true
|
|
41
|
+
*/
|
|
42
|
+
capture?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type NuiClickOutsideInput =
|
|
46
|
+
| ((event: PointerEvent) => void)
|
|
47
|
+
| NuiClickOutsideParameter;
|
|
48
|
+
|
|
49
|
+
export function toClickOutsideOptions(
|
|
50
|
+
param: NuiClickOutsideInput | undefined
|
|
51
|
+
): NuiClickOutsideParameter | undefined {
|
|
52
|
+
if (!param) return undefined;
|
|
53
|
+
return typeof param === 'function' ? { onClickOutside: param } : param;
|
|
54
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Action } from 'svelte/action';
|
|
2
|
+
import {
|
|
3
|
+
type NuiEscapeInput,
|
|
4
|
+
toEscapeOptions
|
|
5
|
+
} from './types';
|
|
6
|
+
import { resolveTarget, createEscapeHandler } from './handler';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Svelte Action for handling Escape key presses in FiveM NUI.
|
|
10
|
+
*
|
|
11
|
+
* Designed specifically for FiveM NUI where Escape is the universal close/dismiss action.
|
|
12
|
+
* Defaults to listening on `document` so closing works without requiring focus on a specific `div`.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* **Nested Modals & Target Scoping**:
|
|
16
|
+
* When using `target: NuiEscapeTarget.Document` (default), all registered listeners on `document` will receive
|
|
17
|
+
* keydown in registration order. If you have nested modals where only the topmost modal should close,
|
|
18
|
+
* consider using `nuiLayer` (which manages a centralized layer stack so only the topmost active layer dismisses),
|
|
19
|
+
* or configure `ignoreHandled: true`, or use `target: NuiEscapeTarget.Element` with a focus trap (`nuiFocusTrap`) and `stopPropagation: true`.
|
|
20
|
+
*
|
|
21
|
+
* @param node - The DOM element attaching the action.
|
|
22
|
+
* @param parameter - Callback function or configuration options object.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```svelte
|
|
26
|
+
* <div use:nuiEscapeAction={() => win.close()}>...</div>
|
|
27
|
+
* <div use:nuiEscapeAction={{ onEscape: close, stopPropagation: true }}>...</div>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const nuiEscapeAction: Action<HTMLElement, NuiEscapeInput> = (
|
|
31
|
+
node,
|
|
32
|
+
parameter
|
|
33
|
+
) => {
|
|
34
|
+
let currentParam = parameter;
|
|
35
|
+
let controller: AbortController | null = null;
|
|
36
|
+
|
|
37
|
+
const handler = createEscapeHandler(() => toEscapeOptions(currentParam));
|
|
38
|
+
|
|
39
|
+
function getTarget(): EventTarget {
|
|
40
|
+
const opts = toEscapeOptions(currentParam);
|
|
41
|
+
return resolveTarget(node, opts?.target);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getCapture(): boolean {
|
|
45
|
+
const opts = toEscapeOptions(currentParam);
|
|
46
|
+
return opts?.capture ?? false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setup() {
|
|
50
|
+
teardown();
|
|
51
|
+
const eventTarget = getTarget();
|
|
52
|
+
controller = new AbortController();
|
|
53
|
+
eventTarget.addEventListener('keydown', handler as EventListener, {
|
|
54
|
+
capture: getCapture(),
|
|
55
|
+
signal: controller.signal
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function teardown() {
|
|
60
|
+
if (controller) {
|
|
61
|
+
controller.abort();
|
|
62
|
+
controller = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setup();
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
update(newParam) {
|
|
70
|
+
const prevOpts = toEscapeOptions(currentParam);
|
|
71
|
+
currentParam = newParam;
|
|
72
|
+
const nextOpts = toEscapeOptions(currentParam);
|
|
73
|
+
|
|
74
|
+
const prevTarget = resolveTarget(node, prevOpts?.target);
|
|
75
|
+
const nextTarget = resolveTarget(node, nextOpts?.target);
|
|
76
|
+
const prevCapture = prevOpts?.capture ?? false;
|
|
77
|
+
const nextCapture = nextOpts?.capture ?? false;
|
|
78
|
+
|
|
79
|
+
// Only target or capture changes require re-binding DOM listeners.
|
|
80
|
+
// Other options (enabled, onEscape, preventDefault, stopPropagation, ignoreRepeat)
|
|
81
|
+
// are read dynamically inside createEscapeHandler on each keydown event.
|
|
82
|
+
if (prevTarget !== nextTarget || prevCapture !== nextCapture) {
|
|
83
|
+
setup();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
destroy() {
|
|
87
|
+
teardown();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { NuiEscapeTarget, type NuiEscapeParameter } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the appropriate EventTarget to attach the Escape key listener to.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveTarget(node: HTMLElement, targetType?: NuiEscapeTarget): EventTarget {
|
|
7
|
+
if (targetType === NuiEscapeTarget.Window) return window;
|
|
8
|
+
if (targetType === NuiEscapeTarget.Element) return node;
|
|
9
|
+
return document;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a reusable keydown event handler for Escape key detection.
|
|
14
|
+
* Shared between Svelte action and composable to eliminate duplication.
|
|
15
|
+
*/
|
|
16
|
+
export function createEscapeHandler(
|
|
17
|
+
getOptions: () => NuiEscapeParameter | undefined
|
|
18
|
+
) {
|
|
19
|
+
return (event: KeyboardEvent) => {
|
|
20
|
+
const opts = getOptions();
|
|
21
|
+
if (!opts || event.key !== 'Escape' || event.isComposing) return;
|
|
22
|
+
|
|
23
|
+
if (opts.ignoreHandled && event.defaultPrevented) return;
|
|
24
|
+
|
|
25
|
+
const enabled = typeof opts.enabled === 'function' ? opts.enabled() : opts.enabled ?? true;
|
|
26
|
+
const handler = opts.onEscape ?? opts.callback;
|
|
27
|
+
if (!enabled || !handler) return;
|
|
28
|
+
|
|
29
|
+
if ((opts.ignoreRepeat ?? true) && event.repeat) {
|
|
30
|
+
if (opts.preventDefault ?? true) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (opts.preventDefault ?? true) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
}
|
|
39
|
+
if (opts.stopPropagation) {
|
|
40
|
+
event.stopPropagation();
|
|
41
|
+
}
|
|
42
|
+
if (opts.stopImmediatePropagation) {
|
|
43
|
+
event.stopImmediatePropagation();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
handler(event);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error('[NuiEscape] Error in onEscape callback:', err);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export enum NuiEscapeTarget {
|
|
2
|
+
Element = 'element',
|
|
3
|
+
Window = 'window',
|
|
4
|
+
Document = 'document'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface NuiEscapeParameter {
|
|
8
|
+
/**
|
|
9
|
+
* Callback invoked when the Escape key is pressed.
|
|
10
|
+
*/
|
|
11
|
+
onEscape?: (event: KeyboardEvent) => void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Use `onEscape` instead.
|
|
15
|
+
*/
|
|
16
|
+
callback?: (event: KeyboardEvent) => void;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether the Escape listener is active.
|
|
20
|
+
* Accepts a boolean or a reactive getter `() => boolean`.
|
|
21
|
+
* @defaultValue true
|
|
22
|
+
*/
|
|
23
|
+
enabled?: boolean | (() => boolean);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Where to attach the listener:
|
|
27
|
+
* - `NuiEscapeTarget.Document`: listens globally on the document (default; does not require element focus)
|
|
28
|
+
* - `NuiEscapeTarget.Window`: listens globally on the window
|
|
29
|
+
* - `NuiEscapeTarget.Element`: listens only for keydown events within this element (requires the element to have focus, e.g. tabindex="-1")
|
|
30
|
+
* @defaultValue NuiEscapeTarget.Document
|
|
31
|
+
*/
|
|
32
|
+
target?: NuiEscapeTarget;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether to listen during the capturing phase.
|
|
36
|
+
* Useful when inner elements might stop propagation before bubbling reaches window/document.
|
|
37
|
+
* @defaultValue false
|
|
38
|
+
*/
|
|
39
|
+
capture?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether to call `event.preventDefault()`.
|
|
43
|
+
* @defaultValue true
|
|
44
|
+
*/
|
|
45
|
+
preventDefault?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether to stop event propagation to parent elements.
|
|
49
|
+
* @defaultValue false
|
|
50
|
+
*/
|
|
51
|
+
stopPropagation?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether to stop immediate propagation to other listeners on the same target.
|
|
55
|
+
* @defaultValue false
|
|
56
|
+
*/
|
|
57
|
+
stopImmediatePropagation?: boolean;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether to ignore repeated keydown events fired while holding the Escape key down.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* If `true` and `preventDefault: true`, `event.preventDefault()` will still be invoked
|
|
64
|
+
* on repeated keydown events to suppress browser/CEF default actions, but the callback itself
|
|
65
|
+
* will only be executed once on the initial keydown.
|
|
66
|
+
* @defaultValue true
|
|
67
|
+
*/
|
|
68
|
+
ignoreRepeat?: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether to ignore the Escape keydown event if an earlier handler has already called `event.preventDefault()`.
|
|
72
|
+
* Useful when multiple independent Escape listeners are registered on `document` to avoid firing handlers
|
|
73
|
+
* after an inner/prior component has already handled the event.
|
|
74
|
+
* @defaultValue false
|
|
75
|
+
*/
|
|
76
|
+
ignoreHandled?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type NuiEscapeInput =
|
|
80
|
+
| ((event: KeyboardEvent) => void)
|
|
81
|
+
| NuiEscapeParameter;
|
|
82
|
+
|
|
83
|
+
export function toEscapeOptions<T extends NuiEscapeParameter = NuiEscapeParameter>(
|
|
84
|
+
param?: ((event: KeyboardEvent) => void) | T
|
|
85
|
+
): T | undefined {
|
|
86
|
+
if (!param) return undefined;
|
|
87
|
+
return typeof param === 'function' ? ({ onEscape: param } as T) : param;
|
|
88
|
+
}
|