@axzydev/axzy_ui_system 1.6.0 → 1.7.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 (34) hide show
  1. package/dist/index.cjs +6053 -4317
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +1 -1
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +145 -1
  6. package/dist/index.d.ts +145 -1
  7. package/dist/index.js +4363 -2612
  8. package/dist/index.js.map +1 -1
  9. package/dist/layered.css +1 -1
  10. package/package.json +1 -1
  11. package/src/App.tsx +10 -0
  12. package/src/components/molecules/json-editor/json-editor-tree.tsx +235 -0
  13. package/src/components/molecules/json-editor/json-editor.index.ts +2 -0
  14. package/src/components/molecules/json-editor/json-editor.props.ts +82 -0
  15. package/src/components/molecules/json-editor/json-editor.tsx +972 -0
  16. package/src/components/molecules/json-editor/json-editor.utils.ts +717 -0
  17. package/src/components/molecules/json-editor/useJsonEditorKeyboard.ts +256 -0
  18. package/src/components/molecules/json-renderer/json-renderer.index.ts +2 -0
  19. package/src/components/molecules/json-renderer/json-renderer.props.ts +31 -0
  20. package/src/components/molecules/json-renderer/json-renderer.tsx +79 -0
  21. package/src/dev.css +24 -1
  22. package/src/hooks/useJsonRender.tsx +9 -0
  23. package/src/i18n/locales/en.ts +54 -0
  24. package/src/i18n/locales/es.ts +54 -0
  25. package/src/index.css +414 -0
  26. package/src/index.ts +10 -2
  27. package/src/sandbox/AxzyLogo.tsx +139 -0
  28. package/src/sandbox/i18n.ts +2 -0
  29. package/src/sandbox/navigation.ts +2 -0
  30. package/src/showcases/FeedbackShowcases.tsx +16 -5
  31. package/src/showcases/HomeShowcase.tsx +16 -9
  32. package/src/showcases/JsonEditorShowcase.tsx +150 -0
  33. package/src/showcases/JsonRendererShowcase.tsx +70 -0
  34. package/src/showcases/StructureShowcases.tsx +6 -1
