libreria-astro-lefebvre 0.0.50 → 0.0.51

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 (35) hide show
  1. package/README.md +68 -0
  2. package/package.json +12 -4
  3. package/src/carbins/AstroButton.ts +2 -14
  4. package/src/carbins/Contenido_2025_Alcorcon.ts +1 -1
  5. package/src/carbins/Contenido_2025_Malaga.ts +1 -1
  6. package/src/carbins/Contenido_2025_Montevideo.ts +1 -1
  7. package/src/carbins/Formulario_2025_Seul.ts +1 -1
  8. package/src/carbins/Formulario_2025_Teruel.ts +2 -2
  9. package/src/carbins/GeometricShapeCard.ts +1 -1
  10. package/src/carbins/ImageTextSimple.ts +1 -1
  11. package/src/carbins/Imagen_2025_Bogota.ts +1 -1
  12. package/src/carbins/Imagen_2025_Fukushima.ts +1 -1
  13. package/src/carbins/TextImageBackground.ts +1 -1
  14. package/src/carbins/TextImageBlock.ts +1 -1
  15. package/src/carbins/TextImageCard.ts +1 -1
  16. package/src/carbins/TextImageHeader.ts +1 -1
  17. package/src/components/Astro/Contenido_2025_Alcorcon.astro +5 -3
  18. package/src/components/Astro/Contenido_2025_Malaga.astro +4 -2
  19. package/src/components/Astro/Contenido_2025_Montevideo.astro +3 -3
  20. package/src/components/Astro/Formulario_2025_Seul.astro +3 -1
  21. package/src/components/Astro/Formulario_2025_Teruel.astro +5 -2
  22. package/src/components/Astro/GeometricShape.astro +4 -1
  23. package/src/components/Astro/GeometricShapeCard.astro +4 -1
  24. package/src/components/Astro/ImageTextSimple.astro +5 -1
  25. package/src/components/Astro/Imagen_2025_Bogota.astro +30 -16
  26. package/src/components/Astro/Imagen_2025_Fukushima.astro +5 -2
  27. package/src/components/Astro/TextImageBackground.astro +6 -1
  28. package/src/components/Astro/TextImageBlock.astro +5 -1
  29. package/src/components/Astro/TextImageCard.astro +5 -1
  30. package/src/components/Astro/TextImageHeader.astro +5 -1
  31. package/src/components/LimboImage.astro +89 -0
  32. package/src/index.ts +2 -0
  33. package/src/lib/functions.js +171 -0
  34. package/src/limbo/LimboProvider.astro +17 -1
  35. package/src/limbo/init.ts +138 -17
package/README.md CHANGED
@@ -8,6 +8,74 @@ Instrucciones:
8
8
  npm i libreria-astro-lefebvre
9
9
  ```
10
10
 
11
+ ## 🖼️ Imágenes de Limbo
12
+
13
+ La librería incluye utilidades para trabajar con imágenes de Limbo de forma sencilla.
14
+
15
+ ### Opción 1: Componente LimboImage (Recomendado para maquetadores)
16
+
17
+ El componente `<LimboImage>` extrae automáticamente la URL del JSON de Limbo:
18
+
19
+ ```astro
20
+ ---
21
+ import LimboImage from 'libreria-astro-lefebvre/components/LimboImage.astro';
22
+
23
+ const { imagen } = Astro.props; // Puede ser URL directa o JSON de Limbo
24
+ ---
25
+
26
+ <!-- Uso básico -->
27
+ <LimboImage src={imagen} alt="Descripción de la imagen" />
28
+
29
+ <!-- Con estilos -->
30
+ <LimboImage
31
+ src={imagen}
32
+ alt="Hero"
33
+ class="w-full h-auto rounded-lg"
34
+ />
35
+
36
+ <!-- Preferir imagen original en lugar del crop -->
37
+ <LimboImage src={imagen} prefer="original" alt="Original" />
38
+
39
+ <!-- Con fallback si no hay imagen -->
40
+ <LimboImage src={imagen} fallback="/default.jpg" alt="Con fallback" />
41
+ ```
42
+
43
+ ### Opción 2: Función extractImageUrl
44
+
45
+ Para casos donde necesitas más control:
46
+
47
+ ```astro
48
+ ---
49
+ import { extractImageUrl } from 'libreria-astro-lefebvre/lib/functions';
50
+
51
+ const { imagen } = Astro.props;
52
+
53
+ // Extraer URL (preferir crop por defecto)
54
+ const srcUrl = extractImageUrl(imagen);
55
+
56
+ // Preferir imagen original
57
+ const originalUrl = extractImageUrl(imagen, { prefer: 'original' });
58
+ ---
59
+
60
+ <img src={srcUrl} alt="Mi imagen" />
61
+ ```
62
+
63
+ ### Funciones disponibles
64
+
65
+ ```javascript
66
+ import {
67
+ extractImageUrl, // Extrae URL de JSON Limbo
68
+ parseImageData, // Obtiene datos completos (original + crops)
69
+ resolveUrl, // Convierte /files/... a URL absoluta
70
+ isValidImageUrl, // Verifica si URL es válida (no blob)
71
+ LIMBO_BASE_URL // { DEV: '...', PROD: '...' }
72
+ } from 'libreria-astro-lefebvre/lib/functions';
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Instalación y desarrollo
78
+
11
79
 
12
80
  Hacer un link en librería local:
