@archbase/components 3.0.25 → 4.0.1

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 (168) hide show
  1. package/dist/buttons/ArchbaseSpeedDial.d.ts +59 -0
  2. package/dist/buttons/index.d.ts +2 -0
  3. package/dist/charts/ArchbaseArcGauge.d.ts +55 -0
  4. package/dist/charts/ArchbaseGantt.d.ts +70 -0
  5. package/dist/charts/ArchbaseLinearGauge.d.ts +55 -0
  6. package/dist/charts/ArchbaseResourceTimeline.d.ts +61 -0
  7. package/dist/charts/ArchbaseSparkline.d.ts +45 -0
  8. package/dist/charts/index.d.ts +10 -0
  9. package/dist/containers/ArchbaseExpansionPanel.d.ts +60 -0
  10. package/dist/containers/ArchbaseFloatingPanel.d.ts +91 -0
  11. package/dist/containers/index.d.ts +4 -0
  12. package/dist/datagrid/export/ArchbaseDataGridExcelExport.d.ts +67 -0
  13. package/dist/datagrid/export/index.d.ts +2 -0
  14. package/dist/datagrid/index.d.ts +1 -0
  15. package/dist/display/ArchbaseAnimationWrapper.d.ts +73 -0
  16. package/dist/display/ArchbaseBarcodeGenerator.d.ts +50 -0
  17. package/dist/display/ArchbaseCarousel.d.ts +100 -0
  18. package/dist/display/ArchbaseCodeViewer.d.ts +63 -0
  19. package/dist/display/ArchbaseDiffViewer.d.ts +41 -0
  20. package/dist/display/ArchbaseJsonTree.d.ts +34 -0
  21. package/dist/display/ArchbaseLightbox.d.ts +89 -0
  22. package/dist/display/ArchbaseMarquee.d.ts +46 -0
  23. package/dist/display/ArchbasePhotoAlbum.d.ts +58 -0
  24. package/dist/display/ArchbaseQRCode.d.ts +44 -0
  25. package/dist/display/ArchbaseTooltipRich.d.ts +82 -0
  26. package/dist/display/ArchbaseVirtualList.d.ts +116 -0
  27. package/dist/display/index.d.ts +24 -0
  28. package/dist/editors/ArchbaseAIPromptInput.d.ts +69 -0
  29. package/dist/editors/ArchbaseAsyncMultiSelect.d.ts +1 -1
  30. package/dist/editors/ArchbaseAsyncSelect.d.ts +1 -1
  31. package/dist/editors/ArchbaseBarcodeScanner.d.ts +63 -0
  32. package/dist/editors/ArchbaseCepInput.d.ts +26 -0
  33. package/dist/editors/ArchbaseColorGradientPicker.d.ts +80 -0
  34. package/dist/editors/ArchbaseCpfCnpjInput.d.ts +21 -0
  35. package/dist/editors/ArchbaseCurrencyInput.d.ts +25 -0
  36. package/dist/editors/ArchbaseDualListbox.d.ts +61 -0
  37. package/dist/editors/ArchbaseInPlaceEditor.d.ts +67 -0
  38. package/dist/editors/ArchbaseLightGrid.d.ts +56 -0
  39. package/dist/editors/ArchbaseMentionInput.d.ts +75 -0
  40. package/dist/editors/ArchbaseMultiEmail.d.ts +53 -0
  41. package/dist/editors/ArchbaseMultiViewCalendar.d.ts +47 -0
  42. package/dist/editors/ArchbaseNumberStepper.d.ts +66 -0
  43. package/dist/editors/ArchbaseOTPInput.d.ts +27 -0
  44. package/dist/editors/ArchbasePhoneInput.d.ts +52 -0
  45. package/dist/editors/ArchbaseRangeSlider.d.ts +22 -0
  46. package/dist/editors/ArchbaseSignaturePad.d.ts +60 -0
  47. package/dist/editors/ArchbaseSpeechToTextInput.d.ts +106 -0
  48. package/dist/editors/ArchbaseSpreadsheet.d.ts +56 -0
  49. package/dist/editors/ArchbaseTagInput.d.ts +55 -0
  50. package/dist/editors/index.d.ts +40 -0
  51. package/dist/feedback/ArchbaseChunkProgressBar.d.ts +35 -0
  52. package/dist/feedback/ArchbaseRipple.d.ts +43 -0
  53. package/dist/feedback/ArchbaseSkeleton.d.ts +115 -0
  54. package/dist/feedback/index.d.ts +6 -0
  55. package/dist/filters/ArchbaseColumnSelector.d.ts +38 -0
  56. package/dist/filters/index.d.ts +2 -0
  57. package/dist/forms/ArchbaseFieldArray.d.ts +80 -0
  58. package/dist/forms/ArchbaseFormWizard.d.ts +73 -0
  59. package/dist/forms/ArchbaseStepper.d.ts +51 -0
  60. package/dist/forms/index.d.ts +8 -0
  61. package/dist/forms/useArchbaseFormWizard.d.ts +74 -0
  62. package/dist/hooks/index.d.ts +2 -0
  63. package/dist/hooks/useArchbaseConfirm.d.ts +59 -0
  64. package/dist/image/editor/index.d.ts +43 -4
  65. package/dist/image/index.d.ts +1 -0
  66. package/dist/index.css +1 -1
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.js +22510 -13820
  69. package/dist/list/ArchbaseTreeList.d.ts +76 -0
  70. package/dist/list/index.d.ts +2 -0
  71. package/dist/navigation/ArchbaseActionSheet.d.ts +70 -0
  72. package/dist/navigation/ArchbaseAppBar.d.ts +63 -0
  73. package/dist/navigation/ArchbaseBottomNavigation.d.ts +61 -0
  74. package/dist/navigation/ArchbaseContextMenu.d.ts +76 -0
  75. package/dist/navigation/ArchbaseKeyboardShortcuts.d.ts +85 -0
  76. package/dist/navigation/ArchbaseSpotlight.d.ts +69 -0
  77. package/dist/navigation/index.d.ts +12 -0
  78. package/dist/notification/ArchbaseNotificationCenter.d.ts +56 -0
  79. package/dist/notification/index.d.ts +2 -0
  80. package/dist/onboarding/ArchbaseOnboardingTour.d.ts +27 -76
  81. package/dist/viewers/ArchbaseDocViewer.d.ts +40 -0
  82. package/dist/viewers/ArchbasePdfBuilder.d.ts +53 -0
  83. package/dist/viewers/ArchbaseXmlViewer.d.ts +36 -0
  84. package/dist/viewers/index.d.ts +6 -0
  85. package/package.json +41 -18
  86. package/src/buttons/ArchbaseSpeedDial.tsx +282 -0
  87. package/src/buttons/index.ts +3 -0
  88. package/src/charts/ArchbaseArcGauge.tsx +325 -0
  89. package/src/charts/ArchbaseGantt.tsx +494 -0
  90. package/src/charts/ArchbaseLinearGauge.tsx +288 -0
  91. package/src/charts/ArchbaseResourceTimeline.tsx +433 -0
  92. package/src/charts/ArchbaseSparkline.tsx +193 -0
  93. package/src/charts/index.ts +20 -1
  94. package/src/containers/ArchbaseExpansionPanel.tsx +208 -0
  95. package/src/containers/ArchbaseFloatingPanel.tsx +448 -0
  96. package/src/containers/index.ts +17 -0
  97. package/src/datagrid/components/pagination/grid-pagination.tsx +2 -2
  98. package/src/datagrid/export/ArchbaseDataGridExcelExport.tsx +293 -0
  99. package/src/datagrid/export/index.ts +8 -0
  100. package/src/datagrid/index.tsx +2 -0
  101. package/src/display/ArchbaseAnimationWrapper.tsx +272 -0
  102. package/src/display/ArchbaseBarcodeGenerator.tsx +303 -0
  103. package/src/display/ArchbaseCarousel.tsx +679 -0
  104. package/src/display/ArchbaseCodeViewer.tsx +271 -0
  105. package/src/display/ArchbaseDiffViewer.tsx +108 -0
  106. package/src/display/ArchbaseJsonTree.tsx +150 -0
  107. package/src/display/ArchbaseLightbox.tsx +279 -0
  108. package/src/display/ArchbaseMarquee.tsx +268 -0
  109. package/src/display/ArchbasePhotoAlbum.tsx +415 -0
  110. package/src/display/ArchbaseQRCode.tsx +137 -0
  111. package/src/display/ArchbaseTooltipRich.tsx +317 -0
  112. package/src/display/ArchbaseVirtualList.tsx +618 -0
  113. package/src/display/index.ts +94 -1
  114. package/src/editors/ArchbaseAIPromptInput.tsx +432 -0
  115. package/src/editors/ArchbaseBarcodeScanner.tsx +570 -0
  116. package/src/editors/ArchbaseCepInput.tsx +173 -0
  117. package/src/editors/ArchbaseColorGradientPicker.tsx +460 -0
  118. package/src/editors/ArchbaseCpfCnpjInput.tsx +193 -0
  119. package/src/editors/ArchbaseCurrencyInput.tsx +100 -0
  120. package/src/editors/ArchbaseDualListbox.tsx +671 -0
  121. package/src/editors/ArchbaseInPlaceEditor.tsx +406 -0
  122. package/src/editors/ArchbaseLightGrid.tsx +538 -0
  123. package/src/editors/ArchbaseMentionInput.tsx +384 -0
  124. package/src/editors/ArchbaseMultiEmail.tsx +367 -0
  125. package/src/editors/ArchbaseMultiViewCalendar.tsx +439 -0
  126. package/src/editors/ArchbaseNumberStepper.tsx +219 -0
  127. package/src/editors/ArchbaseOTPInput.tsx +133 -0
  128. package/src/editors/ArchbaseOperationHoursEditor.tsx +1 -1
  129. package/src/editors/ArchbasePhoneInput.tsx +355 -0
  130. package/src/editors/ArchbaseRangeSlider.tsx +109 -0
  131. package/src/editors/ArchbaseSignaturePad.tsx +425 -0
  132. package/src/editors/ArchbaseSpeechToTextInput.tsx +444 -0
  133. package/src/editors/ArchbaseSpreadsheet.tsx +388 -0
  134. package/src/editors/ArchbaseTagInput.tsx +326 -0
  135. package/src/editors/index.tsx +92 -0
  136. package/src/feedback/ArchbaseChunkProgressBar.tsx +156 -0
  137. package/src/feedback/ArchbaseRipple.tsx +278 -0
  138. package/src/feedback/ArchbaseSkeleton.tsx +419 -0
  139. package/src/feedback/index.ts +22 -1
  140. package/src/filters/ArchbaseColumnSelector.tsx +268 -0
  141. package/src/filters/index.ts +2 -0
  142. package/src/forms/ArchbaseFieldArray.tsx +305 -0
  143. package/src/forms/ArchbaseFormWizard.tsx +284 -0
  144. package/src/forms/ArchbaseStepper.tsx +137 -0
  145. package/src/forms/index.ts +24 -2
  146. package/src/forms/useArchbaseFormWizard.ts +283 -0
  147. package/src/hooks/index.ts +12 -0
  148. package/src/hooks/useArchbaseConfirm.ts +293 -0
  149. package/src/image/editor/functions/image-processing.ts +5 -0
  150. package/src/image/editor/index.tsx +305 -391
  151. package/src/image/index.ts +1 -0
  152. package/src/index.ts +3 -0
  153. package/src/list/ArchbaseTreeList.tsx +422 -0
  154. package/src/list/index.ts +3 -0
  155. package/src/navigation/ArchbaseActionSheet.tsx +301 -0
  156. package/src/navigation/ArchbaseAppBar.tsx +233 -0
  157. package/src/navigation/ArchbaseBottomNavigation.tsx +243 -0
  158. package/src/navigation/ArchbaseContextMenu.tsx +298 -0
  159. package/src/navigation/ArchbaseKeyboardShortcuts.tsx +472 -0
  160. package/src/navigation/ArchbaseSpotlight.tsx +483 -0
  161. package/src/navigation/index.ts +48 -1
  162. package/src/notification/ArchbaseNotificationCenter.tsx +502 -0
  163. package/src/notification/index.ts +2 -0
  164. package/src/onboarding/ArchbaseOnboardingTour.tsx +229 -102
  165. package/src/viewers/ArchbaseDocViewer.tsx +584 -0
  166. package/src/viewers/ArchbasePdfBuilder.tsx +291 -0
  167. package/src/viewers/ArchbaseXmlViewer.tsx +142 -0
  168. package/src/viewers/index.ts +12 -0
