@axzydev/axzy_ui_system 1.7.0 → 1.7.2
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.
- package/dist/index.cjs +1938 -491
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +739 -3
- package/dist/index.d.ts +739 -3
- package/dist/index.js +1830 -417
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +14 -7
- package/src/App.tsx +60 -2
- package/src/components/atoms/image/image.props.ts +2 -0
- package/src/components/atoms/image/image.tsx +3 -2
- package/src/components/atoms/segmented-control/segmented-control.tsx +3 -0
- package/src/components/molecules/card/card.tsx +12 -0
- package/src/components/molecules/stat-card/stat-card.tsx +10 -0
- package/src/components/organisms/dialog/dialog.props.ts +2 -0
- package/src/components/organisms/dialog/dialog.tsx +4 -3
- package/src/dev.css +6 -0
- package/src/index.ts +90 -1
- package/src/json-renderer/catalog/axzyCatalog.ts +362 -0
- package/src/json-renderer/catalog/catalog.props.ts +128 -0
- package/src/json-renderer/catalog/resolve.ts +225 -0
- package/src/json-renderer/index.ts +70 -0
- package/src/json-renderer/kernel/NodeBoundary.tsx +53 -0
- package/src/json-renderer/kernel/SchemaRenderer.tsx +197 -0
- package/src/json-renderer/kernel/kernel.test.tsx +341 -0
- package/src/json-renderer/kernel/partial.ts +92 -0
- package/src/json-renderer/kernel/renderNode.tsx +113 -0
- package/src/json-renderer/kernel/schemaRenderer.props.ts +42 -0
- package/src/json-renderer/kernel/types.ts +159 -0
- package/src/json-renderer/shared/errors.ts +92 -0
- package/src/json-renderer/shared/limits.ts +87 -0
- package/src/json-renderer/shared/paths.ts +66 -0
- package/src/json-renderer/shared/shared.test.ts +108 -0
- package/src/json-renderer/stream/tolerantParse.test.ts +152 -0
- package/src/json-renderer/stream/tolerantParse.ts +449 -0
- package/src/json-renderer/test/setup.ts +38 -0
- package/src/json-renderer/validation/migrate.ts +83 -0
- package/src/json-renderer/validation/validate.ts +161 -0
- package/src/json-renderer/validation/validation.test.ts +165 -0
- package/src/json-renderer/vocabulary/buildContract.ts +158 -0
- package/src/sandbox/axzyConfig.test.ts +40 -0
- package/src/sandbox/axzyConfig.ts +77 -0
- package/src/sandbox/i18n.ts +218 -0
- package/src/sandbox/navigation.ts +1 -0
- package/src/sandbox/useHashRoute.ts +39 -9
- package/src/showcases/DemoJsonDashboard.tsx +489 -0
- package/src/showcases/DemoJsonDocs.tsx +339 -0
- package/src/showcases/DemoJsonPortfolio.tsx +257 -0
- package/src/showcases/JsonScreenShowcase.tsx +341 -0
- package/src/showcases/demoApp/demoData.ts +181 -0
- package/src/showcases/demoApp/nodeTree.test.ts +39 -0
- package/src/showcases/demoApp/nodeTree.ts +35 -0
- package/src/showcases/demoApp/schemas.test.ts +54 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catálogo AXZY por defecto del motor de pantallas.
|
|
3
|
+
*
|
|
4
|
+
* Cubre los componentes que tienen sentido en un esquema (25 en esta fase). Los
|
|
5
|
+
* que ejecutan entrada del usuario, provocan recursión o deciden el tema quedan
|
|
6
|
+
* **excluidos a propósito** y se documentan con su motivo.
|
|
7
|
+
*
|
|
8
|
+
* Regla del catálogo: una prop solo entra si su valor puede describirse en JSON
|
|
9
|
+
* sin código. Por eso `onClick` es `kind: "action"` (nombre de una acción
|
|
10
|
+
* registrada) y los nodos React (`icon`, `actions`, `badge`…) están prohibidos.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import ITText from "@/components/atoms/text/text";
|
|
14
|
+
import ITButton from "@/components/atoms/button/button";
|
|
15
|
+
import ITChip from "@/components/atoms/chip/chip";
|
|
16
|
+
import ITBadget from "@/components/atoms/badget/badget";
|
|
17
|
+
import ITAvatar from "@/components/atoms/avatar/avatar";
|
|
18
|
+
import ITDivider from "@/components/atoms/divider/divider";
|
|
19
|
+
import ITStack from "@/components/atoms/stack/stack";
|
|
20
|
+
import ITFlex from "@/components/atoms/flex/flex";
|
|
21
|
+
import ITGrid from "@/components/atoms/grid/grid";
|
|
22
|
+
import ITLoader from "@/components/atoms/loader/loader";
|
|
23
|
+
import ITSkeleton from "@/components/atoms/skeleton/skeleton";
|
|
24
|
+
import ITProgress from "@/components/atoms/progress/progress";
|
|
25
|
+
import ITImage from "@/components/atoms/image/image";
|
|
26
|
+
import ITCard from "@/components/molecules/card/card";
|
|
27
|
+
import ITAlert from "@/components/molecules/alert/alert";
|
|
28
|
+
import ITEmptyState from "@/components/molecules/empty-state/empty-state";
|
|
29
|
+
import ITStatCard from "@/components/molecules/stat-card/stat-card";
|
|
30
|
+
import ITDataTable from "@/components/organisms/data-table/dataTable";
|
|
31
|
+
import { createCatalog, type CatalogEntry } from "./catalog.props";
|
|
32
|
+
|
|
33
|
+
/** Colores semánticos que acepta el sistema. */
|
|
34
|
+
const COLORS = ["primary", "secondary", "success", "danger", "warning", "info", "purple", "error", "gray"] as const;
|
|
35
|
+
/** Tamaños del sistema. */
|
|
36
|
+
const SIZES = ["sm", "md", "lg"] as const;
|
|
37
|
+
/** Direcciones de layout. */
|
|
38
|
+
const DIRECTIONS = ["row", "column"] as const;
|
|
39
|
+
/** Alineaciones flexibles. */
|
|
40
|
+
const ALIGNS = ["start", "center", "end", "stretch", "baseline"] as const;
|
|
41
|
+
const JUSTIFIES = ["start", "center", "end", "between", "around", "evenly"] as const;
|
|
42
|
+
const WRAPS = ["nowrap", "wrap", "wrap-reverse"] as const;
|
|
43
|
+
|
|
44
|
+
/** Props comunes de texto que casi todos los componentes aceptan. */
|
|
45
|
+
const TEXT_PROPS = {
|
|
46
|
+
className: { kind: "string" as const, doc: "Clases extra del componente." },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Entradas del catálogo por defecto. */
|
|
50
|
+
export const AXZY_CATALOG_ENTRIES: CatalogEntry[] = [
|
|
51
|
+
{
|
|
52
|
+
type: "ITText",
|
|
53
|
+
component: ITText,
|
|
54
|
+
children: true,
|
|
55
|
+
text: true,
|
|
56
|
+
summary: "Texto semántico. Usa `text` o `children` para el contenido.",
|
|
57
|
+
props: {
|
|
58
|
+
as: { kind: "enum", values: ["span", "p", "h1", "h2", "h3", "h4", "label", "div", "strong", "em"], default: "span", doc: "Etiqueta HTML semántica." },
|
|
59
|
+
muted: { kind: "boolean", default: false, doc: "Atenúa el color del texto." },
|
|
60
|
+
...TEXT_PROPS,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: "ITButton",
|
|
65
|
+
component: ITButton,
|
|
66
|
+
children: true,
|
|
67
|
+
text: true,
|
|
68
|
+
summary: "Botón. `onClick` es una acción registrada por el host.",
|
|
69
|
+
props: {
|
|
70
|
+
label: { kind: "string", doc: "Texto del botón (alternativa a `text`)." },
|
|
71
|
+
color: { kind: "enum", values: COLORS, default: "primary" },
|
|
72
|
+
size: { kind: "enum", values: SIZES, default: "md" },
|
|
73
|
+
variant: {
|
|
74
|
+
kind: "enum",
|
|
75
|
+
values: ["filled", "outlined", "raised", "rounded", "text", "raised-text", "icon-only", "link"],
|
|
76
|
+
default: "filled",
|
|
77
|
+
},
|
|
78
|
+
disabled: { kind: "boolean", default: false },
|
|
79
|
+
type: { kind: "enum", values: ["button", "submit", "reset"], default: "button" },
|
|
80
|
+
ariaLabel: { kind: "string", doc: "Etiqueta accesible cuando el botón no lleva texto." },
|
|
81
|
+
title: { kind: "string" },
|
|
82
|
+
onClick: { kind: "action", doc: "Nombre de una acción registrada." },
|
|
83
|
+
...TEXT_PROPS,
|
|
84
|
+
},
|
|
85
|
+
forbidden: ["icon"],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "ITCard",
|
|
89
|
+
component: ITCard,
|
|
90
|
+
children: true,
|
|
91
|
+
summary: "Tarjeta con título, imagen y contenido.",
|
|
92
|
+
props: {
|
|
93
|
+
title: { kind: "string" },
|
|
94
|
+
image: { kind: "string", doc: "URL de la imagen de cabecera." },
|
|
95
|
+
alt: { kind: "string" },
|
|
96
|
+
onClick: { kind: "action" },
|
|
97
|
+
imageClassName: { kind: "string" },
|
|
98
|
+
titleClassName: { kind: "string" },
|
|
99
|
+
contentClassName: { kind: "string" },
|
|
100
|
+
actionClassName: { kind: "string" },
|
|
101
|
+
...TEXT_PROPS,
|
|
102
|
+
},
|
|
103
|
+
forbidden: ["actions"],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: "ITAlert",
|
|
107
|
+
component: ITAlert,
|
|
108
|
+
children: true,
|
|
109
|
+
text: true,
|
|
110
|
+
summary: "Aviso con variante semántica.",
|
|
111
|
+
props: {
|
|
112
|
+
variant: { kind: "enum", values: ["info", "success", "warning", "error"], default: "info" },
|
|
113
|
+
title: { kind: "string" },
|
|
114
|
+
dismissible: { kind: "boolean", default: false },
|
|
115
|
+
onDismiss: { kind: "action" },
|
|
116
|
+
...TEXT_PROPS,
|
|
117
|
+
},
|
|
118
|
+
forbidden: ["icon"],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: "ITEmptyState",
|
|
122
|
+
component: ITEmptyState,
|
|
123
|
+
text: true,
|
|
124
|
+
summary: "Estado vacío con título, descripción y acción.",
|
|
125
|
+
props: {
|
|
126
|
+
title: { kind: "string", required: true },
|
|
127
|
+
description: { kind: "string" },
|
|
128
|
+
...TEXT_PROPS,
|
|
129
|
+
},
|
|
130
|
+
forbidden: ["icon", "action"],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: "ITStatCard",
|
|
134
|
+
component: ITStatCard,
|
|
135
|
+
summary: "Métrica con valor y tendencia.",
|
|
136
|
+
props: {
|
|
137
|
+
label: { kind: "string", required: true },
|
|
138
|
+
value: { kind: "string", required: true, doc: "Valor ya formateado o número." },
|
|
139
|
+
trend: { kind: "string" },
|
|
140
|
+
trendDirection: { kind: "enum", values: ["up", "down", "neutral"] },
|
|
141
|
+
color: { kind: "string", doc: "Color CSS del icono." },
|
|
142
|
+
onClick: { kind: "action" },
|
|
143
|
+
...TEXT_PROPS,
|
|
144
|
+
},
|
|
145
|
+
forbidden: ["icon"],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: "ITChip",
|
|
149
|
+
component: ITChip,
|
|
150
|
+
children: true,
|
|
151
|
+
text: true,
|
|
152
|
+
summary: "Etiqueta compacta, opcionalmente removible o seleccionable.",
|
|
153
|
+
props: {
|
|
154
|
+
label: { kind: "string" },
|
|
155
|
+
color: { kind: "enum", values: COLORS, default: "gray" },
|
|
156
|
+
size: { kind: "enum", values: SIZES, default: "md" },
|
|
157
|
+
variant: { kind: "enum", values: ["soft", "filled", "outlined"], default: "soft" },
|
|
158
|
+
removable: { kind: "boolean", default: false },
|
|
159
|
+
selected: { kind: "boolean", default: false },
|
|
160
|
+
disabled: { kind: "boolean", default: false },
|
|
161
|
+
onRemove: { kind: "action" },
|
|
162
|
+
onClick: { kind: "action" },
|
|
163
|
+
...TEXT_PROPS,
|
|
164
|
+
},
|
|
165
|
+
forbidden: ["icon"],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
type: "ITBadget",
|
|
169
|
+
component: ITBadget,
|
|
170
|
+
children: true,
|
|
171
|
+
text: true,
|
|
172
|
+
summary: "Insignia de estado.",
|
|
173
|
+
props: {
|
|
174
|
+
label: { kind: "string" },
|
|
175
|
+
color: { kind: "enum", values: COLORS, default: "primary" },
|
|
176
|
+
size: { kind: "enum", values: SIZES, default: "md" },
|
|
177
|
+
variant: { kind: "enum", values: ["soft", "filled", "outlined"], default: "soft" },
|
|
178
|
+
...TEXT_PROPS,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: "ITAvatar",
|
|
183
|
+
component: ITAvatar,
|
|
184
|
+
summary: "Avatar con imagen o iniciales.",
|
|
185
|
+
props: {
|
|
186
|
+
src: { kind: "string" },
|
|
187
|
+
alt: { kind: "string" },
|
|
188
|
+
initials: { kind: "string", doc: "Se usa cuando no hay imagen." },
|
|
189
|
+
size: { kind: "enum", values: ["xs", "sm", "md", "lg", "xl"] },
|
|
190
|
+
color: { kind: "string" },
|
|
191
|
+
onClick: { kind: "action" },
|
|
192
|
+
...TEXT_PROPS,
|
|
193
|
+
},
|
|
194
|
+
forbidden: ["badge"],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
type: "ITDivider",
|
|
198
|
+
component: ITDivider,
|
|
199
|
+
summary: "Separador horizontal o vertical.",
|
|
200
|
+
props: {
|
|
201
|
+
orientation: { kind: "enum", values: ["horizontal", "vertical"], default: "horizontal" },
|
|
202
|
+
color: { kind: "string" },
|
|
203
|
+
thickness: { kind: "string" },
|
|
204
|
+
...TEXT_PROPS,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: "ITStack",
|
|
209
|
+
component: ITStack,
|
|
210
|
+
children: true,
|
|
211
|
+
summary: "Pila con dirección y separación uniforme.",
|
|
212
|
+
props: {
|
|
213
|
+
direction: { kind: "enum", values: DIRECTIONS, default: "column" },
|
|
214
|
+
spacing: { kind: "number", default: 0 },
|
|
215
|
+
alignItems: { kind: "enum", values: ALIGNS },
|
|
216
|
+
justifyContent: { kind: "enum", values: JUSTIFIES },
|
|
217
|
+
flexWrap: { kind: "enum", values: WRAPS },
|
|
218
|
+
...TEXT_PROPS,
|
|
219
|
+
},
|
|
220
|
+
forbidden: ["divider", "style"],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
type: "ITFlex",
|
|
224
|
+
component: ITFlex,
|
|
225
|
+
children: true,
|
|
226
|
+
summary: "Contenedor flex completo.",
|
|
227
|
+
props: {
|
|
228
|
+
direction: { kind: "enum", values: DIRECTIONS, default: "row" },
|
|
229
|
+
align: { kind: "enum", values: ALIGNS },
|
|
230
|
+
justify: { kind: "enum", values: JUSTIFIES },
|
|
231
|
+
wrap: { kind: "enum", values: WRAPS },
|
|
232
|
+
gap: { kind: "number", default: 0 },
|
|
233
|
+
grow: { kind: "boolean" },
|
|
234
|
+
shrink: { kind: "boolean" },
|
|
235
|
+
basis: { kind: "string" },
|
|
236
|
+
...TEXT_PROPS,
|
|
237
|
+
},
|
|
238
|
+
forbidden: ["style", "onClick"],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
type: "ITGrid",
|
|
242
|
+
component: ITGrid,
|
|
243
|
+
children: true,
|
|
244
|
+
summary: "Rejilla de 12 columnas; `item` para una celda.",
|
|
245
|
+
props: {
|
|
246
|
+
container: { kind: "boolean", default: false },
|
|
247
|
+
item: { kind: "boolean", default: false },
|
|
248
|
+
spacing: { kind: "number", default: 0 },
|
|
249
|
+
columns: { kind: "integer", default: 12, doc: "Columnas del contenedor." },
|
|
250
|
+
xs: { kind: "integer" },
|
|
251
|
+
sm: { kind: "integer" },
|
|
252
|
+
md: { kind: "integer" },
|
|
253
|
+
lg: { kind: "integer" },
|
|
254
|
+
xl: { kind: "integer" },
|
|
255
|
+
...TEXT_PROPS,
|
|
256
|
+
},
|
|
257
|
+
forbidden: ["style"],
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
type: "ITLoader",
|
|
261
|
+
component: ITLoader,
|
|
262
|
+
summary: "Indicador de carga.",
|
|
263
|
+
props: {
|
|
264
|
+
variant: { kind: "enum", values: ["spinner", "dots", "bar", "pulse"], default: "spinner" },
|
|
265
|
+
size: { kind: "enum", values: ["xs", "sm", "md", "lg", "xl"], default: "md" },
|
|
266
|
+
color: { kind: "string" },
|
|
267
|
+
...TEXT_PROPS,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: "ITSkeleton",
|
|
272
|
+
component: ITSkeleton,
|
|
273
|
+
summary: "Marcador de carga con forma.",
|
|
274
|
+
props: {
|
|
275
|
+
variant: { kind: "enum", values: ["text", "rect", "circle"], default: "text" },
|
|
276
|
+
width: { kind: "string" },
|
|
277
|
+
height: { kind: "string" },
|
|
278
|
+
count: { kind: "integer", default: 1 },
|
|
279
|
+
...TEXT_PROPS,
|
|
280
|
+
},
|
|
281
|
+
forbidden: ["style"],
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
type: "ITProgress",
|
|
285
|
+
component: ITProgress,
|
|
286
|
+
summary: "Barra de progreso.",
|
|
287
|
+
props: {
|
|
288
|
+
value: { kind: "number", default: 0 },
|
|
289
|
+
max: { kind: "number", default: 100 },
|
|
290
|
+
variant: { kind: "enum", values: ["determinate", "indeterminate"], default: "determinate" },
|
|
291
|
+
color: { kind: "enum", values: COLORS, default: "primary" },
|
|
292
|
+
size: { kind: "enum", values: SIZES, default: "md" },
|
|
293
|
+
...TEXT_PROPS,
|
|
294
|
+
},
|
|
295
|
+
forbidden: ["style"],
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
type: "ITDataTable",
|
|
299
|
+
component: ITDataTable,
|
|
300
|
+
summary:
|
|
301
|
+
"Tabla con paginación, orden y filtros. Los datos llegan de una fuente que el host resuelve (`$source`).",
|
|
302
|
+
props: {
|
|
303
|
+
columns: {
|
|
304
|
+
kind: "columns",
|
|
305
|
+
required: true,
|
|
306
|
+
doc: "Columnas: { key, label, type, sortable, filter, currencyMX }.",
|
|
307
|
+
},
|
|
308
|
+
fetchData: {
|
|
309
|
+
kind: "data",
|
|
310
|
+
required: true,
|
|
311
|
+
doc: "Referencia a una fuente: { \"$source\": \"usuarios\" }. El host la convierte en un fetcher.",
|
|
312
|
+
},
|
|
313
|
+
title: { kind: "string", doc: "Título opcional sobre la tabla." },
|
|
314
|
+
defaultItemsPerPage: { kind: "integer", default: 5, doc: "Filas por página al abrir." },
|
|
315
|
+
itemsPerPageOptions: { kind: "array", doc: "Opciones del paginador, p. ej. [5, 10, 20]." },
|
|
316
|
+
debounceMs: { kind: "integer", default: 400, doc: "Espera antes de refiltrar al teclear." },
|
|
317
|
+
variant: { kind: "enum", values: ["default", "striped", "bordered", "minimal"], default: "default" },
|
|
318
|
+
size: { kind: "enum", values: SIZES, default: "md" },
|
|
319
|
+
density: { kind: "enum", values: ["compact", "normal", "comfortable"], default: "normal" },
|
|
320
|
+
layout: { kind: "enum", values: ["auto", "fixed"], default: "auto" },
|
|
321
|
+
showVerticalBorder: { kind: "boolean", default: true },
|
|
322
|
+
autoCardBreakpoint: { kind: "integer", default: 0, doc: "Ancho (px) por debajo del cual cae a tarjetas." },
|
|
323
|
+
onRowClick: { kind: "action", doc: "Nombre de una acción registrada; recibe la fila." },
|
|
324
|
+
className: { kind: "string" },
|
|
325
|
+
containerClassName: { kind: "string" },
|
|
326
|
+
},
|
|
327
|
+
forbidden: ["renderCard", "loadingIndicator"],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: "ITImage",
|
|
331
|
+
component: ITImage,
|
|
332
|
+
summary: "Imagen con respaldo si falla la carga.",
|
|
333
|
+
props: {
|
|
334
|
+
src: { kind: "string", required: true },
|
|
335
|
+
alt: { kind: "string" },
|
|
336
|
+
fallback: { kind: "string", doc: "URL de respaldo." },
|
|
337
|
+
onClick: { kind: "action" },
|
|
338
|
+
...TEXT_PROPS,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
/** Tipos excluidos a propósito, con el motivo que se muestra en el aviso. */
|
|
344
|
+
export const AXZY_EXCLUDED_TYPES: Record<string, string> = {
|
|
345
|
+
ITJsonRenderer: "Recursión: un esquema no puede pedir otro renderer.",
|
|
346
|
+
ITSchemaRenderer: "Recursión: un esquema no puede pedir otro renderer.",
|
|
347
|
+
ITJSONEditor: "Ejecuta entrada del usuario como código.",
|
|
348
|
+
ITFormBuilder: "Se sustituye por el nodo `form` del motor.",
|
|
349
|
+
ITWysiwyg: "Produce HTML, que se salta la lista blanca.",
|
|
350
|
+
ITI18nProvider: "Lo monta el host, no el esquema.",
|
|
351
|
+
ITThemeProvider: "Lo monta el host, no el esquema.",
|
|
352
|
+
ITLayout: "El shell lo declara el router, no un nodo suelto.",
|
|
353
|
+
ITNavbar: "El shell lo declara el router, no un nodo suelto.",
|
|
354
|
+
ITSidebar: "El shell lo declara el router, no un nodo suelto.",
|
|
355
|
+
ITTopBar: "El shell lo declara el router, no un nodo suelto.",
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/** Catálogo AXZY por defecto. */
|
|
359
|
+
export const axzyCatalog = createCatalog(AXZY_CATALOG_ENTRIES, AXZY_EXCLUDED_TYPES);
|
|
360
|
+
|
|
361
|
+
/** Tipos disponibles en esta fase. */
|
|
362
|
+
export const axzyCatalogTypes = Object.keys(axzyCatalog.entries);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Catálogo del motor: qué componentes y qué props están permitidos.
|
|
5
|
+
*
|
|
6
|
+
* Es una **lista blanca**. Todo lo que no esté aquí declarado se ignora y se
|
|
7
|
+
* avisa (`JR010` / `JR011`), porque el esquema se trata siempre como entrada no
|
|
8
|
+
* confiable: puede venir de un backend con un bug o de un modelo que alucina.
|
|
9
|
+
*
|
|
10
|
+
* Cada prop declara su tipo, si es obligatoria, sus valores admitidos y si su
|
|
11
|
+
* valor es una acción registrada por el host (`kind: "action"`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Tipo de un valor admitido en una prop. */
|
|
15
|
+
export type PropKind =
|
|
16
|
+
| "string"
|
|
17
|
+
| "number"
|
|
18
|
+
| "integer"
|
|
19
|
+
| "boolean"
|
|
20
|
+
| "enum"
|
|
21
|
+
| "node"
|
|
22
|
+
| "nodes"
|
|
23
|
+
| "data"
|
|
24
|
+
| "columns"
|
|
25
|
+
| "action"
|
|
26
|
+
| "i18n"
|
|
27
|
+
| "object"
|
|
28
|
+
| "array";
|
|
29
|
+
|
|
30
|
+
/** Especificación de una prop dentro del catálogo. */
|
|
31
|
+
export interface PropSpec {
|
|
32
|
+
/** Tipo admitido. */
|
|
33
|
+
kind: PropKind;
|
|
34
|
+
/** `true` cuando el componente no funciona sin ella. */
|
|
35
|
+
required?: boolean;
|
|
36
|
+
/** Valores admitidos cuando `kind: "enum"`. */
|
|
37
|
+
values?: readonly string[];
|
|
38
|
+
/** Valor por defecto que aplica el motor. */
|
|
39
|
+
default?: unknown;
|
|
40
|
+
/** Descripción para el autor del esquema y para el contrato del emisor. */
|
|
41
|
+
doc?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Entrada de un componente en el catálogo. */
|
|
45
|
+
export interface CatalogEntry {
|
|
46
|
+
/** Nombre del componente tal cual (`ITCard`). */
|
|
47
|
+
type: string;
|
|
48
|
+
/**
|
|
49
|
+
* Componente React que se monta.
|
|
50
|
+
*
|
|
51
|
+
* El tipo es deliberadamente laxo (`any`): las props se validan y coaccionan
|
|
52
|
+
* en runtime contra `props`, así que exigir el tipo exacto de cada componente
|
|
53
|
+
* solo serviría para obligar a un cast por entrada.
|
|
54
|
+
*/
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
component: React.ComponentType<any>;
|
|
57
|
+
/** Props permitidas. Todo lo demás se descarta con aviso. */
|
|
58
|
+
props?: Record<string, PropSpec>;
|
|
59
|
+
/** `true` cuando el nodo admite `children`. @default false */
|
|
60
|
+
children?: boolean;
|
|
61
|
+
/** `true` cuando el nodo admite el atajo `text`. @default false */
|
|
62
|
+
text?: boolean;
|
|
63
|
+
/** Props prohibidas explícitamente (para mensajes más claros). */
|
|
64
|
+
forbidden?: readonly string[];
|
|
65
|
+
/** Descripción breve para el contrato de vocabulario. */
|
|
66
|
+
summary?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Catálogo completo. */
|
|
70
|
+
export interface Catalog {
|
|
71
|
+
/** Entradas indexadas por `type`. */
|
|
72
|
+
entries: Record<string, CatalogEntry>;
|
|
73
|
+
/** Tipos excluidos a propósito, con su motivo (se avisa distinto). */
|
|
74
|
+
excluded?: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Props prohibidas en **cualquier** componente, por seguridad.
|
|
79
|
+
*
|
|
80
|
+
* Solo entran las que inyectan nodos React, HTML o comportamiento: los valores
|
|
81
|
+
* enumerados y seguros (`as`, `className`…) se declaran componente a componente.
|
|
82
|
+
*/
|
|
83
|
+
export const GLOBALLY_FORBIDDEN_PROPS: readonly string[] = [
|
|
84
|
+
"dangerouslySetInnerHTML",
|
|
85
|
+
"render",
|
|
86
|
+
"component",
|
|
87
|
+
"children", // los hijos se declaran como nodos, nunca como prop
|
|
88
|
+
"ref",
|
|
89
|
+
"key",
|
|
90
|
+
"style", // se permite solo si el catálogo lo declara explícitamente
|
|
91
|
+
"icon",
|
|
92
|
+
"actions",
|
|
93
|
+
"action",
|
|
94
|
+
"emptyAction",
|
|
95
|
+
"fallback",
|
|
96
|
+
"logo",
|
|
97
|
+
"badge",
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
/** Crea un catálogo a partir de una lista de entradas. */
|
|
101
|
+
export function createCatalog(
|
|
102
|
+
entries: CatalogEntry[],
|
|
103
|
+
excluded: Record<string, string> = {}
|
|
104
|
+
): Catalog {
|
|
105
|
+
const index: Record<string, CatalogEntry> = {};
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (index[entry.type]) {
|
|
108
|
+
throw new Error(`Catálogo duplicado: ${entry.type}`);
|
|
109
|
+
}
|
|
110
|
+
index[entry.type] = entry;
|
|
111
|
+
}
|
|
112
|
+
return { entries: index, excluded };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Devuelve la entrada de un tipo, o `undefined` si no está en el catálogo. */
|
|
116
|
+
export function getCatalogEntry(catalog: Catalog, type: string): CatalogEntry | undefined {
|
|
117
|
+
return catalog.entries[type];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** `true` cuando el tipo está excluido a propósito (recursión, ejecución de entrada…). */
|
|
121
|
+
export function isExcludedType(catalog: Catalog, type: string): boolean {
|
|
122
|
+
return Boolean(catalog.excluded && type in catalog.excluded);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Motivo por el que un tipo está excluido. */
|
|
126
|
+
export function getExclusionReason(catalog: Catalog, type: string): string | undefined {
|
|
127
|
+
return catalog.excluded?.[type];
|
|
128
|
+
}
|