@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,260 @@
1
+ <!--
2
+ @component NuiRichText
3
+ Renders formatted text or sanitized HTML content.
4
+
5
+ Security Policy & Sanitization:
6
+ - Default Mode (`parseHtml = false`):
7
+ Safe by default. Content is rendered via Svelte's native text binding inside `<pre>`,
8
+ preventing any HTML or script execution.
9
+ - HTML Mode (`parseHtml = true`):
10
+ Enables HTML parsing. Applies an allowlist of safe formatting tags/attributes, protocol
11
+ filtering on links (http, https, mailto, safe relative URLs), unwraps benign wrapper tags,
12
+ and drops dangerous elements (script, style, iframe, etc.).
13
+ - Custom Sanitizer:
14
+ For untrusted or user-submitted content (e.g. player chat, server announcements, external API data),
15
+ pass an enterprise-grade sanitizer (e.g. `DOMPurify.sanitize`) via the `sanitizer` prop.
16
+ The provided sanitizer function must return an XSS-safe HTML string.
17
+ -->
18
+ <script lang="ts">
19
+ interface Props {
20
+ /** Text or HTML content to display. */
21
+ content: string;
22
+ /** Whether to parse and render content as HTML. Defaults to false. */
23
+ parseHtml?: boolean;
24
+ /**
25
+ * Optional custom sanitizer function (e.g. DOMPurify.sanitize).
26
+ * Must return an XSS-safe HTML string.
27
+ */
28
+ sanitizer?: ((dirty: string) => string) | null;
29
+ /** Optional additional CSS class for root container. */
30
+ className?: string;
31
+ }
32
+
33
+ let {
34
+ content,
35
+ parseHtml = false,
36
+ sanitizer = null,
37
+ className = ''
38
+ }: Props = $props();
39
+
40
+ const ALLOWED_TAGS = new Set([
41
+ 'a', 'b', 'blockquote', 'br', 'code', 'dd', 'del', 'div', 'dl', 'dt',
42
+ 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd',
43
+ 'li', 'mark', 'ol', 'p', 'pre', 's', 'small', 'span', 'strike',
44
+ 'strong', 'sub', 'sup', 'time', 'u', 'ul'
45
+ ]);
46
+
47
+ const DROP_CONTENT_TAGS = new Set([
48
+ 'script', 'style', 'iframe', 'noembed', 'noframes', 'noscript',
49
+ 'object', 'embed', 'template', 'svg', 'canvas', 'audio', 'video',
50
+ 'input', 'textarea', 'select', 'button', 'form'
51
+ ]);
52
+
53
+ const ALLOWED_ATTRS: Record<string, Set<string>> = {
54
+ a: new Set(['href', 'title', 'target', 'rel']),
55
+ time: new Set(['datetime']),
56
+ '*': new Set(['class', 'title', 'aria-label', 'aria-hidden'])
57
+ };
58
+
59
+ // Strict ISO-8601 date/time (e.g. 2026-10-17, 2026-10-17T14:30:00Z)
60
+ const ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
61
+
62
+ function escapeHtml(str: string): string {
63
+ return str
64
+ .replace(/&/g, '&amp;')
65
+ .replace(/</g, '&lt;')
66
+ .replace(/>/g, '&gt;')
67
+ .replace(/"/g, '&quot;')
68
+ .replace(/'/g, '&#039;');
69
+ }
70
+
71
+ /**
72
+ * Validates whether a URL uses a safe protocol (http, https, mailto, or relative).
73
+ * Rejects control characters, protocol-relative URLs (//...), and dangerous schemes.
74
+ */
75
+ function isSafeUrl(urlStr: string): boolean {
76
+ const value = urlStr.trim();
77
+
78
+ // Reject control characters rather than trying to normalize them
79
+ if (/[\u0000-\u001F\u007F-\u009F]/.test(value)) {
80
+ return false;
81
+ }
82
+
83
+ // Allow ordinary relative URLs, but reject protocol-relative URLs (//...)
84
+ if (value.startsWith('/') && !value.startsWith('//')) {
85
+ return true;
86
+ }
87
+
88
+ // Allow anchors and query parameters
89
+ if (value.startsWith('#') || value.startsWith('?')) {
90
+ return true;
91
+ }
92
+
93
+ try {
94
+ const parsed = new URL(value);
95
+ return (
96
+ parsed.protocol === 'https:' ||
97
+ parsed.protocol === 'http:' ||
98
+ parsed.protocol === 'mailto:'
99
+ );
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ function cleanNode(node: Node): void {
106
+ const children = Array.from(node.childNodes);
107
+
108
+ for (const child of children) {
109
+ if (child.nodeType === Node.ELEMENT_NODE) {
110
+ const el = child as HTMLElement;
111
+ const tagName = el.tagName.toLowerCase();
112
+
113
+ // Completely discard dangerous elements and their subtrees
114
+ if (DROP_CONTENT_TAGS.has(tagName)) {
115
+ node.removeChild(child);
116
+ continue;
117
+ }
118
+
119
+ // Unwrap benign wrapper elements not in allowlist, preserving their sanitized text/children
120
+ if (!ALLOWED_TAGS.has(tagName)) {
121
+ cleanNode(child);
122
+ while (child.firstChild) {
123
+ node.insertBefore(child.firstChild, child);
124
+ }
125
+ node.removeChild(child);
126
+ continue;
127
+ }
128
+
129
+ const attrs = Array.from(el.attributes);
130
+ for (const attr of attrs) {
131
+ const attrName = attr.name.toLowerCase();
132
+ const allowedForTag = ALLOWED_ATTRS[tagName];
133
+ const allowedGlobal = ALLOWED_ATTRS['*'];
134
+ const isAllowed =
135
+ (allowedForTag && allowedForTag.has(attrName)) ||
136
+ (allowedGlobal && allowedGlobal.has(attrName));
137
+
138
+ if (!isAllowed || attrName.startsWith('on')) {
139
+ el.removeAttribute(attr.name);
140
+ continue;
141
+ }
142
+
143
+ if (attrName === 'href' && !isSafeUrl(attr.value)) {
144
+ el.removeAttribute(attr.name);
145
+ continue;
146
+ }
147
+
148
+ if (attrName === 'datetime' && !ISO_DATETIME_REGEX.test(attr.value.trim())) {
149
+ el.removeAttribute(attr.name);
150
+ continue;
151
+ }
152
+ }
153
+
154
+ if (tagName === 'a') {
155
+ el.setAttribute('rel', 'noopener noreferrer');
156
+ }
157
+
158
+ cleanNode(child);
159
+ } else if (
160
+ child.nodeType !== Node.TEXT_NODE &&
161
+ child.nodeType !== Node.COMMENT_NODE
162
+ ) {
163
+ node.removeChild(child);
164
+ }
165
+ }
166
+ }
167
+
168
+ function defaultSanitize(dirtyHtml: string): string {
169
+ if (typeof window === 'undefined' || typeof DOMParser === 'undefined') {
170
+ return escapeHtml(dirtyHtml);
171
+ }
172
+ try {
173
+ const parser = new DOMParser();
174
+ const doc = parser.parseFromString(dirtyHtml, 'text/html');
175
+ cleanNode(doc.body);
176
+ return doc.body.innerHTML;
177
+ } catch {
178
+ return escapeHtml(dirtyHtml);
179
+ }
180
+ }
181
+
182
+ const safeContent = $derived.by(() => {
183
+ if (!content) return '';
184
+ if (!parseHtml) return content;
185
+
186
+ try {
187
+ return sanitizer ? sanitizer(content) : defaultSanitize(content);
188
+ } catch (error) {
189
+ console.error('[fivem-nui-components] Failed to sanitize rich text:', error);
190
+ return escapeHtml(content);
191
+ }
192
+ });
193
+ </script>
194
+
195
+ <div
196
+ class="nui-rich-text {className}"
197
+ class:nui-rich-text--html={parseHtml}
198
+ >
199
+ {#if parseHtml}
200
+ {@html safeContent}
201
+ {:else}
202
+ <pre class="nui-rich-text__pre">{content}</pre>
203
+ {/if}
204
+ </div>
205
+
206
+ <style>
207
+ .nui-rich-text {
208
+ font-family: inherit;
209
+ line-height: 1.6;
210
+ color: var(--nui-fg, #fafafa);
211
+ }
212
+
213
+ .nui-rich-text__pre {
214
+ background: var(--nui-surface-alt, #101012);
215
+ border: 1px solid var(--nui-border, #27272a);
216
+ border-radius: var(--nui-radius, 0.375rem);
217
+ padding: 1rem;
218
+ overflow-x: auto;
219
+ font-family: 'Consolas', 'Monaco', monospace;
220
+ font-size: 0.875rem;
221
+ margin: 0;
222
+ white-space: pre-wrap;
223
+ word-break: break-word;
224
+ }
225
+
226
+ .nui-rich-text :global(pre),
227
+ .nui-rich-text :global(code) {
228
+ font-family: 'Consolas', 'Monaco', monospace;
229
+ font-size: 0.875rem;
230
+ }
231
+
232
+ .nui-rich-text :global(p) {
233
+ margin: 0 0 1rem 0;
234
+ }
235
+
236
+ .nui-rich-text :global(ul),
237
+ .nui-rich-text :global(ol) {
238
+ margin: 0 0 1rem 0;
239
+ padding-left: 1.5rem;
240
+ }
241
+
242
+ .nui-rich-text :global(li) {
243
+ margin: 0 0 0.25rem 0;
244
+ }
245
+
246
+ .nui-rich-text :global(h1),
247
+ .nui-rich-text :global(h2),
248
+ .nui-rich-text :global(h3),
249
+ .nui-rich-text :global(h4),
250
+ .nui-rich-text :global(h5),
251
+ .nui-rich-text :global(h6) {
252
+ margin: 1rem 0 0.5rem 0;
253
+ font-weight: 600;
254
+ }
255
+
256
+ .nui-rich-text :global(h1) { font-size: 1.5rem; }
257
+ .nui-rich-text :global(h2) { font-size: 1.25rem; }
258
+ .nui-rich-text :global(h3) { font-size: 1.125rem; }
259
+ .nui-rich-text :global(h4) { font-size: 1rem; }
260
+ </style>
@@ -0,0 +1,33 @@
1
+ interface Props {
2
+ /** Text or HTML content to display. */
3
+ content: string;
4
+ /** Whether to parse and render content as HTML. Defaults to false. */
5
+ parseHtml?: boolean;
6
+ /**
7
+ * Optional custom sanitizer function (e.g. DOMPurify.sanitize).
8
+ * Must return an XSS-safe HTML string.
9
+ */
10
+ sanitizer?: ((dirty: string) => string) | null;
11
+ /** Optional additional CSS class for root container. */
12
+ className?: string;
13
+ }
14
+ /**
15
+ * NuiRichText
16
+ * Renders formatted text or sanitized HTML content.
17
+ *
18
+ * Security Policy & Sanitization:
19
+ * - Default Mode (`parseHtml = false`):
20
+ * Safe by default. Content is rendered via Svelte's native text binding inside `<pre>`,
21
+ * preventing any HTML or script execution.
22
+ * - HTML Mode (`parseHtml = true`):
23
+ * Enables HTML parsing. Applies an allowlist of safe formatting tags/attributes, protocol
24
+ * filtering on links (http, https, mailto, safe relative URLs), unwraps benign wrapper tags,
25
+ * and drops dangerous elements (script, style, iframe, etc.).
26
+ * - Custom Sanitizer:
27
+ * For untrusted or user-submitted content (e.g. player chat, server announcements, external API data),
28
+ * pass an enterprise-grade sanitizer (e.g. `DOMPurify.sanitize`) via the `sanitizer` prop.
29
+ * The provided sanitizer function must return an XSS-safe HTML string.
30
+ */
31
+ declare const NuiRichText: import("svelte").Component<Props, {}, "">;
32
+ type NuiRichText = ReturnType<typeof NuiRichText>;
33
+ export default NuiRichText;