13
81
  ``` npm link ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libreria-astro-lefebvre",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "description": "Librería de componentes Astro, React y Vue para Lefebvre",
5
5
  "author": "Equipo web desarrollo Lefebvre",
6
6
  "type": "module",
@@ -12,7 +12,9 @@
12
12
  },
13
13
  "./list": "./src/list.ts",
14
14
  "./limbo": "./src/limbo/index.ts",
15
- "./limbo/LimboProvider.astro": "./src/limbo/LimboProvider.astro"
15
+ "./limbo/LimboProvider.astro": "./src/limbo/LimboProvider.astro",
16
+ "./components/LimboImage.astro": "./src/components/LimboImage.astro",
17
+ "./lib/functions": "./src/lib/functions.js"
16
18
  },
17
19
  "files": [
18
20
  "src"
@@ -26,12 +28,18 @@
26
28
  ],
27
29
  "peerDependencies": {
28
30
  "astro": "^5.11.0",
29
- "limbo-component": "latest"
31
+ "limbo-component": "latest",
32
+ "vue": "^3.3.0",
33
+ "react": "^18.0.0 || ^19.0.0",
34
+ "react-dom": "^18.0.0 || ^19.0.0"
30
35
  },
31
36
  "scripts": {
32
37
  "generate:registry": "node scripts/generateRegistry.js"
33
38
  },
34
39
  "devDependencies": {
35
- "typescript": "^5.8.3"
40
+ "typescript": "^5.8.3",
41
+ "vue": "^3.5.0",
42
+ "react": "^19.0.0",
43
+ "react-dom": "^19.0.0"
36
44
  }
37
45
  }
@@ -27,20 +27,8 @@ export const metadata: ComponentMetadata = {
27
27
  name: 'imagen',
28
28
  type: 'image',
29
29
  label: 'Imagen de prueba',
30
- mandatory: true,
31
- example_value: '',
32
- image_cuts: [
33
- {
34
- label: "mobile",
35
- width: "300",
36
- height: "100"
37
- },
38
- {
39
- label: "desktop",
40
- width: "400",
41
- height: "400"
42
- },
43
- ]
30
+ mandatory: false,
31
+ example_value: ''
44
32
  },
45
33
  {
46
34
  name: 'target',
@@ -53,7 +53,7 @@ export const metadata: ComponentMetadata = {
53
53
  },
54
54
  {
55
55
  name: 'src',
56
- type: 'text',
56
+ type: 'image',
57
57
  label: 'Src de la imagen',
58
58
  mandatory: false,
59
59
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-w-3-4.png'
@@ -39,7 +39,7 @@ export const metadata: ComponentMetadata = {
39
39
  },
40
40
  {
41
41
  name: 'image',
42
- type: 'text',
42
+ type: 'image',
43
43
  label: 'Src de la imagen',
44
44
  mandatory: false,
45
45
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-w-4-3.png'
@@ -39,7 +39,7 @@ export const metadata: ComponentMetadata = {
39
39
  },
40
40
  {
41
41
  name: 'image',
42
- type: 'text',
42
+ type: 'image',
43
43
  label: 'Src de la imagen',
44
44
  mandatory: false,
45
45
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-16-9.png'
@@ -25,7 +25,7 @@ export const metadata: ComponentMetadata = {
25
25
  },
26
26
  {
27
27
  name: 'imageSrc',
28
- type: 'text',
28
+ type: 'image',
29
29
  label: 'Src de la imagen',
30
30
  mandatory: false,
31
31
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-3-4.png'
@@ -97,7 +97,7 @@ export const metadata: ComponentMetadata = {
97
97
  },
98
98
  {
99
99
  name: 'imageModal',
100
- type: 'text',
100
+ type: 'image',
101
101
  label: 'Src de la imagen modal',
102
102
  mandatory: false,
103
103
  example_value: ''
@@ -118,7 +118,7 @@ export const metadata: ComponentMetadata = {
118
118
  },
119
119
  {
120
120
  name: 'imageSrc',
121
- type: 'text',
121
+ type: 'image',
122
122
  label: 'Src de la imagen',
123
123
  mandatory: false,
124
124
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-16-9.png'
@@ -33,7 +33,7 @@ export const metadata: ComponentMetadata = {
33
33
  },
34
34
  {
35
35
  name: 'imageSrc',
36
- type: 'text',
36
+ type: 'image',
37
37
  label: 'URL de la imagen',
38
38
  mandatory: true,
39
39
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-16-9.png'
@@ -28,7 +28,7 @@ export const metadata: ComponentMetadata = {
28
28
  },
29
29
  {
30
30
  name: 'image',
31
- type: 'text',
31
+ type: 'image',
32
32
  label: 'URL de la imagen',
33
33
  mandatory: true,
34
34
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-1-1.png'
@@ -46,7 +46,7 @@ export const metadata: ComponentMetadata = {
46
46
  },
47
47
  {
48
48
  name: 'src',
49
- type: 'text',
49
+ type: 'image',
50
50
  label: 'Src de la imagen',
51
51
  mandatory: false,
52
52
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-16-9.png'
@@ -11,7 +11,7 @@ export const metadata: ComponentMetadata = {
11
11
  fields: [
12
12
  {
13
13
  name: 'image',
14
- type: 'text',
14
+ type: 'image',
15
15
  label: 'Src de la imagen del logo',
16
16
  mandatory: false,
17
17
  example_value: 'https://lefebvre.es/genia-l/images/genial/img-iso4.webp'
@@ -29,7 +29,7 @@ export const metadata: ComponentMetadata = {
29
29
  },
30
30
  {
31
31
  name: 'image',
32
- type: 'text',
32
+ type: 'image',
33
33
  label: 'URL de la imagen',
34
34
  mandatory: true,
35
35
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-w-16-9.png'
@@ -44,7 +44,7 @@ export const metadata: ComponentMetadata = {
44
44
  },
45
45
  {
46
46
  name: 'image',
47
- type: 'text',
47
+ type: 'image',
48
48
  label: 'URL de la imagen',
49
49
  mandatory: true,
50
50
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-4-3.png'
@@ -26,7 +26,7 @@ export const metadata: ComponentMetadata = {
26
26
  },
27
27
  {
28
28
  name: 'image',
29
- type: 'text',
29
+ type: 'image',
30
30
  label: 'URL de la imagen',
31
31
  mandatory: true,
32
32
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-1-1.png'
@@ -50,7 +50,7 @@ export const metadata: ComponentMetadata = {
50
50
  },
51
51
  {
52
52
  name: 'image',
53
- type: 'text',
53
+ type: 'image',
54
54
  label: 'URL de la imagen',
55
55
  mandatory: true,
56
56
  example_value: 'https://assets.lefebvre.es/media/img/preview-comp/comp-4-3.png'
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  title,
4
6
  subtitle,
@@ -11,7 +13,7 @@ const {
11
13
  showBtn = true
12
14
  } = Astro.props;
13
15
 
14
-
16
+ const srcUrl = extractImageUrl(src);
15
17
  ---
16
18
 
17
19
  <section class="w-full flex items-center justify-center pb-[36px]">
@@ -20,8 +22,8 @@ const {
20
22
 
21
23
  <div class="max-w-7xl w-full flex justify-center items-center relative md:w-4/5">
22
24
  <div class="relative w-full max-w-7xl min-h-[400px] md:min-h-[500px] lg:min-h-[auto]">
23
- {src && src !== "" ? (
24
- <img src={src} alt={alt} title={title} class="object-cover object-center w-full h-full lg:max-h-[446px] min-h-[400px] md:min-h-[500px] lg:min-h-[auto] rounded-2xl" loading="lazy">
25
+ {srcUrl && srcUrl !== "" ? (
26
+ <img src={srcUrl} alt={alt} title={title} class="object-cover object-center w-full h-full lg:max-h-[446px] min-h-[400px] md:min-h-[500px] lg:min-h-[auto] rounded-2xl">
25
27
  ) : (
26
28
  <video autoplay loop muted playsinline class="object-cover object-center w-full h-full lg:max-h-[446px] min-h-[400px] md:min-h-[500px] lg:min-h-[auto] rounded-2xl">
27
29
  <source src={iframeSrc} type="video/mp4" />
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  title,
4
6
  description,
@@ -17,7 +19,7 @@ const {
17
19
  alignItems = 'start',
18
20
  } = Astro.props;
19
21
 
20
-
22
+ const imageUrl = extractImageUrl(image);
21
23
  ---
22
24
 
23
25
  <section class="w-full flex items-center justify-center">
@@ -94,7 +96,7 @@ const {
94
96
  )}
95
97
 
96
98
  <div class={`w-full lg:w-1/2 p-4 flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'} aspect-square`}>
