@fivem-nui/svelte-components 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.
Files changed (143) hide show
  1. package/README.md +101 -2
  2. package/dist/components/actions/NuiButton.svelte +292 -0
  3. package/dist/components/actions/NuiButton.svelte.d.ts +60 -0
  4. package/dist/components/data/NuiBadge.svelte +277 -0
  5. package/dist/components/data/NuiBadge.svelte.d.ts +74 -0
  6. package/dist/components/data/NuiCodeBlock.svelte +256 -0
  7. package/dist/components/data/NuiCodeBlock.svelte.d.ts +49 -0
  8. package/dist/components/data/NuiList.svelte +341 -0
  9. package/dist/components/data/NuiList.svelte.d.ts +52 -0
  10. package/dist/components/data/NuiProgressBar.svelte +264 -0
  11. package/dist/components/data/NuiProgressBar.svelte.d.ts +51 -0
  12. package/dist/components/data/NuiRichText.svelte +260 -0
  13. package/dist/components/data/NuiRichText.svelte.d.ts +33 -0
  14. package/dist/components/data/NuiTable.svelte +470 -0
  15. package/dist/components/data/NuiTable.svelte.d.ts +53 -0
  16. package/dist/components/data/chart/NuiChart.svelte +384 -0
  17. package/dist/components/data/chart/NuiChart.svelte.d.ts +49 -0
  18. package/dist/components/data/chart/color.d.ts +9 -0
  19. package/dist/components/data/chart/color.js +78 -0
  20. package/dist/components/data/chart/index.d.ts +10 -0
  21. package/dist/components/data/chart/index.js +10 -0
  22. package/dist/components/data/chart/registry.d.ts +14 -0
  23. package/dist/components/data/chart/registry.js +53 -0
  24. package/dist/components/data/chart/renderers/bar.d.ts +8 -0
  25. package/dist/components/data/chart/renderers/bar.js +79 -0
  26. package/dist/components/data/chart/renderers/base.d.ts +18 -0
  27. package/dist/components/data/chart/renderers/base.js +10 -0
  28. package/dist/components/data/chart/renderers/line.d.ts +8 -0
  29. package/dist/components/data/chart/renderers/line.js +89 -0
  30. package/dist/components/data/chart/renderers/pie.d.ts +10 -0
  31. package/dist/components/data/chart/renderers/pie.js +55 -0
  32. package/dist/components/data/chart/scale.d.ts +22 -0
  33. package/dist/components/data/chart/scale.js +101 -0
  34. package/dist/components/data/chart/types.d.ts +84 -0
  35. package/dist/components/data/chart/types.js +1 -0
  36. package/dist/components/data/chart/validator.d.ts +10 -0
  37. package/dist/components/data/chart/validator.js +82 -0
  38. package/dist/components/forms/NuiCheckbox.svelte +218 -0
  39. package/dist/components/forms/NuiCheckbox.svelte.d.ts +37 -0
  40. package/dist/components/forms/NuiDatepicker.svelte +755 -0
  41. package/dist/components/forms/NuiDatepicker.svelte.d.ts +34 -0
  42. package/dist/components/forms/NuiFileInput.svelte +1047 -0
  43. package/dist/components/forms/NuiFileInput.svelte.d.ts +93 -0
  44. package/dist/components/forms/NuiFileInput.types.d.ts +28 -0
  45. package/dist/components/forms/NuiFileInput.types.js +1 -0
  46. package/dist/components/forms/NuiForm.svelte +391 -0
  47. package/dist/components/forms/NuiForm.svelte.d.ts +69 -0
  48. package/dist/components/forms/NuiInput.svelte +296 -0
  49. package/dist/components/forms/NuiInput.svelte.d.ts +62 -0
  50. package/dist/components/forms/NuiRadioGroup.svelte +238 -0
  51. package/dist/components/forms/NuiRadioGroup.svelte.d.ts +47 -0
  52. package/dist/components/forms/NuiSelect.svelte +819 -0
  53. package/dist/components/forms/NuiSelect.svelte.d.ts +49 -0
  54. package/dist/components/layout/NuiCard.svelte +314 -0
  55. package/dist/components/layout/NuiCard.svelte.d.ts +48 -0
  56. package/dist/components/layout/NuiCardGrid.svelte +98 -0
  57. package/dist/components/layout/NuiCardGrid.svelte.d.ts +22 -0
  58. package/dist/components/layout/NuiFlex.svelte +121 -0
  59. package/dist/components/layout/NuiFlex.svelte.d.ts +23 -0
  60. package/dist/components/layout/NuiGrid.svelte +66 -0
  61. package/dist/components/layout/NuiGrid.svelte.d.ts +14 -0
  62. package/dist/components/layout/NuiStack.svelte +138 -0
  63. package/dist/components/layout/NuiStack.svelte.d.ts +26 -0
  64. package/dist/components/navigation/NuiAccordion.svelte +323 -0
  65. package/dist/components/navigation/NuiAccordion.svelte.d.ts +38 -0
  66. package/dist/components/navigation/NuiBreadcrumbs.svelte +133 -0
  67. package/dist/components/navigation/NuiBreadcrumbs.svelte.d.ts +19 -0
  68. package/dist/components/navigation/NuiMenu.svelte +596 -0
  69. package/dist/components/navigation/NuiMenu.svelte.d.ts +28 -0
  70. package/dist/components/navigation/NuiPagination.svelte +306 -0
  71. package/dist/components/navigation/NuiPagination.svelte.d.ts +40 -0
  72. package/dist/components/navigation/NuiSidebar.svelte +186 -0
  73. package/dist/components/navigation/NuiSidebar.svelte.d.ts +26 -0
  74. package/dist/components/navigation/NuiTabs.svelte +249 -0
  75. package/dist/components/navigation/NuiTabs.svelte.d.ts +31 -0
  76. package/dist/components/overlay/NuiDialog.svelte +118 -0
  77. package/dist/components/overlay/NuiDialog.svelte.d.ts +34 -0
  78. package/dist/components/overlay/NuiModal.svelte +992 -0
  79. package/dist/components/overlay/NuiModal.svelte.d.ts +72 -0
  80. package/dist/components/overlay/NuiTooltip.svelte +159 -0
  81. package/dist/components/overlay/NuiTooltip.svelte.d.ts +13 -0
  82. package/dist/composables/useClickOutside.d.ts +43 -0
  83. package/dist/composables/useClickOutside.js +73 -0
  84. package/dist/composables/useFormValidation.d.ts +23 -0
  85. package/dist/composables/useFormValidation.js +72 -0
  86. package/dist/composables/useIdle.svelte.d.ts +18 -0
  87. package/dist/composables/useIdle.svelte.js +144 -0
  88. package/dist/composables/useKeyboardShortcut.svelte.d.ts +114 -0
  89. package/dist/composables/useKeyboardShortcut.svelte.js +153 -0
  90. package/dist/composables/useLocalStorage.svelte.d.ts +22 -0
  91. package/dist/composables/useLocalStorage.svelte.js +105 -0
  92. package/dist/composables/useMediaQuery.svelte.d.ts +51 -0
  93. package/dist/composables/useMediaQuery.svelte.js +80 -0
  94. package/dist/index.d.ts +46 -0
  95. package/dist/index.js +42 -0
  96. package/package.json +58 -3
  97. package/src/components/actions/NuiButton.svelte +292 -0
  98. package/src/components/data/NuiBadge.svelte +277 -0
  99. package/src/components/data/NuiCodeBlock.svelte +256 -0
  100. package/src/components/data/NuiList.svelte +341 -0
  101. package/src/components/data/NuiProgressBar.svelte +264 -0
  102. package/src/components/data/NuiRichText.svelte +260 -0
  103. package/src/components/data/NuiTable.svelte +470 -0
  104. package/src/components/data/chart/NuiChart.svelte +384 -0
  105. package/src/components/data/chart/color.ts +83 -0
  106. package/src/components/data/chart/index.ts +10 -0
  107. package/src/components/data/chart/registry.ts +71 -0
  108. package/src/components/data/chart/renderers/bar.ts +90 -0
  109. package/src/components/data/chart/renderers/base.ts +32 -0
  110. package/src/components/data/chart/renderers/line.ts +99 -0
  111. package/src/components/data/chart/renderers/pie.ts +66 -0
  112. package/src/components/data/chart/scale.ts +139 -0
  113. package/src/components/data/chart/types.ts +98 -0
  114. package/src/components/data/chart/validator.ts +98 -0
  115. package/src/components/forms/NuiCheckbox.svelte +218 -0
  116. package/src/components/forms/NuiDatepicker.svelte +755 -0
  117. package/src/components/forms/NuiFileInput.svelte +1047 -0
  118. package/src/components/forms/NuiFileInput.types.ts +29 -0
  119. package/src/components/forms/NuiForm.svelte +391 -0
  120. package/src/components/forms/NuiInput.svelte +296 -0
  121. package/src/components/forms/NuiRadioGroup.svelte +238 -0
  122. package/src/components/forms/NuiSelect.svelte +819 -0
  123. package/src/components/layout/NuiCard.svelte +314 -0
  124. package/src/components/layout/NuiCardGrid.svelte +98 -0
  125. package/src/components/layout/NuiFlex.svelte +121 -0
  126. package/src/components/layout/NuiGrid.svelte +66 -0
  127. package/src/components/layout/NuiStack.svelte +138 -0
  128. package/src/components/navigation/NuiAccordion.svelte +323 -0
  129. package/src/components/navigation/NuiBreadcrumbs.svelte +133 -0
  130. package/src/components/navigation/NuiMenu.svelte +596 -0
  131. package/src/components/navigation/NuiPagination.svelte +306 -0
  132. package/src/components/navigation/NuiSidebar.svelte +186 -0
  133. package/src/components/navigation/NuiTabs.svelte +249 -0
  134. package/src/components/overlay/NuiDialog.svelte +118 -0
  135. package/src/components/overlay/NuiModal.svelte +992 -0
  136. package/src/components/overlay/NuiTooltip.svelte +159 -0
  137. package/src/composables/useClickOutside.ts +132 -0
  138. package/src/composables/useFormValidation.ts +105 -0
  139. package/src/composables/useIdle.svelte.ts +191 -0
  140. package/src/composables/useKeyboardShortcut.svelte.ts +278 -0
  141. package/src/composables/useLocalStorage.svelte.ts +137 -0
  142. package/src/composables/useMediaQuery.svelte.ts +102 -0
  143. package/src/index.ts +66 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Keyboard shortcut hooks for FiveM NUI.
