libreria-astro-lefebvre 0.0.49 → 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 (45) 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/Cabecera_2025_Madrid.astro +1 -1
  18. package/src/components/Astro/Contenido_2025_Alcorcon.astro +5 -3
  19. package/src/components/Astro/Contenido_2025_Malaga.astro +4 -2
  20. package/src/components/Astro/Contenido_2025_Montevideo.astro +3 -3
  21. package/src/components/Astro/CorpFooter.astro +1 -1
  22. package/src/components/Astro/CorpNavigation.astro +2 -2
  23. package/src/components/Astro/Footer_2025_Napoles.astro +1 -1
  24. package/src/components/Astro/Formulario_2025_Seul.astro +3 -1
  25. package/src/components/Astro/Formulario_2025_Teruel.astro +5 -2
  26. package/src/components/Astro/GeometricShape.astro +4 -1
  27. package/src/components/Astro/GeometricShapeCard.astro +4 -1
  28. package/src/components/Astro/HeaderCorporativo.astro +2 -2
  29. package/src/components/Astro/ImageTextSimple.astro +5 -1
  30. package/src/components/Astro/Imagen_2025_Bogota.astro +30 -16
  31. package/src/components/Astro/Imagen_2025_Fukushima.astro +5 -2
  32. package/src/components/Astro/Repetidor_2025_Cabra.astro +1 -1
  33. package/src/components/Astro/Repetidor_2025_Orcasitas.astro +2 -2
  34. package/src/components/Astro/Repetidor_2025_Oslo.astro +1 -1
  35. package/src/components/Astro/Repetidor_2025_Yakarta.astro +1 -1
  36. package/src/components/Astro/TextImageBackground.astro +6 -1
  37. package/src/components/Astro/TextImageBlock.astro +5 -1
  38. package/src/components/Astro/TextImageCard.astro +5 -1
  39. package/src/components/Astro/TextImageHeader.astro +5 -1
  40. package/src/components/Astro/Video_2025_Polop.astro +1 -1
  41. package/src/components/LimboImage.astro +89 -0
  42. package/src/index.ts +2 -0
  43. package/src/lib/functions.js +171 -0
  44. package/src/limbo/LimboProvider.astro +17 -1
  45. package/src/limbo/init.ts +138 -17
@@ -88,7 +88,7 @@ const structuredData = `<script type="application/ld+json">
88
88
  />
89
89
  </div>
90
90
  <div class="w-full md:w-1/2 p-0 md:pr-2">
91
- <img src={item.image} alt="Imagen" class="w-full min-h-auto lg:min-h-full object-cover rounded-xl" />
91
+ <img src={item.image} alt="Imagen" loading="lazy" class="w-full min-h-auto lg:min-h-full object-cover rounded-xl" />
92
92
  </div>
93
93
  </div>
94
94
 
@@ -72,7 +72,7 @@ const structuredData = `<script type="application/ld+json">
72
72
  {item.iframeSrc ? (
73
73
  <div class="w-full">
74
74
  <span class={`popup-iframe-trigger-${randomId} cursor-pointer`} data-iframesrc={item.iframeSrc}>
75
- <img src={item.imageSrc} class="w-full h-full rounded-2xl p-2 transition-transform hover:scale-105 cursor-pointer"/>
75
+ <img src={item.imageSrc} loading="lazy" class="w-full h-full rounded-2xl p-2 transition-transform hover:scale-105 cursor-pointer"/>
76
76
  </span>
77
77
  </div>
78
78
  <div class="p-6 flex-grow flex flex-col">
@@ -84,7 +84,7 @@ const structuredData = `<script type="application/ld+json">
84
84
  </div>
85
85
  </div>
86
86
  ) : (
87
- <img src={item.image} alt={item.altImage} class="!max-w-[250px] w-fit" />
87
+ <img src={item.image} alt={item.altImage} class="!max-w-[250px] w-fit" loading="lazy" />
88
88
  <p>{item.descriptionImage}</p>
89
89
  )}
90
90
  </div>
@@ -31,7 +31,7 @@ const structuredDataScript = `<script type="application/ld+json">${structuredDat
31
31
  {items.map(item => (
32
32
  <article class="w-full md:w-1/3 h-full mb-4 md:mb-0 relative">
33
33
  <p class={`popup-iframe-trigger-${randomId} cursor-pointer`} data-iframesrc={item.iframeSrc}>
34
- <img src={item.imageSrc} class="w-full h-full rounded-2xl p-2 transition-transform hover:scale-105 cursor-pointer"/>
34
+ <img src={item.imageSrc} class="w-full h-full rounded-2xl p-2 transition-transform hover:scale-105 cursor-pointer" loading="lazy"/>
35
35
  </p>
36
36
  <p set:html={item.description} class="text-[14px] text-[#363942] text-center font-semibold"></p>
37
37
  </article>
@@ -81,7 +81,7 @@ const structuredData = `<script type="application/ld+json">
81
81
  <p class="text-[#363942] font-poppins font-normal text-[20px] not-italic leading-[28px]" set:html={item.description}></p>
82
82
  </div>
83
83
  <div class="w-full lg:w-2/2 flex items-start">
84
- {/* <img src="/assets/images/genial/gradient-bg.webp" alt="Imagen" class="w-full h-auto rounded-xl" /> */}
84
+ {/* <img src="/assets/images/genial/gradient-bg.webp" alt="Imagen" class="w-full h-auto rounded-xl" loading="lazy" /> */}
85
85
  <video autoplay loop muted playsinline class="w-full h-auto rounded-xl">
86
86
  <source src={item.iframeSrc} type="video/mp4" />
87
87
  </video>
@@ -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" />
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" : ""}`}>
@@ -15,7 +15,7 @@ const {
15
15
  <p class="text-[#363942] font-inter font-normal text-base not-italic leading-[24px]" set:html={description}></p>
16
16
  </div>
17
17
  <div class="w-full lg:w-2/2 flex items-start">
18
- {/* <img src="/assets/images/genial/gradient-bg.webp" alt="Imagen" class="w-full h-auto rounded-xl" /> */}
18
+ {/* <img src="/assets/images/genial/gradient-bg.webp" alt="Imagen" class="w-full h-auto rounded-xl" loading="lazy" /> */}
19
19
  <video autoplay loop muted playsinline class="w-full h-auto rounded-xl">
20
20
  <source src={iframeSrc} type="video/mp4" />
21
21
  </video>
@@ -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