97
- <img src={image} alt={title} class="w-[100%] h-[100%] object-cover rounded-lg" loading="lazy" />
99
+ <img src={imageUrl} alt={title} class="w-[100%] h-[100%] object-cover rounded-lg" />
98
100
  </div>
99
101
  </div>
100
102
  </article>
@@ -1,6 +1,6 @@
1
1
  ---
2
-
3
2
  import Titulo_2025_Santorini from './Titulo_2025_Santorini.astro';
3
+ import { extractImageUrl } from '../../lib/functions.js';
4
4
 
5
5
  const {
6
6
  link='#',
@@ -11,14 +11,14 @@ const {
11
11
  description=''
12
12
  } = Astro.props;
13
13
 
14
-
14
+ const imageUrl = extractImageUrl(image);
15
15
  ---
16
16
 
17
17
  <a href={link} class="w-full flex flex-col cursor-pointer px-4 md:px-0">
18
18
  <article class="w-full h-full flex flex-col rounded-lg border border-gray-200 items-center shadow-article hover:bg-gradient-to-r from-[#001978] via-[#2134F1] to-[#F81BBD] transition-all duration-300 p-[1px]">
19
19
  <div class="bg-white mt-[1px] rounded-tl-[7px] rounded-tr-[7px] h-full">
20
20
  <div class="w-full">
21
- <img loading="lazy" class="cursor-pointer rounded-tr-lg rounded-tl-lg mx-auto h-full" src={image} alt={altImage} />
21
+ <img class="cursor-pointer rounded-tr-lg rounded-tl-lg mx-auto h-full" src={imageUrl} alt={altImage} />
22
22
  </div>
23
23
  <div class="p-6 flex-grow flex flex-col">
24
24
  {tag && tag !== '' && <Titulo_2025_Santorini description={tag} />}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
2
3
 
3
4
  const {
4
5
  title,
@@ -10,6 +11,7 @@ const {
10
11
  businessAction = false
11
12
  } = Astro.props;
12
13
 
14
+ const imageSrcUrl = extractImageUrl(imageSrc);
13
15
  const idTargetLf2 = 'lf2-form-' + Math.random().toString(36).substring(2, 15);
14
16
 
15
17
  const structuredData = `<script type="application/ld+json">
@@ -44,7 +46,7 @@ const structuredData = `<script type="application/ld+json">
44
46
  <div id={idTargetLf2} class="w-full flex flex-col mx-auto gap-3"></div>
45
47
  </div>
46
48
  <div class={`w-full p-0 md:p-4 flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'}`}>
47
- <img src={imageSrc} alt={title} class="w-full md:w-4/5 min-h-auto md:min-h-[650px] object-cover rounded-lg" loading="lazy" />
49
+ <img src={imageSrcUrl} alt={title} class="w-full md:w-4/5 min-h-auto md:min-h-[650px] object-cover rounded-lg" />
48
50
  </div>
49
51
  </div>
50
52
  </article>
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
2
3
 
3
4
  const {
4
5
  bottomBorder = false,
@@ -22,6 +23,8 @@ const {
22
23
 
23
24
  } = Astro.props;
24
25
 
26
+ const imageSrcUrl = extractImageUrl(imageSrc);
27
+ const imageModalUrl = extractImageUrl(imageModal);
25
28
  const randomId = Math.floor(Math.random() * 1000);
26
29
 
27
30
 
@@ -53,7 +56,7 @@ const randomId = Math.floor(Math.random() * 1000);
53
56
  )}
54
57
  </div>
55
58
  <div class={`w-full md:w-1/2 p-4 flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'}`}>
56
- <img src={imageSrc} alt={title} class="w-full md:w-1/2 rounded-2xl" loading="lazy" />
59
+ <img src={imageSrcUrl} alt={title} class="w-full md:w-1/2 rounded-2xl" />
57
60
  </div>
58
61
  </div>
59
62
 
@@ -66,7 +69,7 @@ const randomId = Math.floor(Math.random() * 1000);
66
69
  </div>
67
70
  <div class="w-full h-full flex flex-col lg:flex-row items-start justify-center gap-8">
68
71
  <div class="w-full lg:w-1/3 flex justify-center items-center p-0 lg:p-4 mt-[32px] lg:mt-0">
69
- <img src={imageModal} alt={altModal} class="w-fit" loading="lazy" />
72
+ <img src={imageModalUrl} alt={altModal} class="w-fit" />
70
73
  </div>
71
74
  <div class="w-full lg:w-2/3">
72
75
  <h2 class="font-poppins text-[#262626] text-[28px] lg:text-[32px] font-normal text-left leading-[40px] mb-[32px]">{titleModal}</h2>
@@ -1,5 +1,8 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const { imageSrc, orientation, figure = 'Trapecio', gradiantIndex = 0 } = Astro.props;
5
+ const imageSrcUrl = extractImageUrl(imageSrc);
3
6
  const polygons = [
4
7
  {
5
8
  name: 'Trapeze',
@@ -36,5 +39,5 @@ const selectedPolygon = polygons.find(p => p.name === figure) || polygons[0];
36
39
  ---
37
40
  <div class={`w-[400px] h-[400px] relative ${orientation === 'right' ? '' : '-scale-x-100'}`}>
38
41
  <div class={`absolute bottom-0 right-0 w-full h-full bg-cover mask-no-repeat mask-bottom-right ${gradiants[gradiantIndex]}`} style={`mask-image: url(${selectedPolygon.polygon});`}></div>
39
- <img src={`${imageSrc}`} loading="lazy" class="absolute bottom-0 right-0 max-h-[400px] h-auto z-10 mask-no-repeat mask-bottom-right" style={`mask-image: url(${selectedPolygon.full});`} alt="Figura Geométrica" />
42
+ <img src={`${imageSrcUrl}`} loading="lazy" class="absolute bottom-0 right-0 max-h-[400px] h-auto z-10 mask-no-repeat mask-bottom-right" style={`mask-image: url(${selectedPolygon.full});`} alt="Figura Geométrica" />
40
43
  </div>
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import GeometricShape from './GeometricShape.astro';
3
+ import { extractImageUrl } from '../../lib/functions.js';
3
4
 
4
5
 
5
6
  const {
@@ -12,13 +13,15 @@ const {
12
13
  gradiantIndex = 0
13
14
  } = Astro.props;
14
15
 
16
+ const imageSrcUrl = extractImageUrl(imageSrc);
17
+
15
18
  const randomId = `geo-shape-card-`+ Math.random().toString(36).substring(2, 15);
16
19
 
17
20
  ---
18
21
 
19
22
  <section class={`flex my-8 items-center justify-center relative min-h-[500px]`}>
20
23
  <div class={`absolute top-0 ${orientation === 'right' ? 'right-0' : 'left-0'}`}>
21
- <GeometricShape imageSrc={imageSrc} orientation={orientation} figure={figure} gradiantIndex={gradiantIndex} />
24
+ <GeometricShape imageSrc={imageSrcUrl} orientation={orientation} figure={figure} gradiantIndex={gradiantIndex} />
22
25
  </div>
23
26
  <div class={`flex text-gray-800 flex-col w-3/5 mx-auto ${orientation === 'right' ? 'text-left items-start' : 'text-right items-end'}`}>
24
27
  <h2 class="text-4xl font-bold mb-5 max-w-3xl">{title}</h2>
@@ -1,10 +1,14 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  image,
4
6
  title,
5
7
  video ,
6
8
  description,
7
9
  } = Astro.props;
10
+
11
+ const imageUrl = extractImageUrl(image);
8
12
  ---
9
13
 
10
14
  <!-- Componente que sirve para las páginas soluciones-profesionales y conocenos entre otras. Si se le pasa por parámetro una imagen, la muestra a la izquierda y el texto a la derecha. Si se le pasa un vídeo, muestra el vídeo a la izquierda y el texto a la derecha. -->
@@ -15,7 +19,7 @@ const {
15
19
 
16
20
  {image
17
21
  ? (
18
- <div class="w-full lg:w-1/2 lg:mx-4 mask-add mask-[url(https://assets.lefebvre.es/media/logos-2/svg/lefebvre-iso.svg)] mask-no-repeat mask-center bg-cover" style={`background-image: url('${image}')`} id="rotating-image"></div>
22
+ <div class="w-full lg:w-1/2 lg:mx-4 mask-add mask-[url(https://assets.lefebvre.es/media/logos-2/svg/lefebvre-iso.svg)] mask-no-repeat mask-center bg-cover" style={`background-image: url('${imageUrl}')`} id="rotating-image"></div>
19
23
  )
20
24
  : video
21
25
  ? (
@@ -1,4 +1,12 @@
1
1
  ---
2
+ /**
3
+ * 🎯 Imagen_2025_Bogota
4
+ *
5
+ * Componente de imagen con overlay de texto.
6
+ * Usa LimboImage para simplificar el manejo de imágenes de Limbo.
7
+ */
8
+ import LimboImage from '../LimboImage.astro';
9
+
2
10
  const {
3
11
  title,
4
12
  description,
@@ -10,19 +18,19 @@ const {
10
18
  showBtn = true,
11
19
  showDescription = true,
12
20
  color = '#ffffff',
13
- } = Astro.props;
21
+ } = Astro.props;
14
22
 
15
- const structuredData = `<script type="application/ld+json">
16
- {
17
- "@context": "https://schema.org",
18
- "@type": "ImageObject",
19
- "name": "${title || ''}",
20
- "description": "${description ? description.replace(/<[^>]*>/g, '').replace(/"/g, '\\"') : ''}",
21
- "url": "${src || iframeSrc || ''}",
22
- "contentUrl": "${src || iframeSrc || ''}",
23
- ${src ? `"encodingFormat": "image"` : `"encodingFormat": "video/mp4"`}
24
- }
25
- </script>`;
23
+ const structuredData = `<script type="application/ld+json">
24
+ {
25
+ "@context": "https://schema.org",
26
+ "@type": "ImageObject",
27
+ "name": "${title || ''}",
28
+ "description": "${description ? description.replace(/<[^>]*>/g, '').replace(/"/g, '\\"') : ''}",
29
+ "url": "${src || iframeSrc || ''}",
30
+ "contentUrl": "${src || iframeSrc || ''}",
31
+ ${src ? `"encodingFormat": "image"` : `"encodingFormat": "video/mp4"`}
32
+ }
33
+ </script>`;
26
34
  ---
27
35
 
28
36
  <section class="w-full flex items-center justify-center">
@@ -32,13 +40,19 @@ const {
32
40
  <div class="max-w-full lg:max-w-7xl h-full flex justify-center items-center aspect-auto relative md:w-4/5 lg:aspect-video">
33
41
  <div class="absolute inset-0">
34
42
 
35
- {src && src !== "" ? (
36
- <img src={src} alt={alt} title={title} loading="lazy" class="object-cover object-center w-full h-full rounded-2xl shadow-lg">
37
- ) : (
43
+ {src ? (
44
+ <LimboImage
45
+ src={src}
46
+ alt={alt || title || ''}
47
+ title={title}
48
+ class="object-cover object-center w-full h-full rounded-2xl shadow-lg"
49
+ fallback={iframeSrc}
50
+ />
51
+ ) : iframeSrc ? (
38
52
  <video autoplay loop muted playsinline class="object-cover object-center w-full h-full rounded-2xl shadow-lg">
39
53
  <source src={iframeSrc} type="video/mp4" />
40
54
  </video>
41
- )}
55
+ ) : null}
42
56
  </div>
43
57
 
44
58
  <div class="w-4/5 relative z-10 flex flex-col justify-center items-center h-full text-center p-0 py-8 md:p-6">
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  link = false,
4
6
  image,
@@ -6,6 +8,7 @@ const {
6
8
  descriptionImage
7
9
  } = Astro.props;
8
10
 
11
+ const imageUrl = extractImageUrl(image);
9
12
  ---
10
13
 
11
14
 
@@ -14,12 +17,12 @@ const {
14
17
  <div class="w-full">
15
18
  {(link && link !== '') ? (
16
19
  <a href={link} class="w-full flex flex-col flex-1 justify-center items-center">
17
- <img class="max-w-[80px] w-fit mb-4" src={image} alt={altImage} loading="lazy" />
20
+ <img class="max-w-[80px] w-fit mb-4" src={imageUrl} alt={altImage} />
18
21
  <p class="text-[14px] text-[#363942]" set:html={descriptionImage}></p>
19
22
  </a>
20
23
  ) : (
21
24
  <div class="w-full flex flex-col flex-1 justify-center items-center max-h-[94px]">
22
- <img class="max-w-[80px] w-fit mb-4" src={image} alt={altImage} loading="lazy" />
25
+ <img class="max-w-[80px] w-fit mb-4" src={imageUrl} alt={altImage} />
23
26
  </div>
24
27
  <div class="w-full flex flex-col flex-1 justify-center items-center">
25
28
  <p class="text-[14px] text-[#363942]" set:html={descriptionImage}></p>
@@ -1,13 +1,18 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  title,
4
6
  description,
5
7
  image,
6
8
  orientation = 'left',
7
9
  } = Astro.props;
10
+
11
+ // Extraer URL de la imagen (puede venir como JSON de Limbo o URL directa)
12
+ const imageUrl = extractImageUrl(image);
8
13
  ---
9
14
 
10
- <section class={`w-full flex items-center justify-center bg-white bg-no-repeat ${orientation === 'left' ? 'bg-right' : 'bg-left'} bg-contain `} style={`background-image: url('${image}');`}>
15
+ <section class={`w-full flex items-center justify-center bg-white bg-no-repeat ${orientation === 'left' ? 'bg-right' : 'bg-left'} bg-contain `} style={`background-image: url('${imageUrl}');`}>
11
16
  <article class={`flex ${orientation === 'left' ? 'flex-row' : 'flex-row-reverse'} py-12 w-4/5 center mx-auto `}>
12
17
  <div class="basis-1/2">
13
18
  <h3 class="font-semibold mb-4 text-[2.313em]" style="line-height:47px;" set:html={title}/>
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  title,
4
6
  description,
@@ -7,6 +9,8 @@ const {
7
9
  image,
8
10
  orientation = 'left',
9
11
  } = Astro.props;
12
+
13
+ const imageUrl = extractImageUrl(image);
10
14
  ---
11
15
  <section class="w-full flex items-center justify-center bg-gradient-to-r from-slate-50 to-slate-100 border-t border-b border-gray-300">
12
16
  <article class="w-3/5 flex flex-col items-center justify-center p-12">
@@ -22,7 +26,7 @@ const {
22
26
  </div>
23
27
  </div>
24
28
  <div class={`w-1/2 p-4 flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'}`}>
25
- <img src={image} alt={title} class="w-4/5 h-auto rounded" loading="lazy" />
29
+ <img src={imageUrl} alt={title} class="w-4/5 h-auto rounded" />
26
30
  </div>
27
31
  </div>
28
32
  </article>
@@ -1,10 +1,14 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  title,
4
6
  description,
5
7
  image,
6
8
  orientation = 'left',
7
9
  } = Astro.props;
10
+
11
+ const imageUrl = extractImageUrl(image);
8
12
  ---
9
13
  <article class={`flex ${orientation === 'left' ? 'flex-row' : 'flex-row-reverse'} items-center gap-4 w-full rounded-xl overflow-hidden border border-slate-300 shadow-lg bg-white`}>
10
14
  <div class="w-2/3 p-8 flex flex-col flex-wrap justify-center items-start">
@@ -17,7 +21,7 @@ const {
17
21
  <div class={`w-1/3 aspect-square flex items-center ${orientation === 'left' ? 'justify-end' : 'justify-start'}`}>
18
22
  <div
19
23
  class="w-full h-full bg-center bg-cover"
20
- style={`background-image: url('${image}');`}
24
+ style={`background-image: url('${imageUrl}');`}
21
25
  aria-label={title}
22
26
  role="img"
23
27
  ></div>
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { extractImageUrl } from '../../lib/functions.js';
3
+
2
4
  const {
3
5
  image,
4
6
  pretitle,
@@ -8,9 +10,11 @@ const {
8
10
  buttonText,
9
11
  buttonUrl,
10
12
  } = Astro.props;
13
+
14
+ const imageUrl = extractImageUrl(image);
11
15
  ---
12
16
 
13
- <div class={`bg-[100%_0] bg-cover h-[570px] w-full ${orientation === "right" ? "rotate-y-180" : ""} `} style={`background-image: url('${image}');`} >
17
+ <div class={`bg-[100%_0] bg-cover h-[570px] w-full ${orientation === "right" ? "rotate-y-180" : ""} `} style={`background-image: url('${imageUrl}');`} >
14
18
  <div class="w-full lg:w-[70%] h-full transition duration-900 ease-in-out bg-gradient-to-r from-indigo-900 via-blue-600 to-pink-500 hover:from-pink-500 hover:via-blue-600 hover:to-indigo-900 lg:[clip-path:polygon(0_0,100%_0,88%_86%,68%_100%,0_100%)]">
15
19
  <div class="flex flex-row w-4/5 center mx-auto pt-20 sm:pt-30">
16
20
  <div class={`w-full ${orientation === "right" ? "rotate-y-180" : ""}`}>
@@ -0,0 +1,89 @@
1
+ ---
2
+ /**
3
+ * 🎯 LimboImage - Componente wrapper para imágenes de Limbo
4
+ *
5
+ * Este componente simplifica el uso de imágenes de Limbo en componentes Astro.
6
+ * Extrae automáticamente la URL del JSON de Limbo y maneja URLs relativas.
7
+ *
8
+ * @example
9
+ * // Uso simple - el src puede ser URL directa o JSON de Limbo
10
+ * <LimboImage src={imagenDeLimbo} alt="Descripción" />
11
+ *
12
+ * @example
13
+ * // Con clases personalizadas
14
+ * <LimboImage
15
+ * src={imagenDeLimbo}
16
+ * alt="Hero image"
17
+ * class="w-full h-auto rounded-lg"
18
+ * />
19
+ *
20
+ * @example
21
+ * // Preferir imagen original en lugar del crop
22
+ * <LimboImage src={imagenDeLimbo} prefer="original" />
23
+ *
24
+ * @example
25
+ * // Con fallback si no hay imagen
26
+ * <LimboImage src={imagenDeLimbo} fallback="/default.jpg" />
27
+ */
28
+
29
+ import { extractImageUrl } from '../lib/functions.js';
30
+
31
+ interface Props {
32
+ /** Valor de imagen - puede ser URL directa o JSON de Limbo */
33
+ src: string;
34
+ /** Texto alternativo (requerido para accesibilidad) */
35
+ alt: string;
36
+ /** Título opcional para tooltip */
37
+ title?: string;
38
+ /** Clases CSS adicionales */
39
+ class?: string;
40
+ /** Preferir 'crop' (recorte) u 'original' */
41
+ prefer?: 'crop' | 'original';
42
+ /** Imagen fallback si src está vacío o es inválido */
43
+ fallback?: string;
44
+ /** Carga diferida (por defecto: lazy) */
45
+ loading?: 'lazy' | 'eager';
46
+ /** Decodificación (por defecto: async) */
47
+ decoding?: 'async' | 'sync' | 'auto';
48
+ /** Ancho explícito (para CLS) */
49
+ width?: number | string;
50
+ /** Alto explícito (para CLS) */
51
+ height?: number | string;
52
+ /** Usar entorno de producción para URLs relativas */
53
+ isProd?: boolean;
54
+ /** Atributos HTML adicionales */
55
+ [key: string]: any;
56
+ }
57
+
58
+ const {
59
+ src,
60
+ alt,
61
+ title,
62
+ class: className,
63
+ prefer = 'crop',
64
+ fallback = '',
65
+ loading = 'lazy',
66
+ decoding = 'async',
67
+ width,
68
+ height,
69
+ isProd = false,
70
+ ...restProps
71
+ } = Astro.props;
72
+
73
+ // Extraer URL usando la función centralizada
74
+ const imageUrl = extractImageUrl(src, { prefer, isProd }) || fallback;
75
+ ---
76
+
77
+ {imageUrl && (
78
+ <img
79
+ src={imageUrl}
80
+ alt={alt}
81
+ title={title}
82
+ class={className}
83
+ loading={loading}
84
+ decoding={decoding}
85
+ width={width}
86
+ height={height}
87
+ {...restProps}
88
+ />
89
+ )}
package/src/index.ts CHANGED
@@ -127,3 +127,5 @@ export const components = {
127
127
  Video_2025_Valencia: Video_2025_Valencia,
128
128
  ReactButton: ReactButton
129
129
  };
130
+ // 🆕 Exportar funciones helper adicionales
131
+ export { extractImageUrl, parseImageData } from './lib/functions.js';
@@ -1,5 +1,176 @@
1
1
  import { components } from '../generated/componentRegistry.ts';
2
2
 
3
+ // ============================================================================
4
+ // 🎯 Utilidades de Imagen para Limbo
5
+ // ============================================================================
6
+ // NOTA: Estas funciones están copiadas de component-limbo/src/utils/helpers.js
7
+ // para evitar problemas de dependencias en entornos sin acceso directo a limbo-component.
8
+ // Si se actualizan en helpers.js, sincronizar aquí también.
9
+ // ============================================================================
10
+
11
+ /**
12
+ * URLs base de Limbo según entorno
13
+ */
14
+ export const LIMBO_BASE_URL = {
15
+ DEV: 'https://led-dev-limbo-dev.eu.els.local',
16
+ PROD: 'https://limbo.lefebvre.es'
17
+ };
18
+
19
+ /**
20
+ * Convierte URLs relativas de Limbo a absolutas
21
+ */
22
+ export function resolveUrl(url, isProd = false) {
23
+ if (!url) return '';
24
+ if (url.startsWith('/files/')) {
25
+ const baseUrl = isProd ? LIMBO_BASE_URL.PROD : LIMBO_BASE_URL.DEV;
26
+ return baseUrl + url;
27
+ }
28
+ return url;
29
+ }
30
+
31
+ /**
32
+ * Decodifica entidades HTML comunes en un string
33
+ */
34
+ export function decodeHtmlEntities(value) {
35
+ if (typeof value !== 'string') return value;
36
+ return value
37
+ .replace(/&quot;/g, '"')
38
+ .replace(/&amp;/g, '&')
39
+ .replace(/&lt;/g, '<')
40
+ .replace(/&gt;/g, '>')
41
+ .replace(/&#39;/g, "'")
42
+ .replace(/&#x27;/g, "'");
43
+ }
44
+
45
+ /**
46
+ * Verifica si una URL es válida y usable (no blob, no vacía)
47
+ */
48
+ export function isValidImageUrl(url) {
49
+ if (!url || typeof url !== 'string') return false;
50
+ if (url.startsWith('blob:')) return false;
51
+ return true;
52
+ }
53
+
54
+ /**
55
+ * 🎯 FUNCIÓN PRINCIPAL - Extrae URL de imagen de cualquier formato
56
+ *
57
+ * @param {string} value - URL directa o JSON de Limbo
58
+ * @param {Object} options - { prefer: 'crop'|'original', isProd: boolean }
59
+ * @returns {string} URL lista para usar en <img src="">
60
+ */
61
+ export function extractImageUrl(value, options = {}) {
62
+ if (!value) return '';
63
+
64
+ const { prefer = 'crop', isProd = false } = options;
65
+ const normalizedValue = decodeHtmlEntities(value);
66
+
67
+ try {
68
+ const data = JSON.parse(normalizedValue);
69
+
70
+ const findValidCropUrl = () => {
71
+ if (data.images && Array.isArray(data.images)) {
72
+ for (const img of data.images) {
73
+ if (img && isValidImageUrl(img.url)) {
74
+ return img.url;
75
+ }
76
+ }
77
+ }
78
+ return null;
79
+ };
80
+
81
+ const originalUrl = data.original?.url;
82
+ const cropUrl = findValidCropUrl();
83
+
84
+ if (prefer === 'crop') {
85
+ if (cropUrl) return resolveUrl(cropUrl, isProd);
86
+ if (isValidImageUrl(originalUrl)) return resolveUrl(originalUrl, isProd);
87
+ } else {
88
+ if (isValidImageUrl(originalUrl)) return resolveUrl(originalUrl, isProd);
89
+ if (cropUrl) return resolveUrl(cropUrl, isProd);
90
+ }
91
+
92
+ if (isValidImageUrl(data.url)) {
93
+ return resolveUrl(data.url, isProd);
94
+ }
95
+
96
+ return '';
97
+
98
+ } catch {
99
+ if (typeof normalizedValue === 'string') {
100
+ if (normalizedValue.startsWith('blob:')) return '';
101
+ if (normalizedValue.startsWith('/files/')) return resolveUrl(normalizedValue, isProd);
102
+ if (normalizedValue.startsWith('http') || normalizedValue.startsWith('/')) return normalizedValue;
103
+ }
104
+ }
105
+
106
+ if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
107
+ return '';
108
+ }
109
+
110
+ return value;
111
+ }
112
+
113
+ /**
114
+ * Obtiene datos completos de imagen (original + recortes)
115
+ */
116
+ export function parseImageData(value, options = {}) {
117
+ const { isProd = false } = options;
118
+
119
+ if (!value) return { original: null, images: [], url: '' };
120
+
121
+ try {
122
+ const data = JSON.parse(decodeHtmlEntities(value));
123
+
124
+ const resolvedOriginal = data.original ? {
125
+ ...data.original,
126
+ url: resolveUrl(data.original.url, isProd)
127
+ } : null;
128
+
129
+ const resolvedImages = (data.images || []).map(img => ({
130
+ ...img,
131
+ url: resolveUrl(img.url, isProd)
132
+ }));
133
+
134
+ return {
135
+ original: resolvedOriginal,
136
+ images: resolvedImages,
137
+ url: resolvedImages[0]?.url || resolvedOriginal?.url || resolveUrl(data.url, isProd) || ''
138
+ };
139
+
140
+ } catch {
141
+ return {
142
+ original: null,
143
+ images: [],
144
+ url: resolveUrl(value, isProd)
145
+ };
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Preprocesa campos de imagen para enviar a servidor de preview
151
+ */
152
+ export function prepareImageFieldsForPreview(obj, options = {}) {
153
+ const { isProd = false } = options;
154
+
155
+ if (!obj?.fields) return obj;
156
+
157
+ const processedFields = obj.fields.map(field => {
158
+ if (field.type === 'image' && typeof field.example_value === 'string') {
159
+ const extractedUrl = extractImageUrl(field.example_value, { isProd });
160
+ return {
161
+ ...field,
162
+ example_value: extractedUrl || field.example_value
163
+ };
164
+ }
165
+ return field;
166
+ });
167
+
168
+ return {
169
+ ...obj,
170
+ fields: processedFields
171
+ };
172
+ }
173
+
3
174
  export function listComponents() {
4
175
  const metadatas = components
5
176
  .map(c => c.component.metadata)
@@ -30,8 +30,16 @@ interface Props {
30
30
  selector?: string;
31
31
  /** Tipo de retorno por defecto */
32
32
  defaultReturnType?: 'url' | 'assetId' | 'object' | 'json' | 'base64';
33
+ /** Modo UI por defecto */
34
+ defaultModeUI?: 'full' | 'gallery-only' | 'upload-only' | 'crop-only';
33
35
  /** Tamaño del modal (default: 'xlarge') */
34
36
  sizeModal?: 'small' | 'medium' | 'large' | 'xlarge' | 'fullscreen';
37
+ /** Texto del botón por defecto */
38
+ defaultButtonText?: string;
39
+ /** Permitir crops adicionales además de los obligatorios */
40
+ allowAdditionalCrops?: boolean;
41
+ /** Máximo número de crops permitidos */
42
+ maxCrops?: number;
35
43
  /** Versión de limbo-component a usar (default: 'latest') */
36
44
  version?: string;
37
45
  /** Usar assets locales en vez de CDN (para desarrollo con npm link) */
@@ -43,7 +51,11 @@ const {
43
51
  prod = false,
44
52
  selector = '.js-limbo',
45
53
  defaultReturnType = 'json',
54
+ defaultModeUI = 'full',
46
55
  sizeModal = 'xlarge',
56
+ defaultButtonText = 'Seleccionar imagen',
57
+ allowAdditionalCrops = true,
58
+ maxCrops = 10,
47
59
  version = 'latest',
48
60
  useLocalAssets = !prod
49
61
  } = Astro.props;
@@ -57,7 +69,11 @@ const initScript = generateLimboInitScript({
57
69
  prod,
58
70
  selector,
59
71
  defaultReturnType,
60
- sizeModal
72
+ defaultModeUI,
73
+ sizeModal,
74
+ defaultButtonText,
75
+ allowAdditionalCrops,
76
+ maxCrops
61
77
  });
62
78
  ---
63
79
 
package/src/limbo/init.ts CHANGED
@@ -1,16 +1,38 @@
1
1
  /**
2
2
  * Limbo Integration Module for libreria-astro-lefebvre
3
3
  *
4
+ * v2.0 - Actualizado para limbo-component v2.0
5
+ *
4
6
  * Este módulo gestiona la inicialización y configuración de limbo-component
5
7
  * para ser usado en cualquier portal que consuma esta librería.
8
+ *
9
+ * Soporta configuración via data attributes en inputs HTML:
10
+ * - data-mandatorycrops: JSON array de crops obligatorios
11
+ * - data-modeui: full | gallery-only | upload-only | crop-only
12
+ * - data-returntype: url | assetId | object | json | base64
13
+ * - data-allowadditionalcrops: true | false
14
+ * - data-maxcrops: number
15
+ * - data-buttontext: texto personalizado del botón
6
16
  */
7
17
 
18
+ export interface MandatoryCrop {
19
+ label: string;
20
+ width?: number;
21
+ height?: number;
22
+ required?: boolean;
23
+ preset_aspect?: string;
24
+ }
25
+
8
26
  export interface LimboConfig {
9
27
  publicKey: string;
10
28
  prod?: boolean;
11
29
  selector?: string;
12
- defaultReturnType?: 'url' | 'assetId' | 'object' | 'json' | 'base64';
13
- sizeModal?: 'small' | 'medium' | 'large' | 'xlarge' | 'fullscreen';
30
+ defaultReturnType?: "url" | "assetId" | "object" | "json" | "base64";
31
+ defaultModeUI?: "full" | "gallery-only" | "upload-only" | "crop-only";
32
+ sizeModal?: "small" | "medium" | "large" | "xlarge" | "fullscreen";
33
+ defaultButtonText?: string;
34
+ allowAdditionalCrops?: boolean;
35
+ maxCrops?: number;
14
36
  onSelect?: (data: any) => void;
15
37
  onError?: (error: Error) => void;
16
38
  }
@@ -27,14 +49,18 @@ export function generateLimboInitScript(config: LimboConfig): string {
27
49
  const {
28
50
  publicKey,
29
51
  prod = false,
30
- selector = '.js-limbo',
31
- defaultReturnType = 'json',
32
- sizeModal = 'xlarge'
52
+ selector = ".js-limbo",
53
+ defaultReturnType = "json",
54
+ defaultModeUI = "full",
55
+ sizeModal = "xlarge",
56
+ defaultButtonText = "Seleccionar imagen",
57
+ allowAdditionalCrops = true,
58
+ maxCrops = 10,
33
59
  } = config;
34
60
 
35
61
  // URL del proxy para evitar CORS (usa endpoint local del portal)
36
62
  // El proxy está en /api/limbo-token del portal que consume la librería
37
- const tokenProxyUrl = '/api/limbo-token';
63
+ const tokenProxyUrl = "/api/limbo-token";
38
64
 
39
65
  return `
40
66
  (function() {
@@ -79,11 +105,41 @@ export function generateLimboInitScript(config: LimboConfig): string {
79
105
  }
80
106
  }
81
107
 
108
+ // Estado de inicialización
109
+ var initRetrys = 0;
110
+ var maxRetrys = 20;
111
+
112
+ // Función global para re-escanear inputs (útil después de hydration de Vue/React)
113
+ window.LimboRescan = function(selector) {
114
+ var targetSelector = selector || '${selector}';
115
+ var inputs = document.querySelectorAll(targetSelector);
116
+ var processed = 0;
117
+ console.log('[Limbo] Rescan manual: buscando inputs con selector', targetSelector);
118
+ inputs.forEach(function(input) {
119
+ if (input.tagName === 'INPUT' && !input.dataset.limboProcessed) {
120
+ try {
121
+ Limbo.autoInputs._processInput(input);
122
+ input.dataset.limboProcessed = 'true';
123
+ processed++;
124
+ } catch (e) {
125
+ console.error('[Limbo] Error procesando input:', e);
126
+ }
127
+ }
128
+ });
129
+ console.log('[Limbo] Rescan completado:', processed, 'inputs procesados de', inputs.length, 'encontrados');
130
+ return processed;
131
+ };
132
+
82
133
  // Esperamos a que Limbo esté disponible
83
134
  function initLimbo() {
84
135
  if (typeof Limbo === 'undefined') {
85
- console.warn('[Limbo] Limbo no está cargado. Reintentando...');
86
- setTimeout(initLimbo, 100);
136
+ initRetrys++;
137
+ if (initRetrys <= maxRetrys) {
138
+ console.warn('[Limbo] Limbo no está cargado. Reintentando... (' + initRetrys + '/' + maxRetrys + ')');
139
+ setTimeout(initLimbo, 100);
140
+ } else {
141
+ console.error('[Limbo] No se pudo cargar Limbo después de ' + maxRetrys + ' intentos.');
142
+ }
87
143
  return;
88
144
  }
89
145
 
@@ -104,10 +160,17 @@ export function generateLimboInitScript(config: LimboConfig): string {
104
160
  // Configurar auto-inputs para detectar elementos con el selector
105
161
  Limbo.configureAutoInputs({
106
162
  selector: '${selector}',
107
- buttonText: 'Seleccionar imagen',
163
+ buttonText: '${defaultButtonText}',
108
164
  returnType: '${defaultReturnType}',
165
+ modeUI: '${defaultModeUI}',
166
+ allowAdditionalCrops: ${allowAdditionalCrops},
167
+ maxCrops: ${maxCrops},
168
+ // 🔧 Los recortes se guardan en Limbo para garantizar persistencia
169
+ // Los recortes del page-builder se pueden identificar por su nombre (contienen dimensiones)
170
+ localCropsOnly: false,
109
171
 
110
172
  // Parsear configuración del input desde datasets
173
+ // Los valores del dataset tienen prioridad sobre los defaults
111
174
  parseInputConfig: function(input) {
112
175
  var config = {};
113
176
 
@@ -130,11 +193,68 @@ export function generateLimboInitScript(config: LimboConfig): string {
130
193
  config.modeUI = input.dataset.modeui;
131
194
  }
132
195
 
196
+ // Parsear allowAdditionalCrops
197
+ if (input.dataset.allowadditionalcrops !== undefined) {
198
+ config.allowAdditionalCrops = input.dataset.allowadditionalcrops === 'true';
199
+ }
200
+
201
+ // Parsear maxCrops
202
+ if (input.dataset.maxcrops) {
203
+ config.maxCrops = parseInt(input.dataset.maxcrops, 10);
204
+ }
205
+
206
+ // Parsear buttonText personalizado
207
+ if (input.dataset.buttontext) {
208
+ config.buttonText = input.dataset.buttontext;
209
+ }
210
+
133
211
  return config;
134
212
  }
135
213
  });
136
214
 
137
215
  console.log('[Limbo] Integración configurada. Buscando inputs con selector: ${selector}');
216
+
217
+ // Debug: Verificar que el observer esté activo
218
+ console.log('[Limbo] AutoInputs stats:', Limbo.autoInputs?.getStats?.() || 'N/A');
219
+
220
+ // Debug: Escanear manualmente después de un delay para Vue/React hydration
221
+ setTimeout(function() {
222
+ console.log('[Limbo] Re-escaneando inputs después de hydration...');
223
+ var inputs = document.querySelectorAll('${selector}');
224
+ console.log('[Limbo] Encontrados ' + inputs.length + ' inputs con selector ${selector}');
225
+ inputs.forEach(function(input, idx) {
226
+ console.log('[Limbo] Input ' + idx + ':', input.tagName, input.className, input.id || input.name);
227
+ // Forzar procesamiento si es un input válido
228
+ if (input.tagName === 'INPUT' && !input.dataset.limboProcessed) {
229
+ try {
230
+ Limbo.autoInputs._processInput(input);
231
+ input.dataset.limboProcessed = 'true';
232
+ console.log('[Limbo] Input procesado manualmente:', input.id || input.name);
233
+ } catch (e) {
234
+ console.error('[Limbo] Error procesando input:', e);
235
+ }
236
+ }
237
+ });
238
+ }, 1000); // Esperar 1 segundo para Vue hydration
239
+
240
+ // Segunda pasada después de 2.5 segundos para componentes Vue más lentos
241
+ setTimeout(function() {
242
+ var inputs = document.querySelectorAll('${selector}');
243
+ var pendingInputs = Array.from(inputs).filter(function(input) {
244
+ return input.tagName === 'INPUT' && !input.dataset.limboProcessed;
245
+ });
246
+ if (pendingInputs.length > 0) {
247
+ console.log('[Limbo] Segunda pasada: procesando ' + pendingInputs.length + ' inputs pendientes');
248
+ pendingInputs.forEach(function(input) {
249
+ try {
250
+ Limbo.autoInputs._processInput(input);
251
+ input.dataset.limboProcessed = 'true';
252
+ } catch (e) {
253
+ console.error('[Limbo] Error procesando input:', e);
254
+ }
255
+ });
256
+ }
257
+ }, 2500);
138
258
  }
139
259
 
140
260
  // Iniciar cuando el DOM esté listo
@@ -150,25 +270,26 @@ export function generateLimboInitScript(config: LimboConfig): string {
150
270
  /**
151
271
  * Genera las URLs de los assets de Limbo (CSS y JS)
152
272
  */
153
- export function getLimboAssetUrls(options: { prod?: boolean; version?: string } = {}) {
154
- const { prod = false, version = 'latest' } = options;
155
-
273
+ export function getLimboAssetUrls(
274
+ options: { prod?: boolean; version?: string } = {}
275
+ ) {
276
+ const { prod = false, version = "latest" } = options;
156
277
  // En producción, usar CDN de npm o URL de producción
157
278
  if (prod) {
158
279
  return {
159
280
  css: `https://unpkg.com/limbo-component@${version}/dist/limbo.css`,
160
- js: `https://unpkg.com/limbo-component@${version}/dist/limbo.min.js`
281
+ js: `https://unpkg.com/limbo-component@${version}/dist/limbo.min.js`,
161
282
  };
162
283
  }
163
284
 
164
- // En desarrollo, usar el build local (npm link)
285
+ // En desarrollo, usar el build local desde public/limbo
165
286
  return {
166
- css: '/node_modules/limbo-component/dist/limbo.css',
167
- js: '/node_modules/limbo-component/dist/limbo.min.js'
287
+ css: "/limbo/limbo.css",
288
+ js: "/limbo/limbo.min.js",
168
289
  };
169
290
  }
170
291
 
171
292
  export default {
172
293
  generateLimboInitScript,
173
- getLimboAssetUrls
294
+ getLimboAssetUrls,
174
295
  };