@nocios/crudify-components 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 (53) hide show
  1. package/.github/workflows/test.yml +59 -0
  2. package/.nvmrc +1 -0
  3. package/README.md +398 -0
  4. package/README_DEPTH.md +1230 -0
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/coverage-final.json +85 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +506 -0
  10. package/coverage/prettify.css +1 -0
  11. package/coverage/prettify.js +2 -0
  12. package/coverage/sort-arrow-sprite.png +0 -0
  13. package/coverage/sorter.js +210 -0
  14. package/dist/CrudiaMarkdownField-C54-A_J3.d.mts +328 -0
  15. package/dist/CrudiaMarkdownField-C8HQh7s5.d.ts +328 -0
  16. package/dist/GlobalNotificationProvider-Zq18OkpI.d.mts +96 -0
  17. package/dist/GlobalNotificationProvider-Zq18OkpI.d.ts +96 -0
  18. package/dist/api-B4uXiHF0.d.mts +118 -0
  19. package/dist/api-B4uXiHF0.d.ts +118 -0
  20. package/dist/chunk-2XOTIEKS.js +1 -0
  21. package/dist/chunk-5HFI5CZ5.js +1 -0
  22. package/dist/chunk-CHDM7KGH.js +1 -0
  23. package/dist/chunk-HVTRRU4W.mjs +1 -0
  24. package/dist/chunk-JAPL7EZJ.mjs +1 -0
  25. package/dist/chunk-JNEWPO2J.mjs +1 -0
  26. package/dist/chunk-MFYHD6S5.js +1 -0
  27. package/dist/chunk-MGJZTOEM.mjs +1 -0
  28. package/dist/chunk-NBQH6QOU.mjs +1 -0
  29. package/dist/chunk-NSV6ECYO.js +1 -0
  30. package/dist/chunk-PNI3ZBZV.js +1 -0
  31. package/dist/chunk-U4RS66TB.mjs +1 -0
  32. package/dist/components.d.mts +24 -0
  33. package/dist/components.d.ts +24 -0
  34. package/dist/components.js +1 -0
  35. package/dist/components.mjs +1 -0
  36. package/dist/errorTranslation-DGdrMidg.d.ts +143 -0
  37. package/dist/errorTranslation-qwwQTvCO.d.mts +143 -0
  38. package/dist/hooks.d.mts +6 -0
  39. package/dist/hooks.d.ts +6 -0
  40. package/dist/hooks.js +1 -0
  41. package/dist/hooks.mjs +1 -0
  42. package/dist/index-BUKX3duW.d.ts +854 -0
  43. package/dist/index-Y9tTsinC.d.mts +854 -0
  44. package/dist/index.d.mts +1274 -0
  45. package/dist/index.d.ts +1274 -0
  46. package/dist/index.js +6 -0
  47. package/dist/index.mjs +6 -0
  48. package/dist/utils.d.mts +175 -0
  49. package/dist/utils.d.ts +175 -0
  50. package/dist/utils.js +1 -0
  51. package/dist/utils.mjs +1 -0
  52. package/package.json +88 -0
  53. package/vitest.config.ts +28 -0