@@ -0,0 +1,256 @@
1
+ import { useCallback, type KeyboardEvent, type RefObject } from "react";
2
+
3
+ /** Resultado de una operación de edición sobre el textarea. */
4
+ interface EditResult {
5
+ /** Texto completo resultante. */
6
+ value: string;
7
+ /** Inicio de la selección final. */
8
+ selectionStart: number;
9
+ /** Fin de la selección final. */
10
+ selectionEnd: number;
11
+ /** Si el manejador consumió el evento (hay que hacer `preventDefault`). */
12
+ handled: boolean;
13
+ }
14
+
15
+ /**
16
+ * Ajustes del hook de edición.
17
+ *
18
+ * No recibe el `value` por props a propósito: lee el documento del propio
19
+ * `<textarea>` en cada pulsación. Entre dos teclas rápidas React todavía puede
20
+ * no haber re-renderizado, y calcular la edición sobre el valor de props pierde
21
+ * o duplica caracteres. `applyEdit` se encarga de notificar y de reponer el
22
+ * cursor tras el commit.
23
+ */
24
+ export interface UseJsonEditorKeyboardOptions {
25
+ /** Referencia al textarea real. */
26
+ textareaRef: RefObject<HTMLTextAreaElement | null>;
27
+ /** Espacios por nivel de indentación. */
28
+ indentSize: number;
29
+ /** `true` cuando el editor es de solo lectura (no se intercepta nada). */
30
+ readOnly?: boolean;
31
+ /** Aplica una edición al textarea conservando el deshacer nativo. */
32
+ applyEdit: (result: EditResult) => void;
33
+ }
34
+
35
+ /** Caracteres que abren bloque y su pareja de cierre. */
36
+ const PAIRS: Record<string, string> = { "{": "}", "[": "]", '"': '"' };
37
+
38
+ /** Caracteres de cierre que se "saltan" al teclear si ya están delante. */
39
+ const CLOSERS = new Set(["}", "]", '"']);
40
+
41
+ /**
42
+ * Comportamiento de editor de código sobre un `<textarea>` nativo: indentación
43
+ * con Tab, continuación de indentación al pulsar Enter, cierre automático de
44
+ * llaves/corchetes/comillas y borrado por parejas.
45
+ *
46
+ * Se implementa como hook para que `ITJSONEditor` siga siendo un componente de
47
+ * presentación y el textarea conserve el deshacer del navegador.
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * const textareaRef = useRef<HTMLTextAreaElement>(null);
52
+ * const handleKeyDown = useJsonEditorKeyboard({ textareaRef, indentSize: 2, applyEdit });
53
+ * <textarea ref={textareaRef} onKeyDown={handleKeyDown} />
54
+ * ```
55
+ */
56
+ export function useJsonEditorKeyboard({
57
+ textareaRef,
58
+ indentSize,
59
+ readOnly = false,
60
+ applyEdit,
61
+ }: UseJsonEditorKeyboardOptions) {
62
+ return useCallback(
63
+ (event: KeyboardEvent<HTMLTextAreaElement>) => {
64
+ const textarea = textareaRef.current;
65
+ if (!textarea || readOnly) return;
66
+
67
+ const current = textarea.value;
68
+ const { selectionStart, selectionEnd } = textarea;
69
+ const indent = " ".repeat(indentSize);
70
+ const before = current.slice(0, selectionStart);
71
+ const after = current.slice(selectionEnd);
72
+
73
+ /** Sustituye la selección por `text` y deja el cursor al final. */
74
+ const replace = (text: string, caretOffset = text.length): EditResult => {
75
+ const next = current.slice(0, selectionStart) + text + after;
76
+ const caret = selectionStart + caretOffset;
77
+ return { value: next, selectionStart: caret, selectionEnd: caret, handled: true };
78
+ };
79
+
80
+ /* ------------------------------------------------------------ Tab */
81
+ if (event.key === "Tab") {
82
+ event.preventDefault();
83
+ const lineStart = current.lastIndexOf("\n", selectionStart - 1) + 1;
84
+ const lineEndRaw = current.indexOf("\n", selectionEnd);
85
+ const lineEnd = lineEndRaw === -1 ? current.length : lineEndRaw;
86
+ const multiline = current.slice(lineStart, lineEnd).includes("\n");
87
+
88
+ if (!multiline && !event.shiftKey && selectionStart === selectionEnd) {
89
+ // Sangrado simple en el cursor.
90
+ applyEdit(replace(indent));
91
+ return;
92
+ }
93
+
94
+ // Sangrado/desangrado por bloques: afecta a todas las líneas tocadas.
95
+ const block = current.slice(lineStart, lineEnd);
96
+ const lines = block.split("\n");
97
+ /** Cuánto se ha desplazado cada línea (positivo al indentar). */
98
+ const shifts: number[] = [];
99
+ const shifted = lines
100
+ .map((line) => {
101
+ if (event.shiftKey) {
102
+ if (line.startsWith(indent)) {
103
+ shifts.push(-indent.length);
104
+ return line.slice(indent.length);
105
+ }
106
+ const match = line.match(/^[ \t]/);
107
+ shifts.push(match ? -1 : 0);
108
+ return match ? line.slice(1) : line;
109
+ }
110
+ shifts.push(indent.length);
111
+ return indent + line;
112
+ })
113
+ .join("\n");
114
+
115
+ const next = current.slice(0, lineStart) + shifted + current.slice(lineEnd);
116
+ /**
117
+ * Lleva una posición del bloque original a su nueva posición: suma los
118
+ * desplazamientos de las líneas anteriores y recorta el de la propia
119
+ * línea para no dejar el cursor antes de su inicio.
120
+ */
121
+ const remap = (position: number): number => {
122
+ const relative = Math.max(0, position - lineStart);
123
+ let offset = 0;
124
+ let lineIndex = lines.length - 1;
125
+ for (let i = 0; i < lines.length; i++) {
126
+ const lineLength = lines[i].length + (i < lines.length - 1 ? 1 : 0);
127
+ if (relative <= offset + lineLength) {
128
+ lineIndex = i;
129
+ break;
130
+ }
131
+ offset += lineLength;
132
+ }
133
+ const accumulated = shifts.slice(0, lineIndex).reduce((total, n) => total + n, 0);
134
+ const inLine = relative - offset;
135
+ const shift = shifts[lineIndex] ?? 0;
136
+ return lineStart + accumulated + Math.max(0, inLine + Math.min(shift, inLine));
137
+ };
138
+
139
+ const startNext = remap(selectionStart);
140
+ const endNext = Math.max(startNext, remap(selectionEnd));
141
+ applyEdit({
142
+ value: next,
143
+ selectionStart: Math.min(startNext, next.length),
144
+ selectionEnd: Math.min(endNext, next.length),
145
+ handled: true,
146
+ });
147
+ return;
148
+ }
149
+
150
+ /* ---------------------------------------------------------- Enter */
151
+ if (event.key === "Enter" && !event.shiftKey) {
152
+ event.preventDefault();
153
+ const currentLine = before.slice(before.lastIndexOf("\n") + 1);
154
+ const leading = currentLine.match(/^[ \t]*/)?.[0] ?? "";
155
+ /** `true` cuando el cursor está al final de su línea (nada detrás salvo el cierre). */
156
+ const atLineEnd = /^[ \t]*[}\]]?[ \t]*$/.test(after);
157
+ const nextChar = after[0] ?? "";
158
+ const prevChar = before[before.length - 1] ?? "";
159
+ const opensBlock = prevChar === "{" || prevChar === "[";
160
+ const closesBlock = nextChar === "}" || nextChar === "]";
161
+
162
+ // Entre llaves vacías: se abre un bloque y se deja el cursor centrado.
163
+ if (opensBlock && closesBlock) {
164
+ const inner = `\n${leading}${indent}\n${leading}`;
165
+ applyEdit(replace(inner, 1 + leading.length + indent.length));
166
+ return;
167
+ }
168
+ if (opensBlock) {
169
+ applyEdit(replace(`\n${leading}${indent}`));
170
+ return;
171
+ }
172
+ if (!atLineEnd) {
173
+ // A mitad de línea solo se parte la línea: no se toca el resto, pero la
174
+ // línea nueva sí hereda la sangría del bloque en el que se está.
175
+ applyEdit(replace(`\n${leading}${closesBlock ? indent : ""}`, 1 + leading.length + (closesBlock ? indent.length : 0)));
176
+ return;
177
+ }
178
+ if (closesBlock) {
179
+ applyEdit(replace(`\n${leading}`, 1 + leading.length));
180
+ return;
181
+ }
182
+ applyEdit(replace(`\n${leading}`));
183
+ return;
184
+ }
185
+
186
+ /* --------------------------------------------- Cierre automático */
187
+ if (PAIRS[event.key]) {
188
+ const closing = PAIRS[event.key];
189
+ const selected = current.slice(selectionStart, selectionEnd);
190
+ // Teclear la comilla de cierre sobre una ya existente solo avanza el
191
+ // cursor. La comprobación es contra el MISMO carácter: delante de un `}`
192
+ // o un `]` lo que toca es abrir una cadena, no saltar nada.
193
+ if (event.key === '"' && nextCharOf(after) === '"' && !selected) {
194
+ event.preventDefault();
195
+ applyEdit({ value: current, selectionStart: selectionEnd + 1, selectionEnd: selectionEnd + 1, handled: true });
196
+ return;
197
+ }
198
+ event.preventDefault();
199
+ applyEdit(replace(`${event.key}${selected}${closing}`, 1));
200
+ return;
201
+ }
202
+
203
+ /* ------------------------------------------------ Salto de cierre */
204
+ if (CLOSERS.has(event.key) && selectionStart === selectionEnd && after[0] === event.key) {
205
+ event.preventDefault();
206
+ applyEdit({ value: current, selectionStart: selectionStart + 1, selectionEnd: selectionStart + 1, handled: true });
207
+ return;
208
+ }
209
+
210
+ /* ----------------------------------------------- Borrado de pareja */
211
+ if (event.key === "Backspace" && selectionStart === selectionEnd && selectionStart > 0) {
212
+ const prev = before[before.length - 1];
213
+ const next = after[0];
214
+ const isPair =
215
+ (prev === "{" && next === "}") || (prev === "[" && next === "]") || (prev === '"' && next === '"');
216
+ if (isPair) {
217
+ event.preventDefault();
218
+ const nextValue = current.slice(0, selectionStart - 1) + current.slice(selectionStart + 1);
219
+ applyEdit({
220
+ value: nextValue,
221
+ selectionStart: selectionStart - 1,
222
+ selectionEnd: selectionStart - 1,
223
+ handled: true,
224
+ });
225
+ return;
226
+ }
227
+ }
228
+
229
+ /* ------------------------------------------------------ Atajos */
230
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "a") {
231
+ // Seleccionar todo dentro del editor (no toda la página).
232
+ event.preventDefault();
233
+ textarea.select();
234
+ return;
235
+ }
236
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "d") {
237
+ // Duplicar la línea actual.
238
+ event.preventDefault();
239
+ const lineStart = before.lastIndexOf("\n") + 1;
240
+ const lineEndRaw = current.indexOf("\n", selectionStart);
241
+ const lineEnd = lineEndRaw === -1 ? current.length : lineEndRaw;
242
+ const line = current.slice(lineStart, lineEnd);
243
+ const next = current.slice(0, lineEnd) + "\n" + line + current.slice(lineEnd);
244
+ const caret = selectionStart + line.length + 1;
245
+ applyEdit({ value: next, selectionStart: caret, selectionEnd: caret, handled: true });
246
+ return;
247
+ }
248
+ },
249
+ [applyEdit, indentSize, readOnly, textareaRef]
250
+ );
251
+ }
252
+
253
+ /** Carácter vigente en una cadena, o `""`. */
254
+ function nextCharOf(text: string): string {
255
+ return text[0] ?? "";
256
+ }
@@ -0,0 +1,2 @@
1
+ export { default as ITJsonRenderer } from "./json-renderer";
2
+ export type { ITJsonRendererProps, JsonComponentNode, JsonComponentType } from "./json-renderer.props";
@@ -0,0 +1,31 @@
1
+ export type JsonComponentType =
2
+ | "button"
3
+ | "text"
4
+ | "card"
5
+ | "flex"
6
+ | "stack"
7
+ | "grid"
8
+ | "alert"
9
+ | "div";
10
+
11
+ export interface JsonProps {
12
+ [key: string]: any;
13
+ }
14
+
15
+ export interface JsonComponentNode {
16
+ /** Component type to render */
17
+ type: JsonComponentType;
18
+ /** Props to pass to the component */
19
+ props?: JsonProps;
20
+ /** Children nodes */
21
+ children?: JsonComponentNode[] | React.ReactNode;
22
+ /** Text content (shorthand for text children) */
23
+ text?: string;
24
+ }
25
+
26
+ export interface ITJsonRendererProps {
27
+ /** JSON structure to render */
28
+ json: JsonComponentNode | JsonComponentNode[];
29
+ /** Additional CSS class names */
30
+ className?: string;
31
+ }
@@ -0,0 +1,79 @@
1
+ import React from "react";
2
+ import { ITJsonRendererProps, JsonComponentNode } from "./json-renderer.props";
3
+ import ITButton from "@/components/atoms/button/button";
4
+ import ITText from "@/components/atoms/text/text";
5
+ import ITCard from "@/components/molecules/card/card";
6
+ import ITFlex from "@/components/atoms/flex/flex";
7
+ import ITStack from "@/components/atoms/stack/stack";
8
+ import ITGrid from "@/components/atoms/grid/grid";
9
+ import ITAlert from "@/components/molecules/alert/alert";
10
+ import clsx from "clsx";
11
+
12
+ /**
13
+ * Render UI from JSON schema using AXZY UI System components.
14
+ *
15
+ * @example
16
+ * <ITJsonRenderer json={{ type: "button", props: { label: "Click me" } }} />
17
+ *
18
+ * @example
19
+ * <ITJsonRenderer json={[
20
+ * { type: "alert", props: { variant: "info", title: "Info" }, children: "Hello" },
21
+ * { type: "card", props: { title: "Card" }, children: { type: "text", text: "Content" } }
22
+ * ]} />
23
+ */
24
+ export default function ITJsonRenderer({
25
+ json,
26
+ className,
27
+ }: ITJsonRendererProps) {
28
+ const renderNode = (node: JsonComponentNode | React.ReactNode): React.ReactNode => {
29
+ if (node == null || typeof node === "boolean") {
30
+ return null;
31
+ }
32
+
33
+ if (typeof node === "string" || typeof node === "number") {
34
+ return node;
35
+ }
36
+
37
+ if (React.isValidElement(node)) {
38
+ return node;
39
+ }
40
+
41
+ if (Array.isArray(node)) {
42
+ return node.map((child, idx) => (
43
+ <React.Fragment key={idx}>{renderNode(child as any)}</React.Fragment>
44
+ ));
45
+ }
46
+
47
+ // node is JsonComponentNode
48
+ const { type, props = {}, children, text } = node as JsonComponentNode;
49
+
50
+ const childContent = children !== undefined ? renderNode(children as any) : text;
51
+
52
+ switch (type) {
53
+ case "button":
54
+ return <ITButton {...props}>{childContent}</ITButton>;
55
+ case "text":
56
+ return <ITText {...props}>{childContent}</ITText>;
57
+ case "card":
58
+ return <ITCard {...props}>{childContent}</ITCard>;
59
+ case "flex":
60
+ return <ITFlex {...props}>{childContent}</ITFlex>;
61
+ case "stack":
62
+ return <ITStack {...props}>{childContent}</ITStack>;
63
+ case "grid":
64
+ // ITGrid can be container or item; by default treat as container if has children layout
65
+ return <ITGrid container {...props}>{childContent}</ITGrid>;
66
+ case "alert":
67
+ return <ITAlert {...props}>{childContent}</ITAlert>;
68
+ case "div":
69
+ default:
70
+ return <div {...props}>{childContent}</div>;
71
+ }
72
+ };
73
+
74
+ if (Array.isArray(json)) {
75
+ return <div className={clsx(className)}>{json.map((n, idx) => <React.Fragment key={idx}>{renderNode(n)}</React.Fragment>)}</div>;
76
+ }
77
+
78
+ return <div className={clsx(className)}>{renderNode(json)}</div>;
79
+ }
package/src/dev.css CHANGED
@@ -112,7 +112,30 @@ html {
112
112
  @media (prefers-reduced-motion: reduce) {
113
113
  .it-landing-rise,
114
114
  .it-landing-float,
115
- .it-reveal {
115
+ .it-reveal,
116
+ .it-logo-latido {
116
117
  animation: none;
117
118
  }
118
119
  }
120
+
121
+ /* ──────────────────────────────────────────────
122
+ MARCA AXZYDEV (dev.css)
123
+ El loader del kit es una animación Lottie; en el sitio se usa la versión
124
+ estática del ícono con un latido suave. Solo dev: nunca llega a dist.
125
+ ────────────────────────────────────────────── */
126
+ @keyframes it-logo-latido {
127
+ 0%,
128
+ 100% {
129
+ opacity: 1;
130
+ transform: scale(1);
131
+ }
132
+ 50% {
133
+ opacity: 0.72;
134
+ transform: scale(0.94);
135
+ }
136
+ }
137
+
138
+ .it-logo-latido {
139
+ animation: it-logo-latido 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
140
+ transform-origin: center;
141
+ }
@@ -0,0 +1,9 @@
1
+ import { useMemo } from "react";
2
+ import ITJsonRenderer from "@/components/molecules/json-renderer/json-renderer";
3
+ import { JsonComponentNode } from "@/components/molecules/json-renderer/json-renderer.props";
4
+
5
+ export function useJsonRender(json: JsonComponentNode | JsonComponentNode[]) {
6
+ return useMemo(() => <ITJsonRenderer json={json} />, [json]);
7
+ }
8
+
9
+ export default useJsonRender;
@@ -158,6 +158,60 @@ const en: ITMessages = {
158
158
  "themeProvider.presets.oceanicTeal": "Oceanic Teal 🌊",
159
159
  "themeProvider.presets.goldenHour": "Golden Hour ☀️",
160
160
  "themeProvider.presets.deepForest": "Deep Forest 🌲",
161
+
162
+ // -------------------------------------------------------- ITJSONEditor
163
+ "jsonEditor.ariaLabel": "JSON editor",
164
+ "jsonEditor.placeholder": '{\n "type": "button",\n "props": { "label": "Confirm" }\n}',
165
+ "jsonEditor.valid": "Valid JSON",
166
+ "jsonEditor.invalid": "Invalid JSON",
167
+ "jsonEditor.invalidAt": "Error on line {line}, column {column}",
168
+ "jsonEditor.format": "Format",
169
+ "jsonEditor.formatHint": "Reindents the document (Shift+Alt+F)",
170
+ "jsonEditor.minify": "Minify",
171
+ "jsonEditor.sortAsc": "A-Z",
172
+ "jsonEditor.sortDesc": "Z-A",
173
+ "jsonEditor.escape": "Escape",
174
+ "jsonEditor.unescape": "Unescape",
175
+ "jsonEditor.copy": "Copy",
176
+ "jsonEditor.copied": "Copied",
177
+ "jsonEditor.copyFailed": "Could not copy to the clipboard",
178
+ "jsonEditor.importFile": "Open",
179
+ "jsonEditor.download": "Download",
180
+ "jsonEditor.fileLoaded": "File loaded",
181
+ "jsonEditor.fileError": "The file could not be read",
182
+ "jsonEditor.applied": "Action applied",
183
+ "jsonEditor.actionFailed": "The action could not be applied",
184
+ "jsonEditor.viewMode": "View mode",
185
+ "jsonEditor.modeText": "Text",
186
+ "jsonEditor.modeTree": "Tree",
187
+ "jsonEditor.viewSettings": "View",
188
+ "jsonEditor.wrap": "Wrap",
189
+ "jsonEditor.lineNumbers": "Numbers",
190
+ "jsonEditor.folding": "Folding",
191
+ "jsonEditor.indent": "Indent",
192
+ "jsonEditor.indentNone": "No indent",
193
+ "jsonEditor.collapseAll": "Collapse all",
194
+ "jsonEditor.expandAll": "Expand all",
195
+ "jsonEditor.collapseBlock": "Collapse the block on line {line}",
196
+ "jsonEditor.expandBlock": "Expand the block on line {line}",
197
+ "jsonEditor.foldMore": "… {count} folded lines",
198
+ "jsonEditor.expandNode": "Expand node",
199
+ "jsonEditor.collapseNode": "Collapse node",
200
+ "jsonEditor.jumpToLine": "Go to line {line}",
201
+ "jsonEditor.childCount": "{count} children",
202
+ "jsonEditor.treeNeedsValid": "Fix the JSON to see the tree",
203
+ "jsonEditor.treeTruncated": "Tree truncated: too many nodes, use the text view",
204
+ "jsonEditor.statLines": "{count} lines",
205
+ "jsonEditor.statNodes": "{count} nodes",
206
+ "jsonEditor.statDepth": "depth {count}",
207
+ "jsonEditor.statRoot": "root: {kind}",
208
+ "jsonEditor.statSize": "size: {size}",
209
+ "jsonEditor.kindObject": "object",
210
+ "jsonEditor.kindArray": "array",
211
+ "jsonEditor.kindString": "string",
212
+ "jsonEditor.kindNumber": "number",
213
+ "jsonEditor.kindBoolean": "boolean",
214
+ "jsonEditor.kindNull": "null",
161
215
  };
162
216
 
163
217
  export default en;
@@ -160,6 +160,60 @@ const es: ITMessages = {
160
160
  "themeProvider.presets.oceanicTeal": "Oceanic Teal 🌊",
161
161
  "themeProvider.presets.goldenHour": "Golden Hour ☀️",
162
162
  "themeProvider.presets.deepForest": "Deep Forest 🌲",
163
+
164
+ // -------------------------------------------------------- ITJSONEditor
165
+ "jsonEditor.ariaLabel": "Editor de JSON",
166
+ "jsonEditor.placeholder": '{\n "type": "button",\n "props": { "label": "Aceptar" }\n}',
167
+ "jsonEditor.valid": "JSON válido",
168
+ "jsonEditor.invalid": "JSON no válido",
169
+ "jsonEditor.invalidAt": "Error en línea {line}, columna {column}",
170
+ "jsonEditor.format": "Formatear",
171
+ "jsonEditor.formatHint": "Reindenta el documento (Mayús+Alt+F)",
172
+ "jsonEditor.minify": "Minificar",
173
+ "jsonEditor.sortAsc": "A-Z",
174
+ "jsonEditor.sortDesc": "Z-A",
175
+ "jsonEditor.escape": "Escapar",
176
+ "jsonEditor.unescape": "Desescapar",
177
+ "jsonEditor.copy": "Copiar",
178
+ "jsonEditor.copied": "Copiado",
179
+ "jsonEditor.copyFailed": "No se pudo copiar al portapapeles",
180
+ "jsonEditor.importFile": "Abrir",
181
+ "jsonEditor.download": "Descargar",
182
+ "jsonEditor.fileLoaded": "Archivo cargado",
183
+ "jsonEditor.fileError": "No se pudo leer el archivo",
184
+ "jsonEditor.applied": "Acción aplicada",
185
+ "jsonEditor.actionFailed": "La acción no se pudo aplicar",
186
+ "jsonEditor.viewMode": "Modo de vista",
187
+ "jsonEditor.modeText": "Texto",
188
+ "jsonEditor.modeTree": "Árbol",
189
+ "jsonEditor.viewSettings": "Vista",
190
+ "jsonEditor.wrap": "Ajuste",
191
+ "jsonEditor.lineNumbers": "Números",
192
+ "jsonEditor.folding": "Plegado",
193
+ "jsonEditor.indent": "Indentación",
194
+ "jsonEditor.indentNone": "Sin sangrar",
195
+ "jsonEditor.collapseAll": "Plegar todo",
196
+ "jsonEditor.expandAll": "Desplegar todo",
197
+ "jsonEditor.collapseBlock": "Plegar el bloque de la línea {line}",
198
+ "jsonEditor.expandBlock": "Desplegar el bloque de la línea {line}",
199
+ "jsonEditor.foldMore": "… {count} líneas plegadas",
200
+ "jsonEditor.expandNode": "Desplegar nodo",
201
+ "jsonEditor.collapseNode": "Plegar nodo",
202
+ "jsonEditor.jumpToLine": "Ir a la línea {line}",
203
+ "jsonEditor.childCount": "{count} hijos",
204
+ "jsonEditor.treeNeedsValid": "Corrige el JSON para ver el árbol",
205
+ "jsonEditor.treeTruncated": "Árbol recortado: demasiados nodos, usa la vista de texto",
206
+ "jsonEditor.statLines": "{count} líneas",
207
+ "jsonEditor.statNodes": "{count} nodos",
208
+ "jsonEditor.statDepth": "prof. {count}",
209
+ "jsonEditor.statRoot": "raíz: {kind}",
210
+ "jsonEditor.statSize": "tamaño: {size}",
211
+ "jsonEditor.kindObject": "objeto",
212
+ "jsonEditor.kindArray": "array",
213
+ "jsonEditor.kindString": "texto",
214
+ "jsonEditor.kindNumber": "número",
215
+ "jsonEditor.kindBoolean": "booleano",
216
+ "jsonEditor.kindNull": "nulo",
163
217
  };
164
218
 
165
219
  export default es;