@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,384 @@
1
+ <!--
2
+ @component NuiChart
3
+ Canvas chart component (bar, line, pie, doughnut) for FiveM NUI.
4
+ -->
5
+ <script module lang="ts">
6
+ export type {
7
+ ChartData,
8
+ ChartDataset,
9
+ ChartOptions,
10
+ ChartType,
11
+ } from "./types";
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ import { onMount } from "svelte";
16
+ import { nui } from "@fivem-nui/svelte";
17
+ import type { SvelteNuiClient } from "@fivem-nui/svelte";
18
+ import type {
19
+ ChartData,
20
+ ChartDataset,
21
+ ChartOptions,
22
+ ChartType,
23
+ } from "./types";
24
+ import { getColor } from "./color";
25
+ import { validateChartData } from "./validator";
26
+ import { renderChart } from "./registry";
27
+
28
+ /**
29
+ * Props for {@link NuiChart} component.
30
+ */
31
+ interface Props {
32
+ /**
33
+ * Chart type determining the rendering strategy.
34
+ * Supported built-in types: `'bar'`, `'line'`, `'pie'`, `'doughnut'` or custom types.
35
+ */
36
+ type: ChartType;
37
+
38
+ /**
39
+ * Source chart data passed from parent.
40
+ */
41
+ data: ChartData;
42
+
43
+ /**
44
+ * Container width in pixels or CSS units.
45
+ * @defaultValue '100%'
46
+ */
47
+ width?: number | string;
48
+
49
+ /**
50
+ * Canvas height in pixels or CSS units.
51
+ * @defaultValue '300px'
52
+ */
53
+ height?: number | string;
54
+
55
+ /**
56
+ * Optional configuration for grid, Y-scale, legend position, and accessibility.
57
+ * @defaultValue {}
58
+ */
59
+ options?: ChartOptions;
60
+
61
+ /**
62
+ * Optional NUI event name (from game / C#).
63
+ * When provided, automatically listens for chart updates from NUI.
64
+ */
65
+ eventName?: string;
66
+
67
+ /**
68
+ * SvelteNuiClient instance to listen for game events.
69
+ * @defaultValue singleton `nui` from `@fivem-nui/svelte`
70
+ */
71
+ client?: SvelteNuiClient;
72
+ }
73
+
74
+ let {
75
+ type,
76
+ data,
77
+ width = "100%",
78
+ height = "300px",
79
+ options = {},
80
+ eventName,
81
+ client = nui,
82
+ }: Props = $props();
83
+
84
+ let canvasEl = $state<HTMLCanvasElement | null>(null);
85
+ let containerEl = $state<HTMLDivElement | null>(null);
86
+ let rafId: number | null = null;
87
+
88
+ // Data sources: separation of parent props, NUI events, and local visibility state
89
+ let remoteData = $state<ChartData | null>(null);
90
+ let hiddenIndices = $state<Record<number, boolean>>({});
91
+ let lastPropsData: ChartData | null = null;
92
+
93
+ // New parent props reset remote overrides and local hidden states
94
+ $effect(() => {
95
+ if (data !== lastPropsData) {
96
+ lastPropsData = data;
97
+ remoteData = null;
98
+ hiddenIndices = {};
99
+ }
100
+ });
101
+
102
+ // Listen for NUI updates with runtime validation
103
+ $effect(() => {
104
+ if (!eventName || !client) return;
105
+ const unsub = client.on(eventName, (payload: unknown) => {
106
+ const validation = validateChartData(payload);
107
+ if (validation.valid && validation.data) {
108
+ remoteData = validation.data;
109
+ hiddenIndices = {};
110
+ } else {
111
+ console.warn(
112
+ `[NuiChart] Rejected invalid payload from event '${eventName}':`,
113
+ validation.error,
114
+ payload,
115
+ );
116
+ }
117
+ });
118
+ return () => unsub();
119
+ });
120
+
121
+ // Effective data combining remote overrides and local visibility toggles
122
+ const activeRawData = $derived(remoteData ?? data);
123
+ const currentData = $derived<ChartData>({
124
+ labels: activeRawData?.labels ?? [],
125
+ datasets: (activeRawData?.datasets ?? []).map((ds, idx) => ({
126
+ ...ds,
127
+ hidden: hiddenIndices[idx] ?? ds.hidden ?? false,
128
+ })),
129
+ });
130
+
131
+ // Common pie dataset used in renderer and legend
132
+ const activePieDataset = $derived(
133
+ currentData.datasets.find((d) => !d.hidden) ?? currentData.datasets[0],
134
+ );
135
+
136
+ /**
137
+ * Schedules a render on the next animation frame.
138
+ */
139
+ function scheduleRender() {
140
+ if (typeof window === "undefined") return;
141
+ if (rafId !== null) cancelAnimationFrame(rafId);
142
+ rafId = requestAnimationFrame(() => {
143
+ rafId = null;
144
+ if (!canvasEl) return;
145
+ const rect = canvasEl.getBoundingClientRect();
146
+ const w = rect.width || (typeof width === "number" ? width : 400);
147
+ const h = rect.height || (typeof height === "number" ? height : 300);
148
+ renderChart(canvasEl, w, h, type, currentData, options);
149
+ });
150
+ }
151
+
152
+ $effect(() => {
153
+ void type;
154
+ void currentData;
155
+ void options;
156
+ void width;
157
+ void height;
158
+ scheduleRender();
159
+ });
160
+
161
+ onMount(() => {
162
+ scheduleRender();
163
+
164
+ if (containerEl && typeof ResizeObserver !== "undefined") {
165
+ const ro = new ResizeObserver((entries) => {
166
+ for (const entry of entries) {
167
+ if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
168
+ scheduleRender();
169
+ }
170
+ }
171
+ });
172
+ ro.observe(containerEl);
173
+ return () => {
174
+ ro.disconnect();
175
+ if (rafId !== null) cancelAnimationFrame(rafId);
176
+ };
177
+ }
178
+
179
+ return () => {
180
+ if (rafId !== null) cancelAnimationFrame(rafId);
181
+ };
182
+ });
183
+
184
+ const legendPos = $derived(
185
+ options.legend?.position ?? options.plugins?.legend?.position ?? "bottom",
186
+ );
187
+ const showLegend = $derived(legendPos !== "none");
188
+
189
+ /** Accessible description for screen readers. */
190
+ const resolvedAriaLabel = $derived.by(() => {
191
+ if (options.ariaLabel) return options.ariaLabel;
192
+ const labels = currentData.labels || [];
193
+ const labelsSummary =
194
+ labels.length > 5
195
+ ? `${labels.slice(0, 5).join(", ")} and ${labels.length - 5} more`
196
+ : labels.join(", ");
197
+ return `Chart of type ${type}.${labels.length ? ` Categories: ${labelsSummary}` : ""}`;
198
+ });
199
+
200
+ /**
201
+ * Toggles dataset visibility locally without mutating source data.
202
+ */
203
+ function toggleDataset(index: number) {
204
+ const isCurrentlyHidden = currentData.datasets[index]?.hidden ?? false;
205
+ hiddenIndices[index] = !isCurrentlyHidden;
206
+ }
207
+ </script>
208
+
209
+ <div
210
+ class="nui-chart-container"
211
+ bind:this={containerEl}
212
+ style="width: {typeof width === 'number' ? `${width}px` : width};"
213
+ >
214
+ {#if showLegend && legendPos === "top"}
215
+ <div class="nui-chart__legend nui-chart__legend--top">
216
+ {#if type === "pie" || type === "doughnut"}
217
+ {#if activePieDataset && !activePieDataset.hidden}
218
+ {#each currentData.labels || [] as label, idx}
219
+ {#if (activePieDataset.data[idx] ?? 0) > 0}
220
+ <div class="nui-chart__legend-item">
221
+ <span
222
+ class="nui-chart__legend-color"
223
+ style="background-color: {getColor(
224
+ activePieDataset.backgroundColor,
225
+ idx,
226
+ )}"
227
+ ></span>
228
+ <span class="nui-chart__legend-label">{label}</span>
229
+ </div>
230
+ {/if}
231
+ {/each}
232
+ {/if}
233
+ {:else}
234
+ {#each currentData.datasets as ds, idx}
235
+ <button
236
+ type="button"
237
+ class="nui-chart__legend-btn"
238
+ class:nui-chart__legend-btn--hidden={ds.hidden}
239
+ aria-pressed={!ds.hidden}
240
+ aria-label="{ds.hidden ? 'Show' : 'Hide'} series: {ds.label}"
241
+ onclick={() => toggleDataset(idx)}
242
+ >
243
+ <span
244
+ class="nui-chart__legend-color"
245
+ style="background-color: {getColor(
246
+ ds.borderColor || ds.backgroundColor,
247
+ idx,
248
+ )}"
249
+ ></span>
250
+ <span class="nui-chart__legend-label">{ds.label}</span>
251
+ </button>
252
+ {/each}
253
+ {/if}
254
+ </div>
255
+ {/if}
256
+
257
+ <canvas
258
+ bind:this={canvasEl}
259
+ class="nui-chart"
260
+ role="img"
261
+ aria-label={resolvedAriaLabel}
262
+ style="width: 100%; height: {typeof height === 'number'
263
+ ? `${height}px`
264
+ : height};"
265
+ >
266
+ <p>{resolvedAriaLabel}</p>
267
+ </canvas>
268
+
269
+ {#if showLegend && legendPos !== "top"}
270
+ <div class="nui-chart__legend nui-chart__legend--bottom">
271
+ {#if type === "pie" || type === "doughnut"}
272
+ {#if activePieDataset && !activePieDataset.hidden}
273
+ {#each currentData.labels || [] as label, idx}
274
+ {#if (activePieDataset.data[idx] ?? 0) > 0}
275
+ <div class="nui-chart__legend-item">
276
+ <span
277
+ class="nui-chart__legend-color"
278
+ style="background-color: {getColor(
279
+ activePieDataset.backgroundColor,
280
+ idx,
281
+ )}"
282
+ ></span>
283
+ <span class="nui-chart__legend-label">{label}</span>
284
+ </div>
285
+ {/if}
286
+ {/each}
287
+ {/if}
288
+ {:else}
289
+ {#each currentData.datasets as ds, idx}
290
+ <button
291
+ type="button"
292
+ class="nui-chart__legend-btn"
293
+ class:nui-chart__legend-btn--hidden={ds.hidden}
294
+ aria-pressed={!ds.hidden}
295
+ aria-label="{ds.hidden ? 'Show' : 'Hide'} series: {ds.label}"
296
+ onclick={() => toggleDataset(idx)}
297
+ >
298
+ <span
299
+ class="nui-chart__legend-color"
300
+ style="background-color: {getColor(
301
+ ds.borderColor || ds.backgroundColor,
302
+ idx,
303
+ )}"
304
+ ></span>
305
+ <span class="nui-chart__legend-label">{ds.label}</span>
306
+ </button>
307
+ {/each}
308
+ {/if}
309
+ </div>
310
+ {/if}
311
+ </div>
312
+
313
+ <style>
314
+ .nui-chart-container {
315
+ position: relative;
316
+ display: flex;
317
+ flex-direction: column;
318
+ gap: 0.75rem;
319
+ font-family: inherit;
320
+ box-sizing: border-box;
321
+ }
322
+
323
+ .nui-chart {
324
+ display: block;
325
+ border-radius: var(--nui-radius, 0.375rem);
326
+ box-sizing: border-box;
327
+ }
328
+
329
+ .nui-chart__legend {
330
+ display: flex;
331
+ flex-wrap: wrap;
332
+ align-items: center;
333
+ justify-content: center;
334
+ gap: 1rem;
335
+ padding: 0.25rem 0;
336
+ }
337
+
338
+ .nui-chart__legend-item,
339
+ .nui-chart__legend-btn {
340
+ display: inline-flex;
341
+ align-items: center;
342
+ gap: 0.375rem;
343
+ font-size: 0.75rem;
344
+ color: var(--nui-muted, #a1a1aa);
345
+ line-height: 1;
346
+ }
347
+
348
+ .nui-chart__legend-btn {
349
+ background: transparent;
350
+ border: none;
351
+ padding: 0.125rem 0.25rem;
352
+ border-radius: 0.25rem;
353
+ cursor: pointer;
354
+ font: inherit;
355
+ transition:
356
+ opacity 0.15s ease,
357
+ text-decoration 0.15s ease;
358
+ }
359
+
360
+ .nui-chart__legend-btn:hover {
361
+ opacity: 0.8;
362
+ }
363
+
364
+ .nui-chart__legend-btn:focus-visible {
365
+ outline: 2px solid var(--nui-primary, #6366f1);
366
+ outline-offset: 2px;
367
+ }
368
+
369
+ .nui-chart__legend-btn--hidden {
370
+ opacity: 0.4;
371
+ text-decoration: line-through;
372
+ }
373
+
374
+ .nui-chart__legend-color {
375
+ width: 0.75rem;
376
+ height: 0.75rem;
377
+ border-radius: 0.125rem;
378
+ flex-shrink: 0;
379
+ }
380
+
381
+ .nui-chart__legend-label {
382
+ line-height: 1;
383
+ }
384
+ </style>
@@ -0,0 +1,49 @@
1
+ export type { ChartData, ChartDataset, ChartOptions, ChartType, } from "./types";
2
+ import type { SvelteNuiClient } from "@fivem-nui/svelte";
3
+ import type { ChartData, ChartOptions, ChartType } from "./types";
4
+ /**
5
+ * Props for {@link NuiChart} component.
6
+ */
7
+ interface Props {
8
+ /**
9
+ * Chart type determining the rendering strategy.
10
+ * Supported built-in types: `'bar'`, `'line'`, `'pie'`, `'doughnut'` or custom types.
11
+ */
12
+ type: ChartType;
13
+ /**
14
+ * Source chart data passed from parent.
15
+ */
16
+ data: ChartData;
17
+ /**
18
+ * Container width in pixels or CSS units.
19
+ * @defaultValue '100%'
20
+ */
21
+ width?: number | string;
22
+ /**
23
+ * Canvas height in pixels or CSS units.
24
+ * @defaultValue '300px'
25
+ */
26
+ height?: number | string;
27
+ /**
28
+ * Optional configuration for grid, Y-scale, legend position, and accessibility.
29
+ * @defaultValue {}
30
+ */
31
+ options?: ChartOptions;
32
+ /**
33
+ * Optional NUI event name (from game / C#).
34
+ * When provided, automatically listens for chart updates from NUI.
35
+ */
36
+ eventName?: string;
37
+ /**
38
+ * SvelteNuiClient instance to listen for game events.
39
+ * @defaultValue singleton `nui` from `@fivem-nui/svelte`
40
+ */
41
+ client?: SvelteNuiClient;
42
+ }
43
+ /**
44
+ * NuiChart
45
+ * Canvas chart component (bar, line, pie, doughnut) for FiveM NUI.
46
+ */
47
+ declare const NuiChart: import("svelte").Component<Props, {}, "">;
48
+ type NuiChart = ReturnType<typeof NuiChart>;
49
+ export default NuiChart;
@@ -0,0 +1,9 @@
1
+ export declare const DEFAULT_COLORS: string[];
2
+ /**
3
+ * Returns valid color string from dataset property or fallback palette.
4
+ */
5
+ export declare function getColor(item: string | string[] | undefined, index: number): string;
6
+ /**
7
+ * Converts color (hex, rgb, hsl, named) to rgba(r, g, b, alpha).
8
+ */
9
+ export declare function colorToRgba(color: string, alpha: number): string;
@@ -0,0 +1,78 @@
1
+ export const DEFAULT_COLORS = [
2
+ '#6366f1', // indigo
3
+ '#06b6d4', // cyan
4
+ '#10b981', // emerald
5
+ '#f59e0b', // amber
6
+ '#ef4444', // red
7
+ '#8b5cf6', // purple
8
+ '#ec4899', // pink
9
+ ];
10
+ let helperCtx = null;
11
+ function getHelperCtx() {
12
+ if (typeof document === 'undefined')
13
+ return null;
14
+ if (!helperCtx) {
15
+ const canvas = document.createElement('canvas');
16
+ canvas.width = 1;
17
+ canvas.height = 1;
18
+ helperCtx = canvas.getContext('2d', { willReadFrequently: true });
19
+ }
20
+ return helperCtx;
21
+ }
22
+ /**
23
+ * Returns valid color string from dataset property or fallback palette.
24
+ */
25
+ export function getColor(item, index) {
26
+ const safeIdx = Number.isFinite(index) ? Math.floor(Math.abs(index)) : 0;
27
+ if (Array.isArray(item) && item.length > 0) {
28
+ const color = item[safeIdx % item.length];
29
+ if (typeof color === 'string' && color.trim()) {
30
+ return color.trim();
31
+ }
32
+ }
33
+ if (typeof item === 'string' && item.trim()) {
34
+ return item.trim();
35
+ }
36
+ return DEFAULT_COLORS[safeIdx % DEFAULT_COLORS.length];
37
+ }
38
+ /**
39
+ * Converts color (hex, rgb, hsl, named) to rgba(r, g, b, alpha).
40
+ */
41
+ export function colorToRgba(color, alpha) {
42
+ const a = Math.max(0, Math.min(1, Number.isFinite(alpha) ? alpha : 1));
43
+ if (!color || typeof color !== 'string')
44
+ return `rgba(99, 102, 241, ${a})`;
45
+ const trimmed = color.trim();
46
+ // Fast path for hex formats: #RGB or #RRGGBB
47
+ if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(trimmed)) {
48
+ let hex = trimmed.slice(1);
49
+ if (hex.length === 3) {
50
+ hex = hex.split('').map((c) => c + c).join('');
51
+ }
52
+ const r = parseInt(hex.slice(0, 2), 16);
53
+ const g = parseInt(hex.slice(2, 4), 16);
54
+ const b = parseInt(hex.slice(4, 6), 16);
55
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
56
+ }
57
+ // Fast path for rgb(...) or rgba(...)
58
+ const rgbMatch = trimmed.match(/^rgba?\(\s*(\d{1,3})\s*[, ]\s*(\d{1,3})\s*[, ]\s*(\d{1,3})/i);
59
+ if (rgbMatch) {
60
+ return `rgba(${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}, ${a})`;
61
+ }
62
+ // Canvas parser for named colors, hsl, etc.
63
+ const ctx = getHelperCtx();
64
+ if (ctx) {
65
+ try {
66
+ ctx.clearRect(0, 0, 1, 1);
67
+ ctx.fillStyle = '#000000';
68
+ ctx.fillStyle = trimmed;
69
+ ctx.fillRect(0, 0, 1, 1);
70
+ const pixel = ctx.getImageData(0, 0, 1, 1).data;
71
+ return `rgba(${pixel[0]}, ${pixel[1]}, ${pixel[2]}, ${a})`;
72
+ }
73
+ catch {
74
+ // Fallback on environment error
75
+ }
76
+ }
77
+ return `rgba(99, 102, 241, ${a})`;
78
+ }
@@ -0,0 +1,10 @@
1
+ export { default as NuiChart } from './NuiChart.svelte';
2
+ export * from './types';
3
+ export * from './color';
4
+ export * from './validator';
5
+ export * from './scale';
6
+ export * from './registry';
7
+ export * from './renderers/base';
8
+ export * from './renderers/bar';
9
+ export * from './renderers/line';
10
+ export * from './renderers/pie';
@@ -0,0 +1,10 @@
1
+ export { default as NuiChart } from './NuiChart.svelte';
2
+ export * from './types';
3
+ export * from './color';
4
+ export * from './validator';
5
+ export * from './scale';
6
+ export * from './registry';
7
+ export * from './renderers/base';
8
+ export * from './renderers/bar';
9
+ export * from './renderers/line';
10
+ export * from './renderers/pie';
@@ -0,0 +1,14 @@
1
+ import type { ChartData, ChartOptions, ChartType } from './types';
2
+ import type { ChartRenderer } from './renderers/base';
3
+ /**
4
+ * Registers or overrides a chart renderer.
5
+ */
6
+ export declare function registerChartRenderer(type: string, renderer: ChartRenderer): void;
7
+ /**
8
+ * Returns the registered renderer for given chart type.
9
+ */
10
+ export declare function getChartRenderer(type: ChartType | string): ChartRenderer | undefined;
11
+ /**
12
+ * Renders chart on the given canvas element with hidpi scaling.
13
+ */
14
+ export declare function renderChart(canvasEl: HTMLCanvasElement, w: number, h: number, type: ChartType | string, currentData: ChartData | undefined, options?: ChartOptions): void;
@@ -0,0 +1,53 @@
1
+ import { drawMessage } from './renderers/base';
2
+ import { BarChartRenderer } from './renderers/bar';
3
+ import { LineChartRenderer } from './renderers/line';
4
+ import { PieChartRenderer } from './renderers/pie';
5
+ const renderers = new Map([
6
+ ['bar', new BarChartRenderer()],
7
+ ['line', new LineChartRenderer()],
8
+ ['pie', new PieChartRenderer(false)],
9
+ ['doughnut', new PieChartRenderer(true)],
10
+ ]);
11
+ /**
12
+ * Registers or overrides a chart renderer.
13
+ */
14
+ export function registerChartRenderer(type, renderer) {
15
+ renderers.set(type, renderer);
16
+ }
17
+ /**
18
+ * Returns the registered renderer for given chart type.
19
+ */
20
+ export function getChartRenderer(type) {
21
+ return renderers.get(type);
22
+ }
23
+ /**
24
+ * Renders chart on the given canvas element with hidpi scaling.
25
+ */
26
+ export function renderChart(canvasEl, w, h, type, currentData, options = {}) {
27
+ const ctx = canvasEl.getContext('2d');
28
+ if (!ctx)
29
+ return;
30
+ const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
31
+ canvasEl.width = Math.floor(w * dpr);
32
+ canvasEl.height = Math.floor(h * dpr);
33
+ ctx.resetTransform?.();
34
+ ctx.scale(dpr, dpr);
35
+ ctx.clearRect(0, 0, w, h);
36
+ if (!currentData || !currentData.datasets || currentData.datasets.length === 0) {
37
+ drawMessage(ctx, w, h, 'No data to display');
38
+ return;
39
+ }
40
+ const renderer = getChartRenderer(type);
41
+ if (!renderer) {
42
+ drawMessage(ctx, w, h, `Unsupported chart type: ${type}`);
43
+ return;
44
+ }
45
+ const context = {
46
+ canvas: canvasEl,
47
+ ctx,
48
+ width: w,
49
+ height: h,
50
+ dpr,
51
+ };
52
+ renderer.render(context, currentData, options);
53
+ }
@@ -0,0 +1,8 @@
1
+ import type { ChartData, ChartOptions } from '../types';
2
+ import type { ChartRenderer, RenderContext } from './base';
3
+ /**
4
+ * Renderer for bar and column charts.
5
+ */
6
+ export declare class BarChartRenderer implements ChartRenderer {
7
+ render(context: RenderContext, data: ChartData, options: ChartOptions): void;
8
+ }