@@ -0,0 +1,210 @@
1
+ /* eslint-disable */
2
+ var addSorting = (function() {
3
+ 'use strict';
4
+ var cols,
5
+ currentSort = {
6
+ index: 0,
7
+ desc: false
8
+ };
9
+
10
+ // returns the summary table element
11
+ function getTable() {
12
+ return document.querySelector('.coverage-summary');
13
+ }
14
+ // returns the thead element of the summary table
15
+ function getTableHeader() {
16
+ return getTable().querySelector('thead tr');
17
+ }
18
+ // returns the tbody element of the summary table
19
+ function getTableBody() {
20
+ return getTable().querySelector('tbody');
21
+ }
22
+ // returns the th element for nth column
23
+ function getNthColumn(n) {
24
+ return getTableHeader().querySelectorAll('th')[n];
25
+ }
26
+
27
+ function onFilterInput() {
28
+ const searchValue = document.getElementById('fileSearch').value;
29
+ const rows = document.getElementsByTagName('tbody')[0].children;
30
+
31
+ // Try to create a RegExp from the searchValue. If it fails (invalid regex),
32
+ // it will be treated as a plain text search
33
+ let searchRegex;
34
+ try {
35
+ searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
36
+ } catch (error) {
37
+ searchRegex = null;
38
+ }
39
+
40
+ for (let i = 0; i < rows.length; i++) {
41
+ const row = rows[i];
42
+ let isMatch = false;
43
+
44
+ if (searchRegex) {
45
+ // If a valid regex was created, use it for matching
46
+ isMatch = searchRegex.test(row.textContent);
47
+ } else {
48
+ // Otherwise, fall back to the original plain text search
49
+ isMatch = row.textContent
50
+ .toLowerCase()
51
+ .includes(searchValue.toLowerCase());
52
+ }
53
+
54
+ row.style.display = isMatch ? '' : 'none';
55
+ }
56
+ }
57
+
58
+ // loads the search box
59
+ function addSearchBox() {
60
+ var template = document.getElementById('filterTemplate');
61
+ var templateClone = template.content.cloneNode(true);
62
+ templateClone.getElementById('fileSearch').oninput = onFilterInput;
63
+ template.parentElement.appendChild(templateClone);
64
+ }
65
+
66
+ // loads all columns
67
+ function loadColumns() {
68
+ var colNodes = getTableHeader().querySelectorAll('th'),
69
+ colNode,
70
+ cols = [],
71
+ col,
72
+ i;
73
+
74
+ for (i = 0; i < colNodes.length; i += 1) {
75
+ colNode = colNodes[i];
76
+ col = {
77
+ key: colNode.getAttribute('data-col'),
78
+ sortable: !colNode.getAttribute('data-nosort'),
79
+ type: colNode.getAttribute('data-type') || 'string'
80
+ };
81
+ cols.push(col);
82
+ if (col.sortable) {
83
+ col.defaultDescSort = col.type === 'number';
84
+ colNode.innerHTML =
85
+ colNode.innerHTML + '<span class="sorter"></span>';
86
+ }
87
+ }
88
+ return cols;
89
+ }
90
+ // attaches a data attribute to every tr element with an object
91
+ // of data values keyed by column name
92
+ function loadRowData(tableRow) {
93
+ var tableCols = tableRow.querySelectorAll('td'),
94
+ colNode,
95
+ col,
96
+ data = {},
97
+ i,
98
+ val;
99
+ for (i = 0; i < tableCols.length; i += 1) {
100
+ colNode = tableCols[i];
101
+ col = cols[i];
102
+ val = colNode.getAttribute('data-value');
103
+ if (col.type === 'number') {
104
+ val = Number(val);
105
+ }
106
+ data[col.key] = val;
107
+ }
108
+ return data;
109
+ }
110
+ // loads all row data
111
+ function loadData() {
112
+ var rows = getTableBody().querySelectorAll('tr'),
113
+ i;
114
+
115
+ for (i = 0; i < rows.length; i += 1) {
116
+ rows[i].data = loadRowData(rows[i]);
117
+ }
118
+ }
119
+ // sorts the table using the data for the ith column
120
+ function sortByIndex(index, desc) {
121
+ var key = cols[index].key,
122
+ sorter = function(a, b) {
123
+ a = a.data[key];
124
+ b = b.data[key];
125
+ return a < b ? -1 : a > b ? 1 : 0;
126
+ },
127
+ finalSorter = sorter,
128
+ tableBody = document.querySelector('.coverage-summary tbody'),
129
+ rowNodes = tableBody.querySelectorAll('tr'),
130
+ rows = [],
131
+ i;
132
+
133
+ if (desc) {
134
+ finalSorter = function(a, b) {
135
+ return -1 * sorter(a, b);
136
+ };
137
+ }
138
+
139
+ for (i = 0; i < rowNodes.length; i += 1) {
140
+ rows.push(rowNodes[i]);
141
+ tableBody.removeChild(rowNodes[i]);
142
+ }
143
+
144
+ rows.sort(finalSorter);
145
+
146
+ for (i = 0; i < rows.length; i += 1) {
147
+ tableBody.appendChild(rows[i]);
148
+ }
149
+ }
150
+ // removes sort indicators for current column being sorted
151
+ function removeSortIndicators() {
152
+ var col = getNthColumn(currentSort.index),
153
+ cls = col.className;
154
+
155
+ cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
156
+ col.className = cls;
157
+ }
158
+ // adds sort indicators for current column being sorted
159
+ function addSortIndicators() {
160
+ getNthColumn(currentSort.index).className += currentSort.desc
161
+ ? ' sorted-desc'
162
+ : ' sorted';
163
+ }
164
+ // adds event listeners for all sorter widgets
165
+ function enableUI() {
166
+ var i,
167
+ el,
168
+ ithSorter = function ithSorter(i) {
169
+ var col = cols[i];
170
+
171
+ return function() {
172
+ var desc = col.defaultDescSort;
173
+
174
+ if (currentSort.index === i) {
175
+ desc = !currentSort.desc;
176
+ }
177
+ sortByIndex(i, desc);
178
+ removeSortIndicators();
179
+ currentSort.index = i;
180
+ currentSort.desc = desc;
181
+ addSortIndicators();
182
+ };
183
+ };
184
+ for (i = 0; i < cols.length; i += 1) {
185
+ if (cols[i].sortable) {
186
+ // add the click event handler on the th so users
187
+ // dont have to click on those tiny arrows
188
+ el = getNthColumn(i).querySelector('.sorter').parentElement;
189
+ if (el.addEventListener) {
190
+ el.addEventListener('click', ithSorter(i));
191
+ } else {
192
+ el.attachEvent('onclick', ithSorter(i));
193
+ }
194
+ }
195
+ }
196
+ }
197
+ // adds sorting functionality to the UI
198
+ return function() {
199
+ if (!getTable()) {
200
+ return;
201
+ }
202
+ cols = loadColumns();
203
+ loadData();
204
+ addSearchBox();
205
+ addSortIndicators();
206
+ enableUI();
207
+ };
208
+ })();
209
+
210
+ window.addEventListener('load', addSorting);
@@ -0,0 +1,328 @@
1
+ import React from 'react';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import { A as AutoGenerateConfig } from './GlobalNotificationProvider-Zq18OkpI.mjs';
4
+
5
+ /**
6
+ * Password validation types and constants
7
+ * Used for password requirements validation in forms
8
+ */
9
+ type PasswordRuleType = "minLength" | "alphanumeric" | "uppercase" | "lowercase" | "number" | "special";
10
+ interface PasswordRule {
11
+ type: PasswordRuleType;
12
+ value?: number | string;
13
+ message: string;
14
+ enforce?: boolean;
15
+ }
16
+ interface EvaluatedPasswordRule {
17
+ message: string;
18
+ valid: boolean;
19
+ enforce: boolean;
20
+ }
21
+ /**
22
+ * Default password rules used as fallback when no custom rules are provided.
23
+ * Messages are translation keys that should be translated at runtime.
24
+ */
25
+ declare const DEFAULT_PASSWORD_RULES: PasswordRule[];
26
+
27
+ type BoxScreenType = "login" | "signUp" | "forgotPassword" | "resetPassword" | "checkCode";
28
+ interface CrudifyLoginConfig {
29
+ publicApiKey?: string | null;
30
+ env?: "dev" | "stg" | "api" | "prod";
31
+ appName?: string;
32
+ logo?: string;
33
+ loginActions?: string[];
34
+ }
35
+ interface CrudifyLoginTranslations {
36
+ [key: string]: string | CrudifyLoginTranslations;
37
+ }
38
+ interface UserLoginData {
39
+ token: string;
40
+ username?: string;
41
+ email?: string;
42
+ userId?: string;
43
+ profile?: Record<string, unknown>;
44
+ [key: string]: unknown;
45
+ }
46
+ interface CrudifyLoginProps {
47
+ onScreenChange?: (screen: BoxScreenType, params?: Record<string, string>) => void;
48
+ onExternalNavigate?: (path: string) => void;
49
+ onLoginSuccess?: (userData: UserLoginData, redirectUrl?: string) => void;
50
+ onError?: (error: string) => void;
51
+ initialScreen?: BoxScreenType;
52
+ redirectUrl?: string;
53
+ autoReadFromCookies?: boolean;
54
+ translations?: CrudifyLoginTranslations;
55
+ translationsUrl?: string;
56
+ language?: string;
57
+ /** Custom password rules for reset password form. Falls back to default rules if not provided. */
58
+ passwordRules?: PasswordRule[];
59
+ }
60
+
61
+ declare const CrudifyLogin: React.FC<CrudifyLoginProps>;
62
+
63
+ interface UserProfileDisplayProps {
64
+ showExtendedData?: boolean;
65
+ showProfileCard?: boolean;
66
+ autoRefresh?: boolean;
67
+ }
68
+ declare const UserProfileDisplay: React.FC<UserProfileDisplayProps>;
69
+
70
+ declare const POLICY_ACTIONS: readonly ["create", "read", "update", "delete"];
71
+ type PolicyAction = typeof POLICY_ACTIONS[number];
72
+ declare const PREFERRED_POLICY_ORDER: PolicyAction[];
73
+
74
+ type Policy = {
75
+ id: string;
76
+ action: PolicyAction;
77
+ fields?: {
78
+ allow: string[];
79
+ owner_allow: string[];
80
+ deny: string[];
81
+ };
82
+ permission?: string;
83
+ };
84
+ type FieldErrorMap = string | ({
85
+ _error?: string;
86
+ } & Record<string, string | undefined>);
87
+ interface PoliciesProps {
88
+ policies: Policy[];
89
+ onChange: (next: Policy[]) => void;
90
+ availableFields: string[];
91
+ errors?: FieldErrorMap;
92
+ isSubmitting?: boolean;
93
+ }
94
+ declare const Policies: React.FC<PoliciesProps>;
95
+
96
+ declare function LoginComponent(): react_jsx_runtime.JSX.Element;
97
+ /**
98
+ * Componente simple de estado de sesión para mostrar en cualquier lugar
99
+ */
100
+ declare function SessionStatus(): react_jsx_runtime.JSX.Element;
101
+
102
+ interface CrudiaAutoGenerateProps {
103
+ config: AutoGenerateConfig;
104
+ value?: string;
105
+ onChange?: (value: string) => void;
106
+ label?: string;
107
+ error?: boolean;
108
+ helperText?: string;
109
+ readOnly?: boolean;
110
+ disabled?: boolean;
111
+ }
112
+ /**
113
+ * Componente para campos autogenerados (códigos secuenciales)
114
+ *
115
+ * Características:
116
+ * - Auto-fetch del código al montar
117
+ * - Detección automática de errores de duplicado
118
+ * - Botón "Regenerar" en endAdornment cuando hay error de duplicado
119
+ * - Estados de loading y error
120
+ * - Readonly por defecto
121
+ * - Integración con useAutoGenerate hook
122
+ *
123
+ * Flujo de Manejo de Errores de Duplicado:
124
+ * 1. Usuario abre formulario → Código se genera automáticamente (ej: PROD-0013671)
125
+ * 2. Usuario llena formulario y hace submit
126
+ * 3. Backend retorna error "duplicate key" o "unique constraint"
127
+ * 4. Formulario padre pasa error={true} y helperText="El código ya existe"
128
+ * 5. Componente detecta palabras clave de duplicado y muestra botón de regenerar
129
+ * 6. Usuario hace click en botón de regenerar → Nuevo código se genera
130
+ * 7. onChange se llama con nuevo código → Formulario padre limpia el error
131
+ * 8. Botón de regenerar desaparece (porque ya no hay error)
132
+ *
133
+ * @example
134
+ * ```tsx
135
+ * // Uso básico
136
+ * <CrudiaAutoGenerate
137
+ * config={{ prefix: "PROD-", padding: 7 }}
138
+ * onChange={(code) => setFormData({ ...formData, barCode: code })}
139
+ * label="Código de Barras"
140
+ * />
141
+ *
142
+ * // Con manejo de error de duplicado desde el formulario
143
+ * <CrudiaAutoGenerate
144
+ * config={{ prefix: "PROD-", padding: 7 }}
145
+ * onChange={(code) => {
146
+ * setFormData({ ...formData, barCode: code });
147
+ * // Limpiar error cuando cambia el código
148
+ * setFormErrors({ ...formErrors, barCode: null });
149
+ * }}
150
+ * label="Código de Barras"
151
+ * error={!!formErrors.barCode}
152
+ * helperText={formErrors.barCode}
153
+ * />
154
+ * ```
155
+ */
156
+ declare const CrudiaAutoGenerate: React.FC<CrudiaAutoGenerateProps>;
157
+
158
+ /**
159
+ * File field component with:
160
+ * - Drag & drop
161
+ * - File preview
162
+ * - Progress bar during upload
163
+ * - Delete files (soft delete)
164
+ * - Type and size validation
165
+ * - Single/multiple support
166
+ */
167
+
168
+ /**
169
+ * Deletion handlers exposed by the component
170
+ */
171
+ interface CrudiaFileFieldDeletionHandlers {
172
+ /** Execute all pending deletions (call this on form success callback) */
173
+ commitDeletions: () => Promise<{
174
+ success: boolean;
175
+ errors: string[];
176
+ }>;
177
+ /** Cancel all pending deletions and restore files */
178
+ restorePendingDeletions: () => void;
179
+ /** Whether there are files pending deletion */
180
+ hasPendingDeletions: boolean;
181
+ /** Mark the field as submitted (triggers validation errors display) */
182
+ markAsSubmitted: () => void;
183
+ /** Whether the field is valid */
184
+ isValid: boolean;
185
+ /** Whether files are currently being uploaded */
186
+ isUploading: boolean;
187
+ /** Wait for all uploads to complete - returns completed file paths */
188
+ waitForUploads: () => Promise<string[]>;
189
+ /** Get completed file paths synchronously (may be stale if uploads just finished) */
190
+ getCompletedFilePaths: () => string[];
191
+ }
192
+ /**
193
+ * Props del componente CrudiaFileField
194
+ */
195
+ interface CrudiaFileFieldProps {
196
+ /** Label del campo */
197
+ label?: string;
198
+ /** Tipos MIME permitidos (ej: ["image/png", "image/jpeg"]) */
199
+ accept?: string[];
200
+ /** Tamaño máximo por archivo en bytes */
201
+ maxFileSize?: number;
202
+ /** Permitir múltiples archivos */
203
+ multiple?: boolean;
204
+ /** Número máximo de archivos (solo si multiple=true) */
205
+ maxFiles?: number;
206
+ /** Número mínimo de archivos requeridos */
207
+ minFiles?: number;
208
+ /** Campo requerido */
209
+ required?: boolean;
210
+ /** Campo deshabilitado */
211
+ disabled?: boolean;
212
+ /** Error externo */
213
+ error?: boolean;
214
+ /** Texto de ayuda o error */
215
+ helperText?: string;
216
+ /**
217
+ * Callback cuando cambian los filePaths de archivos completados
218
+ * Los filePaths son rutas relativas con visibility (public/... o private/...)
219
+ */
220
+ onChange?: (filePaths: string[]) => void;
221
+ /** Callback cuando se valida el campo */
222
+ onValidation?: (isValid: boolean, error: string | null) => void;
223
+ /** Archivos existentes (para edición) - usar filePath (ruta relativa con visibility) */
224
+ initialFiles?: Array<{
225
+ filePath: string;
226
+ name: string;
227
+ size?: number;
228
+ contentType?: string;
229
+ }>;
230
+ /** Texto placeholder para drag zone */
231
+ placeholder?: string;
232
+ /** Mostrar lista de archivos */
233
+ showFileList?: boolean;
234
+ /**
235
+ * Visibilidad de los archivos subidos
236
+ * @default "private"
237
+ */
238
+ visibility?: "public" | "private";
239
+ /**
240
+ * Mostrar preview de imágenes
241
+ * @default true para imágenes
242
+ */
243
+ showPreview?: boolean;
244
+ /**
245
+ * URL base para prefijar al path del archivo
246
+ * El valor guardado será baseUrl + path
247
+ * También se usa para previsualizar/descargar archivos públicos en modo readonly
248
+ */
249
+ baseUrl: string;
250
+ /**
251
+ * Callback called when deletion handlers are ready
252
+ * Use this to get access to commitDeletions and restorePendingDeletions functions
253
+ */
254
+ onDeletionHandlersReady?: (handlers: CrudiaFileFieldDeletionHandlers) => void;
255
+ /**
256
+ * Form mode: 'create' or 'edit' - affects delete behavior
257
+ * - create: delete shows confirmation and deletes immediately from server
258
+ * - edit: existing files use pendingDeletion, new files delete immediately
259
+ * @default "create"
260
+ */
261
+ mode?: "create" | "edit";
262
+ }
263
+ /**
264
+ * Main file field component
265
+ *
266
+ * @example
267
+ * ```tsx
268
+ * // Basic usage - single file
269
+ * <CrudiaFileField
270
+ * label="Document"
271
+ * accept={["application/pdf"]}
272
+ * onChange={(filePaths) => setFormData({ ...formData, document: filePaths[0] })}
273
+ * />
274
+ *
275
+ * // Multiple files with limits
276
+ * <CrudiaFileField
277
+ * label="Images"
278
+ * accept={["image/png", "image/jpeg"]}
279
+ * multiple
280
+ * maxFiles={5}
281
+ * minFiles={1}
282
+ * maxFileSize={5 * 1024 * 1024}
283
+ * onChange={(filePaths) => setFormData({ ...formData, images: filePaths })}
284
+ * />
285
+ *
286
+ * // With initial files (edit mode)
287
+ * <CrudiaFileField
288
+ * label="Attachments"
289
+ * multiple
290
+ * initialFiles={existingFiles}
291
+ * onChange={handleFilesChange}
292
+ * />
293
+ * ```
294
+ */
295
+ declare const CrudiaFileField: React.FC<CrudiaFileFieldProps>;
296
+
297
+ /**
298
+ * Markdown field component with rich text editing using MDXEditor
299
+ * - WYSIWYG markdown editing
300
+ * - Toolbar with formatting options
301
+ * - Saves content as markdown string
302
+ */
303
+
304
+ interface CrudiaMarkdownFieldProps {
305
+ /** Field label */
306
+ label?: string;
307
+ /** Current value (markdown string) */
308
+ value?: string;
309
+ /** Callback when content changes */
310
+ onChange?: (value: string) => void;
311
+ /** Required field */
312
+ required?: boolean;
313
+ /** Disabled/readonly field */
314
+ disabled?: boolean;
315
+ /** External error state */
316
+ error?: boolean;
317
+ /** Helper or error text */
318
+ helperText?: string;
319
+ /** Placeholder text */
320
+ placeholder?: string;
321
+ /** Minimum editor height in pixels */
322
+ minHeight?: number;
323
+ /** Maximum editor height in pixels (scrolls after) */
324
+ maxHeight?: number;
325
+ }
326
+ declare const CrudiaMarkdownField: React.FC<CrudiaMarkdownFieldProps>;
327
+
328
+ export { type BoxScreenType as B, CrudifyLogin as C, DEFAULT_PASSWORD_RULES as D, type EvaluatedPasswordRule as E, LoginComponent as L, Policies as P, SessionStatus as S, UserProfileDisplay as U, CrudiaAutoGenerate as a, CrudiaFileField as b, CrudiaMarkdownField as c, type CrudifyLoginConfig as d, type CrudifyLoginProps as e, type CrudifyLoginTranslations as f, type UserLoginData as g, type PolicyAction as h, type CrudiaAutoGenerateProps as i, type CrudiaFileFieldProps as j, type CrudiaMarkdownFieldProps as k, type PasswordRule as l, POLICY_ACTIONS as m, PREFERRED_POLICY_ORDER as n, type CrudiaFileFieldDeletionHandlers as o, type PasswordRuleType as p };