3
+ */
4
+ export interface KeyboardShortcut {
5
+ /**
6
+ * The key to match case-insensitively (eg. 'e', 'Enter', 'Escape', ' ', 'F1').
7
+ * Ignored if `code` is provided.
8
+ */
9
+ key?: string;
10
+ /**
11
+ * @deprecated Use `key` instead. Alias kept for backwards compatibility.
12
+ */
13
+ event?: KeyboardEvent['key'];
14
+ /**
15
+ * Physical key code to match (e.g. 'KeyE', 'Digit1', 'Space', 'Enter').
16
+ * When provided, `key` is completely ignored in favor of `code`.
17
+ * Highly recommended for layout-independent shortcuts and combinations with Shift (e.g. Shift+1).
18
+ */
19
+ code?: string;
20
+ /**
21
+ * Require Ctrl key to be pressed in the FiveM Windows CEF environment.
22
+ */
23
+ ctrl?: boolean;
24
+ /**
25
+ * Require primary modifier key to be pressed.
26
+ * In FiveM (Windows CEF), this is equivalent to `ctrl`.
27
+ * Note: `ctrl` takes precedence over `mod` if both are defined.
28
+ */
29
+ mod?: boolean;
30
+ /**
31
+ * Require Alt key to be pressed.
32
+ */
33
+ alt?: boolean;
34
+ /**
35
+ * Require Shift key to be pressed.
36
+ */
37
+ shift?: boolean;
38
+ /**
39
+ * Require Meta (Windows key) to be pressed.
40
+ */
41
+ meta?: boolean;
42
+ /**
43
+ * When true, requires modifier keys (Ctrl, Alt, Shift, Meta) to strictly match
44
+ * the specified options. Unspecified modifiers must NOT be pressed.
45
+ * @default false
46
+ */
47
+ exact?: boolean;
48
+ /**
49
+ * Callback invoked when the shortcut matches.
50
+ */
51
+ callback: (event: KeyboardEvent) => void;
52
+ /**
53
+ * Whether the shortcut is currently enabled.
54
+ * Accepts a boolean or a reactive getter `() => boolean`.
55
+ * @default true
56
+ */
57
+ enabled?: boolean | (() => boolean);
58
+ /**
59
+ * Automatically call `event.preventDefault()`.
60
+ * @default true
61
+ */
62
+ preventDefault?: boolean;
63
+ /**
64
+ * Automatically call `event.stopPropagation()`.
65
+ * Stops bubbling up the DOM hierarchy.
66
+ * @default false
67
+ */
68
+ stopPropagation?: boolean;
69
+ /**
70
+ * Automatically call `event.stopImmediatePropagation()`.
71
+ * Prevents other listeners on the same element from being executed.
72
+ * @default false
73
+ */
74
+ stopImmediatePropagation?: boolean;
75
+ /**
76
+ * If true, suppresses shortcut execution when the active element is an input,
77
+ * textarea, select, contenteditable, or ARIA textbox/searchbox.
78
+ * @default false
79
+ */
80
+ ignoreInputs?: boolean;
81
+ /**
82
+ * Ignore repeating keydown events when holding a key down.
83
+ * @default false
84
+ */
85
+ ignoreRepeat?: boolean;
86
+ }
87
+ export interface KeyboardShortcutOptions {
88
+ /**
89
+ * Event target to attach the keydown listener to.
90
+ * Can be an EventTarget (e.g. window, document, HTMLElement) or getter function returning one (e.g. `() => myElement`).
91
+ * Note: The options object itself is evaluated once on init to make the target reactive, pass `target` as a getter function.
92
+ * Defaults to `window` (global NUI shortcuts).
93
+ */
94
+ target?: EventTarget | null | (() => EventTarget | null);
95
+ /**
96
+ * Listen during capture phase. Useful for global NUI handlers to intercept events early.
97
+ * @default false
98
+ */
99
+ capture?: boolean;
100
+ }
101
+ export type KeyboardShortcutsSource = KeyboardShortcut[] | (() => KeyboardShortcut[]);
102
+ export type KeyboardShortcutSource = KeyboardShortcut | (() => KeyboardShortcut);
103
+ /**
104
+ * Registers multiple keyboard shortcuts with reactive lifecycle management.
105
+ *
106
+ * Note:
107
+ * - Must be called within a component initialization context (or wrapped in `$effect.root`).
108
+ * - Array order matters: the first matching shortcut terminates evaluation via `break`.
109
+ */
110
+ export declare function useKeyboardShortcuts(shortcuts: KeyboardShortcutsSource, options?: KeyboardShortcutOptions): () => void;
111
+ /**
112
+ * Registers a single keyboard shortcut with reactive lifecycle management.
113
+ */
114
+ export declare function useKeyboardShortcut(shortcut: KeyboardShortcutSource, options?: KeyboardShortcutOptions): () => void;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Keyboard shortcut hooks for FiveM NUI.
3
+ */
4
+ /**
5
+ * Checks whether the focused element accepts text input, supporting Shadow DOM via event.composedPath().
6
+ */
7
+ function isTextInputFocused(event) {
8
+ if (typeof document === 'undefined')
9
+ return false;
10
+ const path = event.composedPath();
11
+ const el = path[0] instanceof HTMLElement ? path[0] : document.activeElement;
12
+ if (!el || !(el instanceof HTMLElement))
13
+ return false;
14
+ const tag = el.tagName.toLowerCase();
15
+ if (tag === 'input' || tag === 'textarea' || tag === 'select') {
16
+ return true;
17
+ }
18
+ if (el.isContentEditable) {
19
+ return true;
20
+ }
21
+ const role = el.getAttribute('role');
22
+ if (role === 'textbox' || role === 'searchbox') {
23
+ return true;
24
+ }
25
+ if (el.closest('[contenteditable]:not([contenteditable="false"])')) {
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+ function isShortcutActive(shortcut, event) {
31
+ // 1. Cheap checks first (ignoreRepeat)
32
+ if (shortcut.ignoreRepeat && event.repeat)
33
+ return false;
34
+ // 2. Key or Code match (code overrides key entirely)
35
+ if (shortcut.code) {
36
+ if (event.code?.toLowerCase() !== shortcut.code.toLowerCase())
37
+ return false;
38
+ }
39
+ else {
40
+ const expectedKey = (shortcut.key ?? shortcut.event ?? '').toLowerCase();
41
+ if (!expectedKey)
42
+ return false;
43
+ if (event.key?.toLowerCase() !== expectedKey)
44
+ return false;
45
+ }
46
+ // 3. Modifier match
47
+ const expectedCtrl = shortcut.ctrl ?? shortcut.mod;
48
+ if (shortcut.exact) {
49
+ const needCtrl = Boolean(expectedCtrl);
50
+ const needAlt = Boolean(shortcut.alt);
51
+ const needShift = Boolean(shortcut.shift);
52
+ const needMeta = Boolean(shortcut.meta);
53
+ if (event.ctrlKey !== needCtrl)
54
+ return false;
55
+ if (event.altKey !== needAlt)
56
+ return false;
57
+ if (event.shiftKey !== needShift)
58
+ return false;
59
+ if (event.metaKey !== needMeta)
60
+ return false;
61
+ }
62
+ else {
63
+ if (expectedCtrl !== undefined && Boolean(expectedCtrl) !== event.ctrlKey)
64
+ return false;
65
+ if (shortcut.alt !== undefined && Boolean(shortcut.alt) !== event.altKey)
66
+ return false;
67
+ if (shortcut.shift !== undefined && Boolean(shortcut.shift) !== event.shiftKey)
68
+ return false;
69
+ if (shortcut.meta !== undefined && Boolean(shortcut.meta) !== event.metaKey)
70
+ return false;
71
+ }
72
+ // 4. Expensive / side-effect checks last (enabled function, DOM queries for inputs)
73
+ if (shortcut.enabled !== undefined) {
74
+ const isAllowed = typeof shortcut.enabled === 'function' ? shortcut.enabled() : shortcut.enabled;
75
+ if (!isAllowed)
76
+ return false;
77
+ }
78
+ if (shortcut.ignoreInputs && isTextInputFocused(event))
79
+ return false;
80
+ return true;
81
+ }
82
+ /**
83
+ * Registers multiple keyboard shortcuts with reactive lifecycle management.
84
+ *
85
+ * Note:
86
+ * - Must be called within a component initialization context (or wrapped in `$effect.root`).
87
+ * - Array order matters: the first matching shortcut terminates evaluation via `break`.
88
+ */
89
+ export function useKeyboardShortcuts(shortcuts, options) {
90
+ let controller = null;
91
+ function handler(event) {
92
+ // Ignore IME composition events (e.g. active Asian text input)
93
+ if (event.isComposing)
94
+ return;
95
+ const list = typeof shortcuts === 'function' ? shortcuts() : shortcuts;
96
+ if (!list || !Array.isArray(list))
97
+ return;
98
+ for (const shortcut of list) {
99
+ if (isShortcutActive(shortcut, event)) {
100
+ if (shortcut.preventDefault ?? true) {
101
+ event.preventDefault();
102
+ }
103
+ if (shortcut.stopPropagation) {
104
+ event.stopPropagation();
105
+ }
106
+ if (shortcut.stopImmediatePropagation) {
107
+ event.stopImmediatePropagation();
108
+ }
109
+ try {
110
+ shortcut.callback(event);
111
+ }
112
+ catch (err) {
113
+ console.error('[nui-keyboard] Error in shortcut callback:', err);
114
+ }
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ const cleanup = () => {
120
+ if (controller) {
121
+ controller.abort();
122
+ controller = null;
123
+ }
124
+ };
125
+ const setupListener = () => {
126
+ cleanup(); // Prevent memory leaks on re-binding
127
+ const targetFn = options?.target;
128
+ const target = typeof targetFn === 'function' ? targetFn() : targetFn;
129
+ const resolvedTarget = target ?? (typeof window !== 'undefined' ? window : null);
130
+ if (!resolvedTarget)
131
+ return;
132
+ controller = new AbortController();
133
+ resolvedTarget.addEventListener('keydown', handler, {
134
+ capture: options?.capture ?? false,
135
+ signal: controller.signal
136
+ });
137
+ };
138
+ // Lifecycle binding
139
+ $effect(() => {
140
+ setupListener();
141
+ return cleanup;
142
+ });
143
+ return cleanup;
144
+ }
145
+ /**
146
+ * Registers a single keyboard shortcut with reactive lifecycle management.
147
+ */
148
+ export function useKeyboardShortcut(shortcut, options) {
149
+ return useKeyboardShortcuts(() => {
150
+ const item = typeof shortcut === 'function' ? shortcut() : shortcut;
151
+ return item ? [item] : [];
152
+ }, options);
153
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * LocalStorage synchronization hook for FiveM NUI.
3
+ */
4
+ export interface LocalStorageState<T> {
5
+ get current(): T;
6
+ set current(value: T);
7
+ set(value: T | ((prev: T) => T)): void;
8
+ }
9
+ /**
10
+ * LocalStorage synchronization hook.
11
+ *
12
+ * Automatically synchronizes state with `localStorage`, listens for cross-window
13
+ * `storage` events (including removals/clears), and dispatches custom events with
14
+ * source filtering for intra-window CEF (FiveM NUI) synchronization.
15
+ *
16
+ * Note:
17
+ * - Must be called within a component initialization context (or wrapped in `$effect.root`).
18
+ * - Mutating nested properties of objects directly (e.g. `state.current.prop = val`) does not trigger a write; use assignments or `.set()`.
19
+ * - If the key changes dynamically, the state switches to the new key, overwriting uncommitted state.
20
+ * - Write failures (e.g., storage quota exceeded) keep the memory state updated while leaving storage unchanged.
21
+ */
22
+ export declare function useLocalStorage<T>(key: string | (() => string), initialValue: T): LocalStorageState<T>;
@@ -0,0 +1,105 @@
1
+ const STORAGE_EVENT_PREFIX = 'nui-local-storage:';
2
+ /**
3
+ * LocalStorage synchronization hook.
4
+ *
5
+ * Automatically synchronizes state with `localStorage`, listens for cross-window
6
+ * `storage` events (including removals/clears), and dispatches custom events with
7
+ * source filtering for intra-window CEF (FiveM NUI) synchronization.
8
+ *
9
+ * Note:
10
+ * - Must be called within a component initialization context (or wrapped in `$effect.root`).
11
+ * - Mutating nested properties of objects directly (e.g. `state.current.prop = val`) does not trigger a write; use assignments or `.set()`.
12
+ * - If the key changes dynamically, the state switches to the new key, overwriting uncommitted state.
13
+ * - Write failures (e.g., storage quota exceeded) keep the memory state updated while leaving storage unchanged.
14
+ */
15
+ export function useLocalStorage(key, initialValue) {
16
+ // Helper to resolve reactive or static key
17
+ const resolveKey = () => (typeof key === 'function' ? key() : key);
18
+ function cloneInitial() {
19
+ if (initialValue === undefined)
20
+ return initialValue;
21
+ try {
22
+ if (typeof structuredClone === 'function') {
23
+ return structuredClone(initialValue);
24
+ }
25
+ return JSON.parse(JSON.stringify(initialValue));
26
+ }
27
+ catch {
28
+ return initialValue;
29
+ }
30
+ }
31
+ function readStorage(targetKey) {
32
+ try {
33
+ const item = window.localStorage.getItem(targetKey);
34
+ if (item !== null) {
35
+ return JSON.parse(item);
36
+ }
37
+ }
38
+ catch (error) {
39
+ console.warn(`[nui-local-storage] Error reading key "${targetKey}":`, error);
40
+ }
41
+ return cloneInitial();
42
+ }
43
+ // Synchronous initial read to prevent flash of default value on first render
44
+ let stored = $state(readStorage(resolveKey()));
45
+ let loadedKey = resolveKey();
46
+ const instanceId = Symbol();
47
+ function setValue(value) {
48
+ const currentKey = resolveKey();
49
+ const valueToStore = typeof value === 'function' ? value(stored) : value;
50
+ stored = valueToStore;
51
+ try {
52
+ window.localStorage.setItem(currentKey, JSON.stringify(valueToStore));
53
+ }
54
+ catch (error) {
55
+ console.warn(`[nui-local-storage] Error writing key "${currentKey}":`, error);
56
+ return; // Memory state is updated, but storage write failed
57
+ }
58
+ // Dispatch custom event for intra-window CEF synchronization (critical for FiveM NUI) only after successful write
59
+ try {
60
+ window.dispatchEvent(new CustomEvent(`${STORAGE_EVENT_PREFIX}${currentKey}`, {
61
+ detail: { source: instanceId }
62
+ }));
63
+ }
64
+ catch (error) {
65
+ console.warn(`[nui-local-storage] Error dispatching sync event for key "${currentKey}":`, error);
66
+ }
67
+ }
68
+ const setupEffect = () => {
69
+ const currentKey = resolveKey();
70
+ // Sync state only if the key has actually changed (prevents double parsing on initial mount)
71
+ if (currentKey !== loadedKey) {
72
+ loadedKey = currentKey;
73
+ stored = readStorage(currentKey);
74
+ }
75
+ const controller = new AbortController();
76
+ const { signal } = controller;
77
+ // 1. Cross-window storage events (handles external tabs, clear(), removeItem())
78
+ window.addEventListener('storage', (event) => {
79
+ if (event.key === currentKey || event.key === null) {
80
+ stored = readStorage(currentKey);
81
+ }
82
+ }, { signal });
83
+ // 2. Intra-window custom events (FiveM NUI CEF synchronization with fresh storage reads)
84
+ window.addEventListener(`${STORAGE_EVENT_PREFIX}${currentKey}`, (event) => {
85
+ const customEvent = event;
86
+ if (customEvent.detail.source !== instanceId) {
87
+ stored = readStorage(currentKey);
88
+ }
89
+ }, { signal });
90
+ return () => {
91
+ controller.abort();
92
+ };
93
+ };
94
+ // Reactive lifecycle management for dynamic keys
95
+ $effect(setupEffect);
96
+ return {
97
+ get current() {
98
+ return stored;
99
+ },
100
+ set current(val) {
101
+ setValue(val);
102
+ },
103
+ set: setValue
104
+ };
105
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Media query hooks for FiveM NUI.
3
+ */
4
+ export interface MediaQueryState {
5
+ readonly matches: boolean;
6
+ }
7
+ export type MediaQuerySource = string | (() => string);
8
+ /**
9
+ * Media query listener.
10
+ *
11
+ * Supports both static query strings and reactive getters (`() => string`).
12
+ * Automatically manages event listener lifecycle via `$effect`.
13
+ *
14
+ * Note: Must be called within a component initialization context (or wrapped in `$effect.root`).
15
+ */
16
+ export declare function useMediaQuery(querySource: MediaQuerySource): MediaQueryState;
17
+ /**
18
+ * Tracks whether the viewport width is strictly less than the specified breakpoint.
19
+ * Uses modern CSS range syntax (fully supported in modern Chromium/FiveM CEF).
20
+ * Accepts any valid CSS length unit (e.g. '768px', '48rem').
21
+ * @default breakpoint '768px'
22
+ */
23
+ export declare function useIsMobile(breakpoint?: string): MediaQueryState;
24
+ /**
25
+ * Tracks whether the viewport width falls within the tablet range using modern CSS range syntax.
26
+ * Accepts any valid CSS length units.
27
+ * @default min '768px', max '1024px'
28
+ */
29
+ export declare function useIsTablet(min?: string, max?: string): MediaQueryState;
30
+ /**
31
+ * Tracks whether the viewport width is greater than or equal to the specified breakpoint.
32
+ * Accepts any valid CSS length units.
33
+ * @default breakpoint '1024px'
34
+ */
35
+ export declare function useIsDesktop(breakpoint?: string): MediaQueryState;
36
+ /**
37
+ * Tracks dark mode preference.
38
+ * Note: In embedded CEF (FiveM NUI), OS-level color scheme detection may not reflect host system settings;
39
+ * consider overriding or syncing this via `useLocalStorage` if your UI supports manual theme switching.
40
+ */
41
+ export declare function useDarkMode(): MediaQueryState;
42
+ /**
43
+ * Tracks reduced motion preference.
44
+ * Note: In embedded CEF (FiveM NUI), OS-level accessibility settings may not propagate from the host system.
45
+ */
46
+ export declare function useReducedMotion(): MediaQueryState;
47
+ /**
48
+ * Tracks high contrast / forced colors mode.
49
+ * Note: In embedded CEF (FiveM NUI), system contrast settings may not propagate from the host system.
50
+ */
51
+ export declare function useHighContrast(): MediaQueryState;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Media query hooks for FiveM NUI.
3
+ */
4
+ /**
5
+ * Media query listener.
6
+ *
7
+ * Supports both static query strings and reactive getters (`() => string`).
8
+ * Automatically manages event listener lifecycle via `$effect`.
9
+ *
10
+ * Note: Must be called within a component initialization context (or wrapped in `$effect.root`).
11
+ */
12
+ export function useMediaQuery(querySource) {
13
+ const resolveQuery = () => (typeof querySource === 'function' ? querySource() : querySource);
14
+ let matches = $state(typeof window !== 'undefined' ? window.matchMedia(resolveQuery()).matches : false);
15
+ $effect(() => {
16
+ const query = resolveQuery();
17
+ const mediaQuery = window.matchMedia(query);
18
+ // Sync matches state in case the query changed
19
+ matches = mediaQuery.matches;
20
+ const handler = (event) => {
21
+ matches = event.matches;
22
+ };
23
+ mediaQuery.addEventListener('change', handler);
24
+ return () => {
25
+ mediaQuery.removeEventListener('change', handler);
26
+ };
27
+ });
28
+ return {
29
+ get matches() {
30
+ return matches;
31
+ }
32
+ };
33
+ }
34
+ /**
35
+ * Tracks whether the viewport width is strictly less than the specified breakpoint.
36
+ * Uses modern CSS range syntax (fully supported in modern Chromium/FiveM CEF).
37
+ * Accepts any valid CSS length unit (e.g. '768px', '48rem').
38
+ * @default breakpoint '768px'
39
+ */
40
+ export function useIsMobile(breakpoint = '768px') {
41
+ return useMediaQuery(`(width < ${breakpoint})`);
42
+ }
43
+ /**
44
+ * Tracks whether the viewport width falls within the tablet range using modern CSS range syntax.
45
+ * Accepts any valid CSS length units.
46
+ * @default min '768px', max '1024px'
47
+ */
48
+ export function useIsTablet(min = '768px', max = '1024px') {
49
+ return useMediaQuery(`(${min} <= width < ${max})`);
50
+ }
51
+ /**
52
+ * Tracks whether the viewport width is greater than or equal to the specified breakpoint.
53
+ * Accepts any valid CSS length units.
54
+ * @default breakpoint '1024px'
55
+ */
56
+ export function useIsDesktop(breakpoint = '1024px') {
57
+ return useMediaQuery(`(width >= ${breakpoint})`);
58
+ }
59
+ /**
60
+ * Tracks dark mode preference.
61
+ * Note: In embedded CEF (FiveM NUI), OS-level color scheme detection may not reflect host system settings;
62
+ * consider overriding or syncing this via `useLocalStorage` if your UI supports manual theme switching.
63
+ */
64
+ export function useDarkMode() {
65
+ return useMediaQuery('(prefers-color-scheme: dark)');
66
+ }
67
+ /**
68
+ * Tracks reduced motion preference.
69
+ * Note: In embedded CEF (FiveM NUI), OS-level accessibility settings may not propagate from the host system.
70
+ */
71
+ export function useReducedMotion() {
72
+ return useMediaQuery('(prefers-reduced-motion: reduce)');
73
+ }
74
+ /**
75
+ * Tracks high contrast / forced colors mode.
76
+ * Note: In embedded CEF (FiveM NUI), system contrast settings may not propagate from the host system.
77
+ */
78
+ export function useHighContrast() {
79
+ return useMediaQuery('(forced-colors: active)');
80
+ }
@@ -0,0 +1,46 @@
1
+ export { default as NuiButton } from './components/actions/NuiButton.svelte';
2
+ export { default as NuiForm } from './components/forms/NuiForm.svelte';
3
+ export { default as NuiInput } from './components/forms/NuiInput.svelte';
4
+ export { default as NuiCheckbox } from './components/forms/NuiCheckbox.svelte';
5
+ export { default as NuiRadioGroup } from './components/forms/NuiRadioGroup.svelte';
6
+ export type { NuiRadioOption } from './components/forms/NuiRadioGroup.svelte';
7
+ export { default as NuiSelect } from './components/forms/NuiSelect.svelte';
8
+ export type { NuiSelectOption } from './components/forms/NuiSelect.svelte';
9
+ export { default as NuiDatepicker } from './components/forms/NuiDatepicker.svelte';
10
+ export { default as NuiFileInput } from './components/forms/NuiFileInput.svelte';
11
+ export type { FileInfo, FileMetadata } from './components/forms/NuiFileInput.types';
12
+ export { default as NuiCard } from './components/layout/NuiCard.svelte';
13
+ export { default as NuiCardGrid } from './components/layout/NuiCardGrid.svelte';
14
+ export { default as NuiFlex } from './components/layout/NuiFlex.svelte';
15
+ export { default as NuiGrid } from './components/layout/NuiGrid.svelte';
16
+ export { default as NuiStack } from './components/layout/NuiStack.svelte';
17
+ export { default as NuiAccordion } from './components/navigation/NuiAccordion.svelte';
18
+ export type { AccordionItem } from './components/navigation/NuiAccordion.svelte';
19
+ export { default as NuiBreadcrumbs } from './components/navigation/NuiBreadcrumbs.svelte';
20
+ export { default as NuiMenu } from './components/navigation/NuiMenu.svelte';
21
+ export { default as NuiPagination } from './components/navigation/NuiPagination.svelte';
22
+ export { default as NuiSidebar } from './components/navigation/NuiSidebar.svelte';
23
+ export { default as NuiTabs } from './components/navigation/NuiTabs.svelte';
24
+ export type { Tab } from './components/navigation/NuiTabs.svelte';
25
+ export { default as NuiDialog } from './components/overlay/NuiDialog.svelte';
26
+ export type { NuiDialogAction } from './components/overlay/NuiDialog.svelte';
27
+ export { default as NuiModal } from './components/overlay/NuiModal.svelte';
28
+ export type { ModalAction } from './components/overlay/NuiModal.svelte';
29
+ export { default as NuiTooltip } from './components/overlay/NuiTooltip.svelte';
30
+ export { default as NuiBadge } from './components/data/NuiBadge.svelte';
31
+ export { default as NuiChart } from './components/data/chart/NuiChart.svelte';
32
+ export type { ChartData, ChartDataset, ChartOptions, ChartType } from './components/data/chart/types';
33
+ export { default as NuiCodeBlock } from './components/data/NuiCodeBlock.svelte';
34
+ export { default as NuiList } from './components/data/NuiList.svelte';
35
+ export type { NuiListItem } from './components/data/NuiList.svelte';
36
+ export { default as NuiProgressBar } from './components/data/NuiProgressBar.svelte';
37
+ export { default as NuiRichText } from './components/data/NuiRichText.svelte';
38
+ export { default as NuiTable } from './components/data/NuiTable.svelte';
39
+ export type { Column as NuiTableColumn } from './components/data/NuiTable.svelte';
40
+ export { validateForm, validateField } from './composables/useFormValidation';
41
+ export type { ValidationRules } from './composables/useFormValidation';
42
+ export { useKeyboardShortcut, useKeyboardShortcuts, type KeyboardShortcut, type KeyboardShortcutOptions, type KeyboardShortcutsSource, type KeyboardShortcutSource } from './composables/useKeyboardShortcut.svelte';
43
+ export { useClickOutside, useClickOutsideElement, clickOutsideAction, type ClickOutsideOptions } from './composables/useClickOutside';
44
+ export { useLocalStorage, type LocalStorageState } from './composables/useLocalStorage.svelte';
45
+ export { useMediaQuery, useIsMobile, useIsTablet, useIsDesktop, useDarkMode, useReducedMotion, useHighContrast, type MediaQueryState } from './composables/useMediaQuery.svelte';
46
+ export { useIdle, useIdleUntil, type IdleState } from './composables/useIdle.svelte';
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ // Actions
2
+ export { default as NuiButton } from './components/actions/NuiButton.svelte';
3
+ // Forms
4
+ export { default as NuiForm } from './components/forms/NuiForm.svelte';
5
+ export { default as NuiInput } from './components/forms/NuiInput.svelte';
6
+ export { default as NuiCheckbox } from './components/forms/NuiCheckbox.svelte';
7
+ export { default as NuiRadioGroup } from './components/forms/NuiRadioGroup.svelte';
8
+ export { default as NuiSelect } from './components/forms/NuiSelect.svelte';
9
+ export { default as NuiDatepicker } from './components/forms/NuiDatepicker.svelte';
10
+ export { default as NuiFileInput } from './components/forms/NuiFileInput.svelte';
11
+ // Layout
12
+ export { default as NuiCard } from './components/layout/NuiCard.svelte';
13
+ export { default as NuiCardGrid } from './components/layout/NuiCardGrid.svelte';
14
+ export { default as NuiFlex } from './components/layout/NuiFlex.svelte';
15
+ export { default as NuiGrid } from './components/layout/NuiGrid.svelte';
16
+ export { default as NuiStack } from './components/layout/NuiStack.svelte';
17
+ // Navigation
18
+ export { default as NuiAccordion } from './components/navigation/NuiAccordion.svelte';
19
+ export { default as NuiBreadcrumbs } from './components/navigation/NuiBreadcrumbs.svelte';
20
+ export { default as NuiMenu } from './components/navigation/NuiMenu.svelte';
21
+ export { default as NuiPagination } from './components/navigation/NuiPagination.svelte';
22
+ export { default as NuiSidebar } from './components/navigation/NuiSidebar.svelte';
23
+ export { default as NuiTabs } from './components/navigation/NuiTabs.svelte';
24
+ // Overlay
25
+ export { default as NuiDialog } from './components/overlay/NuiDialog.svelte';
26
+ export { default as NuiModal } from './components/overlay/NuiModal.svelte';
27
+ export { default as NuiTooltip } from './components/overlay/NuiTooltip.svelte';
28
+ // Data display
29
+ export { default as NuiBadge } from './components/data/NuiBadge.svelte';
30
+ export { default as NuiChart } from './components/data/chart/NuiChart.svelte';
31
+ export { default as NuiCodeBlock } from './components/data/NuiCodeBlock.svelte';
32
+ export { default as NuiList } from './components/data/NuiList.svelte';
33
+ export { default as NuiProgressBar } from './components/data/NuiProgressBar.svelte';
34
+ export { default as NuiRichText } from './components/data/NuiRichText.svelte';
35
+ export { default as NuiTable } from './components/data/NuiTable.svelte';
36
+ // Composables
37
+ export { validateForm, validateField } from './composables/useFormValidation';
38
+ export { useKeyboardShortcut, useKeyboardShortcuts } from './composables/useKeyboardShortcut.svelte';
39
+ export { useClickOutside, useClickOutsideElement, clickOutsideAction } from './composables/useClickOutside';
40
+ export { useLocalStorage } from './composables/useLocalStorage.svelte';
41
+ export { useMediaQuery, useIsMobile, useIsTablet, useIsDesktop, useDarkMode, useReducedMotion, useHighContrast } from './composables/useMediaQuery.svelte';
42
+ export { useIdle, useIdleUntil } from './composables/useIdle.svelte';