@@ -0,0 +1,388 @@
1
+ /**
2
+ * ArchbaseSpreadsheet — wrapper para fortune-sheet integrado ao DataSource.
3
+ * Permite exibir e editar dados tabulares em formato de planilha.
4
+ * @status experimental
5
+ */
6
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
+ import type { CSSProperties } from 'react';
8
+ import { Box, Input } from '@mantine/core';
9
+ import { useForceUpdate } from '@mantine/hooks';
10
+ import { Workbook } from '@fortune-sheet/react';
11
+ import '@fortune-sheet/react/dist/index.css';
12
+ import type { DataSourceEvent, IArchbaseDataSourceBase } from '@archbase/data';
13
+ import { DataSourceEventNames, useArchbaseDataSourceListener } from '@archbase/data';
14
+
15
+ // ─────────────────────────── Types ───────────────────────────
16
+
17
+ export type ArchbaseColumnType = 'text' | 'number' | 'date' | 'boolean' | 'currency';
18
+
19
+ export interface ArchbaseColumnDef<T> {
20
+ /** Campo do registro mapeado para esta coluna */
21
+ field: keyof T | string;
22
+ /** Cabeçalho exibido na planilha */
23
+ header: string;
24
+ /** Largura da coluna em pixels */
25
+ width?: number;
26
+ /** Tipo do dado */
27
+ type?: ArchbaseColumnType;
28
+ /** Formato numérico (ex: '#,##0.00') */
29
+ format?: string;
30
+ /** Se a coluna é editável */
31
+ editable?: boolean;
32
+ }
33
+
34
+ export interface ArchbaseSpreadsheetProps<T, ID> {
35
+ /** Fonte de dados que fornece os registros */
36
+ dataSource?: IArchbaseDataSourceBase<T>;
37
+ /** Definição de colunas para mapeamento DataSource -> planilha */
38
+ columns?: ArchbaseColumnDef<T>[];
39
+ /** Dados standalone no formato Sheet[] do fortune-sheet */
40
+ data?: any[];
41
+ /** Permite edição (default: true) */
42
+ editable?: boolean;
43
+ /** Exibe a toolbar (default: true) */
44
+ showToolbar?: boolean;
45
+ /** Exibe a barra de fórmulas (default: true) */
46
+ showFormulaBar?: boolean;
47
+ /** Exibe as abas de planilhas (default: true) */
48
+ showSheetTabs?: boolean;
49
+ /** Altura do componente */
50
+ height?: string | number;
51
+ /** Largura do componente */
52
+ width?: string | number;
53
+ /** Callback quando uma célula é alterada */
54
+ onCellChange?: (row: number, col: number, oldValue: any, newValue: any) => void;
55
+ /** Callback quando a seleção muda */
56
+ onSelectionChange?: (ranges: any[]) => void;
57
+ /** Rótulo (Input.Wrapper) */
58
+ label?: string;
59
+ /** Descrição (Input.Wrapper) */
60
+ description?: string;
61
+ /** Somente leitura */
62
+ readOnly?: boolean;
63
+ /** Locale (default: 'pt-BR') */
64
+ locale?: string;
65
+ /** Estilo adicional do container */
66
+ style?: CSSProperties;
67
+ /** Classe CSS adicional */
68
+ className?: string;
69
+ }
70
+
71
+ // ─────────────────────── Helpers ─────────────────────────
72
+
73
+ /** Retorna o valor de um campo possivelmente aninhado (notacao de ponto) */
74
+ function getNestedValue(obj: any, path: string): any {
75
+ if (!obj || !path) return undefined;
76
+ return path.split('.').reduce((acc, part) => acc?.[part], obj);
77
+ }
78
+
79
+ /** Mapeia ArchbaseColumnType para o cell type do fortune-sheet */
80
+ function mapColumnTypeToCellType(type?: ArchbaseColumnType): number {
81
+ switch (type) {
82
+ case 'number':
83
+ case 'currency':
84
+ return 0; // number
85
+ case 'boolean':
86
+ return 0; // tratado como number (0/1)
87
+ case 'date':
88
+ return 0; // date stored as number in sheets
89
+ case 'text':
90
+ default:
91
+ return 1; // string
92
+ }
93
+ }
94
+
95
+ /** Converte valor do DataSource para valor da célula */
96
+ function toCellValue(value: any, type?: ArchbaseColumnType): any {
97
+ if (value === null || value === undefined) return '';
98
+ switch (type) {
99
+ case 'boolean':
100
+ return value ? 1 : 0;
101
+ case 'number':
102
+ case 'currency':
103
+ return typeof value === 'number' ? value : Number(value) || 0;
104
+ default:
105
+ return String(value);
106
+ }
107
+ }
108
+
109
+ /** Converte valor da célula de volta para o tipo esperado pelo DataSource */
110
+ function fromCellValue(value: any, type?: ArchbaseColumnType): any {
111
+ if (value === '' || value === null || value === undefined) return null;
112
+ switch (type) {
113
+ case 'boolean':
114
+ return Boolean(Number(value));
115
+ case 'number':
116
+ case 'currency':
117
+ return Number(value);
118
+ default:
119
+ return String(value);
120
+ }
121
+ }
122
+
123
+ // ─────────────────────── Conversao DS -> Sheet ───────────────────────
124
+
125
+ function buildSheetFromDataSource<T>(
126
+ dataSource: IArchbaseDataSourceBase<T>,
127
+ columns: ArchbaseColumnDef<T>[],
128
+ ): any {
129
+ const records = dataSource.browseRecords();
130
+ const celldata: any[] = [];
131
+
132
+ // Header row
133
+ columns.forEach((col, colIdx) => {
134
+ celldata.push({
135
+ r: 0,
136
+ c: colIdx,
137
+ v: {
138
+ v: col.header,
139
+ ct: { fa: 'General', t: 's' },
140
+ m: col.header,
141
+ bl: 1, // bold
142
+ },
143
+ });
144
+ });
145
+
146
+ // Data rows
147
+ records.forEach((record, rowIdx) => {
148
+ columns.forEach((col, colIdx) => {
149
+ const raw = getNestedValue(record, String(col.field));
150
+ const value = toCellValue(raw, col.type);
151
+ const ct =
152
+ col.type === 'number' || col.type === 'currency'
153
+ ? { fa: col.format || 'General', t: 'n' }
154
+ : { fa: 'General', t: 's' };
155
+
156
+ celldata.push({
157
+ r: rowIdx + 1, // +1 for header
158
+ c: colIdx,
159
+ v: {
160
+ v: value,
161
+ ct,
162
+ m: String(value),
163
+ },
164
+ });
165
+ });
166
+ });
167
+
168
+ // Column widths
169
+ const columnlen: Record<number, number> = {};
170
+ columns.forEach((col, idx) => {
171
+ columnlen[idx] = col.width ?? 100;
172
+ });
173
+
174
+ return {
175
+ name: 'Sheet1',
176
+ index: '0',
177
+ status: 1,
178
+ order: 0,
179
+ celldata,
180
+ column: columns.length,
181
+ row: records.length + 1,
182
+ config: {
183
+ columnlen,
184
+ },
185
+ };
186
+ }
187
+
188
+ // ─────────────────────── Componente ─────────────────────────
189
+
190
+ function ArchbaseSpreadsheetInner<T, ID>(
191
+ props: ArchbaseSpreadsheetProps<T, ID>,
192
+ ) {
193
+ const {
194
+ dataSource,
195
+ columns,
196
+ data: externalData,
197
+ editable = true,
198
+ showToolbar = true,
199
+ showFormulaBar = true,
200
+ showSheetTabs = true,
201
+ height = '400px',
202
+ width = '100%',
203
+ onCellChange,
204
+ onSelectionChange,
205
+ label,
206
+ description,
207
+ readOnly = false,
208
+ locale = 'pt-BR',
209
+ style,
210
+ className,
211
+ } = props;
212
+
213
+ const forceUpdate = useForceUpdate();
214
+ const prevCellDataRef = useRef<Map<string, any>>(new Map());
215
+
216
+ // ── Construir dados da planilha ──
217
+
218
+ const sheetData = useMemo(() => {
219
+ if (externalData && externalData.length > 0) {
220
+ return externalData;
221
+ }
222
+ if (dataSource && columns && columns.length > 0) {
223
+ return [buildSheetFromDataSource(dataSource, columns)];
224
+ }
225
+ return [
226
+ {
227
+ name: 'Sheet1',
228
+ index: '0',
229
+ status: 1,
230
+ order: 0,
231
+ celldata: [],
232
+ column: 10,
233
+ row: 30,
234
+ },
235
+ ];
236
+ }, [externalData, dataSource, columns]);
237
+
238
+ // ── Guardar referencia a valores anteriores para detectar mudancas ──
239
+
240
+ useEffect(() => {
241
+ const map = new Map<string, any>();
242
+ if (sheetData && sheetData.length > 0) {
243
+ const sheet = sheetData[0];
244
+ if (sheet.celldata) {
245
+ for (const cell of sheet.celldata) {
246
+ map.set(`${cell.r}_${cell.c}`, cell.v?.v);
247
+ }
248
+ }
249
+ }
250
+ prevCellDataRef.current = map;
251
+ }, [sheetData]);
252
+
253
+ // ── Listener do DataSource ──
254
+
255
+ const handleDataSourceEvent = useCallback(
256
+ (event: DataSourceEvent<T>) => {
257
+ switch (event.type) {
258
+ case DataSourceEventNames.dataChanged:
259
+ case DataSourceEventNames.recordChanged:
260
+ case DataSourceEventNames.refreshData:
261
+ case DataSourceEventNames.afterInsert:
262
+ case DataSourceEventNames.afterRemove:
263
+ case DataSourceEventNames.afterCancel:
264
+ case DataSourceEventNames.afterSave:
265
+ case DataSourceEventNames.afterEdit:
266
+ forceUpdate();
267
+ break;
268
+ }
269
+ },
270
+ [forceUpdate],
271
+ );
272
+
273
+ if (dataSource) {
274
+ useArchbaseDataSourceListener<T>({
275
+ dataSource,
276
+ listener: handleDataSourceEvent,
277
+ });
278
+ }
279
+
280
+ // ── Handlers fortune-sheet ──
281
+
282
+ const handleCellUpdated = useCallback(
283
+ (row: number, col: number, newValue: any) => {
284
+ const key = `${row}_${col}`;
285
+ const oldValue = prevCellDataRef.current.get(key);
286
+ prevCellDataRef.current.set(key, newValue);
287
+
288
+ // Callback externo
289
+ onCellChange?.(row, col, oldValue, newValue);
290
+
291
+ // Atualizar DataSource se em modo de edicao
292
+ if (dataSource && columns && columns.length > 0 && row > 0) {
293
+ const recordIndex = row - 1; // header na linha 0
294
+ const colDef = columns[col];
295
+ if (colDef && colDef.editable !== false) {
296
+ const records = dataSource.browseRecords();
297
+ if (recordIndex >= 0 && recordIndex < records.length) {
298
+ // Navegar para o registro correspondente
299
+ if (dataSource.getCurrentIndex() !== recordIndex) {
300
+ dataSource.gotoRecord(recordIndex);
301
+ }
302
+ if (dataSource.isEditing() || dataSource.isInserting()) {
303
+ const converted = fromCellValue(newValue, colDef.type);
304
+ dataSource.setFieldValue(String(colDef.field), converted);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ },
310
+ [dataSource, columns, onCellChange],
311
+ );
312
+
313
+ // ── Fortune-sheet onChange handler ──
314
+
315
+ const handleChange = useCallback(
316
+ (data: any[]) => {
317
+ // fortune-sheet dispara onChange com os dados atualizados
318
+ // Detectar mudancas de celula comparando com o estado anterior
319
+ if (data && data.length > 0) {
320
+ const sheet = data[0];
321
+ if (sheet?.celldata) {
322
+ for (const cell of sheet.celldata) {
323
+ const key = `${cell.r}_${cell.c}`;
324
+ const prev = prevCellDataRef.current.get(key);
325
+ const cur = cell.v?.v;
326
+ if (prev !== cur) {
327
+ handleCellUpdated(cell.r, cell.c, cur);
328
+ }
329
+ }
330
+ }
331
+ }
332
+ },
333
+ [handleCellUpdated],
334
+ );
335
+
336
+ // ── Locale mapping ──
337
+
338
+ const lang = useMemo(() => {
339
+ if (locale?.startsWith('pt')) return 'pt-BR' as const;
340
+ if (locale?.startsWith('es')) return 'es' as const;
341
+ if (locale?.startsWith('zh')) return 'zh' as const;
342
+ return 'en' as const;
343
+ }, [locale]);
344
+
345
+ // ── Computed height ──
346
+
347
+ const computedHeight = typeof height === 'number' ? `${height}px` : height;
348
+ const computedWidth = typeof width === 'number' ? `${width}px` : width;
349
+
350
+ // ── Render ──
351
+
352
+ const spreadsheetContent = (
353
+ <Box
354
+ className={className}
355
+ style={{
356
+ height: computedHeight,
357
+ width: computedWidth,
358
+ position: 'relative',
359
+ overflow: 'hidden',
360
+ ...style,
361
+ }}
362
+ >
363
+ <Workbook
364
+ data={sheetData}
365
+ onChange={handleChange}
366
+ allowEdit={editable && !readOnly}
367
+ showToolbar={showToolbar}
368
+ showFormulaBar={showFormulaBar}
369
+ showSheetTabs={showSheetTabs}
370
+ lang={lang}
371
+ />
372
+ </Box>
373
+ );
374
+
375
+ if (label || description) {
376
+ return (
377
+ <Input.Wrapper label={label} description={description}>
378
+ {spreadsheetContent}
379
+ </Input.Wrapper>
380
+ );
381
+ }
382
+
383
+ return spreadsheetContent;
384
+ }
385
+
386
+ export const ArchbaseSpreadsheet = React.memo(ArchbaseSpreadsheetInner) as typeof ArchbaseSpreadsheetInner;
387
+
388
+ (ArchbaseSpreadsheet as any).displayName = 'ArchbaseSpreadsheet';
@@ -0,0 +1,326 @@
1
+ /**
2
+ * ArchbaseTagInput — editor de tags integrado ao DataSource (v1/v2) com suporte a validacao e estado controlado.
3
+ * Utiliza o componente TagsInput do Mantine internamente.
4
+ * @status stable
5
+ */
6
+ import { MantineSize, TagsInput } from '@mantine/core';
7
+ import { useForceUpdate } from '@mantine/hooks';
8
+ import type { CSSProperties, FocusEventHandler } from 'react';
9
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
10
+ import type { DataSourceEvent, IArchbaseDataSourceBase } from '@archbase/data';
11
+ import { DataSourceEventNames, useArchbaseDidUpdate, useArchbaseV1V2Compatibility } from '@archbase/data';
12
+ import { useValidationErrors } from '@archbase/core';
13
+
14
+ export interface ArchbaseTagInputProps<T, ID> {
15
+ /** Fonte de dados onde sera atribuido o valor das tags (V1 ou V2) */
16
+ dataSource?: IArchbaseDataSourceBase<T>;
17
+ /** Campo onde devera ser atribuido o valor das tags na fonte de dados */
18
+ dataField?: string;
19
+ /** Valor controlado (array de strings) quando usado sem dataSource */
20
+ value?: string[];
21
+ /** Valor padrao inicial (array de strings) */
22
+ defaultValue?: string[];
23
+ /** Delimitador usado para serializar/deserializar quando o campo no dataSource e uma string. Padrao: ',' */
24
+ delimiter?: string;
25
+ /** Titulo do campo */
26
+ label?: string;
27
+ /** Descricao do campo */
28
+ description?: string;
29
+ /** Ultimo erro ocorrido no campo */
30
+ error?: string;
31
+ /** Texto sugestao do campo */
32
+ placeholder?: string;
33
+ /** Indicador se o preenchimento e obrigatorio */
34
+ required?: boolean;
35
+ /** Indicador se o campo esta desabilitado */
36
+ disabled?: boolean;
37
+ /** Indicador se o campo e somente leitura. Obs: usado em conjunto com o status da fonte de dados */
38
+ readOnly?: boolean;
39
+ /** Lista de caracteres que, ao serem digitados, criam uma nova tag. Ex: [',', ' ', 'Enter'] */
40
+ separators?: string[];
41
+ /** Numero maximo de tags permitidas */
42
+ maxTags?: number;
43
+ /** Permite tags duplicadas */
44
+ allowDuplicates?: boolean;
45
+ /** Evento quando o valor das tags e alterado */
46
+ onChangeValue?: (value: string[], event?: any) => void;
47
+ /** Evento quando o campo recebe o foco */
48
+ onFocusEnter?: FocusEventHandler<HTMLInputElement>;
49
+ /** Evento quando o foco sai do campo */
50
+ onFocusExit?: FocusEventHandler<HTMLInputElement>;
51
+ /** Referencia para o componente interno */
52
+ innerRef?: React.RefObject<HTMLInputElement>;
53
+ /** Estilo do campo */
54
+ style?: CSSProperties;
55
+ /** Classe CSS adicional */
56
+ className?: string;
57
+ /** Largura do campo */
58
+ width?: string | number;
59
+ /** Tamanho do campo (Mantine size) */
60
+ size?: MantineSize;
61
+ }
62
+
63
+ export function ArchbaseTagInput<T, ID>({
64
+ dataSource,
65
+ dataField,
66
+ value: valueProp,
67
+ defaultValue,
68
+ delimiter = ',',
69
+ label,
70
+ description,
71
+ error,
72
+ placeholder,
73
+ required = false,
74
+ disabled = false,
75
+ readOnly = false,
76
+ separators,
77
+ maxTags,
78
+ allowDuplicates = false,
79
+ onChangeValue = () => {},
80
+ onFocusEnter = () => {},
81
+ onFocusExit = () => {},
82
+ innerRef,
83
+ style,
84
+ className,
85
+ width,
86
+ size,
87
+ }: ArchbaseTagInputProps<T, ID>) {
88
+ // Hook de compatibilidade V1/V2
89
+ const v1v2Compatibility = useArchbaseV1V2Compatibility<string[]>(
90
+ 'ArchbaseTagInput',
91
+ dataSource,
92
+ dataField,
93
+ [],
94
+ );
95
+
96
+ const [tags, setTags] = useState<string[]>(valueProp || defaultValue || []);
97
+ const innerComponentRef = useRef<any>(null);
98
+ const [internalError, setInternalError] = useState<string | undefined>(error);
99
+ const forceUpdate = useForceUpdate();
100
+ /** Guarda se o valor original no dataSource e uma string (para serializar de volta) */
101
+ const originalIsStringRef = useRef<boolean>(false);
102
+
103
+ // Contexto de validacao (opcional - pode nao existir)
104
+ const validationContext = useValidationErrors();
105
+
106
+ // Chave unica para o field
107
+ const fieldKey = `${dataField}`;
108
+
109
+ // Recuperar erro do contexto se existir
110
+ const contextError = validationContext?.getError(fieldKey);
111
+
112
+ // Atualizar erro externo
113
+ useEffect(() => {
114
+ if (error !== undefined && error !== internalError) {
115
+ setInternalError(error);
116
+ }
117
+ }, [error]);
118
+
119
+ /**
120
+ * Converte o valor bruto do campo do dataSource em um array de strings.
121
+ * Se o valor for uma string, faz split pelo delimiter.
122
+ * Se for um array, usa diretamente.
123
+ */
124
+ const parseFieldValue = (raw: any): string[] => {
125
+ if (raw === null || raw === undefined || raw === '') {
126
+ return [];
127
+ }
128
+ if (Array.isArray(raw)) {
129
+ originalIsStringRef.current = false;
130
+ return raw.map(String);
131
+ }
132
+ if (typeof raw === 'string') {
133
+ originalIsStringRef.current = true;
134
+ return raw
135
+ .split(delimiter)
136
+ .map((s) => s.trim())
137
+ .filter((s) => s.length > 0);
138
+ }
139
+ return [];
140
+ };
141
+
142
+ /**
143
+ * Serializa o array de tags de volta para o formato original do campo no dataSource.
144
+ * Se o campo original era string, junta pelo delimiter; se era array, mantem como array.
145
+ */
146
+ const serializeValue = (tagsArray: string[]): string | string[] => {
147
+ if (originalIsStringRef.current) {
148
+ return tagsArray.join(delimiter);
149
+ }
150
+ return tagsArray;
151
+ };
152
+
153
+ const loadDataSourceFieldValue = () => {
154
+ let initialValue: string[] = valueProp || defaultValue || [];
155
+
156
+ if (dataSource && dataField) {
157
+ const raw = dataSource.getFieldValue(dataField);
158
+ initialValue = parseFieldValue(raw);
159
+ }
160
+
161
+ setTags(initialValue);
162
+ };
163
+
164
+ const fieldChangedListener = useCallback(() => {
165
+ loadDataSourceFieldValue();
166
+ }, []);
167
+
168
+ const dataSourceEvent = useCallback(
169
+ (event: DataSourceEvent<T>) => {
170
+ if (dataSource && dataField) {
171
+ if (
172
+ event.type === DataSourceEventNames.dataChanged ||
173
+ event.type === DataSourceEventNames.recordChanged ||
174
+ event.type === DataSourceEventNames.afterScroll ||
175
+ event.type === DataSourceEventNames.afterCancel ||
176
+ event.type === DataSourceEventNames.afterEdit ||
177
+ event.type === DataSourceEventNames.afterInsert
178
+ ) {
179
+ loadDataSourceFieldValue();
180
+ if (!v1v2Compatibility.isDataSourceV2) {
181
+ forceUpdate();
182
+ }
183
+ }
184
+
185
+ // Limpa erros em eventos de edicao/cancelamento
186
+ if (
187
+ event.type === DataSourceEventNames.afterEdit ||
188
+ event.type === DataSourceEventNames.afterInsert ||
189
+ event.type === DataSourceEventNames.afterCancel
190
+ ) {
191
+ setInternalError(undefined);
192
+ validationContext?.clearError(fieldKey);
193
+ }
194
+
195
+ if (
196
+ event.type === DataSourceEventNames.onFieldError &&
197
+ event.fieldName === dataField
198
+ ) {
199
+ setInternalError(event.error);
200
+ validationContext?.setError(fieldKey, event.error);
201
+ if (!v1v2Compatibility.isDataSourceV2) {
202
+ forceUpdate();
203
+ }
204
+ }
205
+ }
206
+ },
207
+ [
208
+ v1v2Compatibility.isDataSourceV2,
209
+ dataSource,
210
+ dataField,
211
+ loadDataSourceFieldValue,
212
+ forceUpdate,
213
+ validationContext,
214
+ fieldKey,
215
+ ],
216
+ );
217
+
218
+ // Ref para manter callback sempre atualizado (corrige problema de closure desatualizada)
219
+ const dataSourceEventRef = useRef(dataSourceEvent);
220
+ useEffect(() => {
221
+ dataSourceEventRef.current = dataSourceEvent;
222
+ }, [dataSourceEvent]);
223
+
224
+ // Wrapper estavel que delega para ref - nunca muda, entao o listener permanece consistente
225
+ const stableDataSourceEvent = useCallback((event: DataSourceEvent<T>) => {
226
+ dataSourceEventRef.current(event);
227
+ }, []);
228
+
229
+ // Registrar listeners com cleanup apropriado
230
+ useEffect(() => {
231
+ loadDataSourceFieldValue();
232
+ if (dataSource && dataField) {
233
+ const hasFieldListener =
234
+ typeof (dataSource as any).addFieldChangeListener === 'function';
235
+ dataSource.addListener(stableDataSourceEvent);
236
+ if (hasFieldListener) {
237
+ (dataSource as any).addFieldChangeListener(dataField, fieldChangedListener);
238
+ }
239
+
240
+ return () => {
241
+ dataSource.removeListener(stableDataSourceEvent);
242
+ if (hasFieldListener) {
243
+ (dataSource as any).removeFieldChangeListener(
244
+ dataField,
245
+ fieldChangedListener,
246
+ );
247
+ }
248
+ };
249
+ }
250
+ }, [dataSource, dataField, stableDataSourceEvent, fieldChangedListener]);
251
+
252
+ useArchbaseDidUpdate(() => {
253
+ loadDataSourceFieldValue();
254
+ }, []);
255
+
256
+ const handleChange = (newTags: string[]) => {
257
+ // Limpa erro quando usuario edita o campo
258
+ const hasError = internalError || contextError;
259
+ if (hasError) {
260
+ setInternalError(undefined);
261
+ validationContext?.clearError(fieldKey);
262
+ }
263
+
264
+ setTags(newTags);
265
+
266
+ if (
267
+ dataSource &&
268
+ !dataSource.isBrowsing() &&
269
+ dataField
270
+ ) {
271
+ const serialized = serializeValue(newTags);
272
+ v1v2Compatibility.handleValueChange(serialized as any);
273
+ }
274
+
275
+ if (onChangeValue) {
276
+ onChangeValue(newTags);
277
+ }
278
+ };
279
+
280
+ const handleOnFocusExit = (event: React.FocusEvent<HTMLInputElement>) => {
281
+ if (onFocusExit) {
282
+ onFocusExit(event);
283
+ }
284
+ };
285
+
286
+ const handleOnFocusEnter = (event: React.FocusEvent<HTMLInputElement>) => {
287
+ if (onFocusEnter) {
288
+ onFocusEnter(event);
289
+ }
290
+ };
291
+
292
+ const isReadOnly = (): boolean => {
293
+ return readOnly || v1v2Compatibility.isReadOnly;
294
+ };
295
+
296
+ // Erro a ser exibido: local ou do contexto
297
+ const displayError = internalError || contextError;
298
+
299
+ return (
300
+ <TagsInput
301
+ value={tags}
302
+ onChange={handleChange}
303
+ label={label}
304
+ description={description}
305
+ error={displayError}
306
+ placeholder={placeholder}
307
+ required={required}
308
+ disabled={disabled}
309
+ readOnly={isReadOnly()}
310
+ size={size}
311
+ className={className}
312
+ maxTags={maxTags}
313
+ allowDuplicates={allowDuplicates}
314
+ splitChars={separators}
315
+ ref={innerRef || innerComponentRef}
316
+ onFocus={handleOnFocusEnter}
317
+ onBlur={handleOnFocusExit}
318
+ style={{
319
+ width,
320
+ ...style,
321
+ }}
322
+ />
323
+ );
324
+ }
325
+
326
+ ArchbaseTagInput.displayName = 'ArchbaseTagInput';