lumina-slides 9.0.3 → 9.0.5
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/lumina-slides.js +11178 -10991
- package/dist/lumina-slides.umd.cjs +204 -204
- package/dist/style.css +1 -1
- package/package.json +3 -1
- package/src/components/layouts/LayoutFeatures.vue +7 -5
- package/src/components/layouts/LayoutFlex.vue +47 -7
- package/src/components/layouts/LayoutSteps.vue +7 -5
- package/src/components/parts/FlexHtml.vue +65 -0
- package/src/components/parts/FlexImage.vue +29 -2
- package/src/components/site/SiteDocs.vue +196 -40
- package/src/composables/useFlexLayout.ts +9 -3
- package/src/core/elementResolver.ts +33 -7
- package/src/core/schema.ts +40 -15
- package/src/core/types.ts +33 -3
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
:style="mainFlexStyle">
|
|
7
7
|
<template v-for="(element, i) in data.elements" :key="i">
|
|
8
8
|
<!-- Image Element -->
|
|
9
|
-
<LuminaElement v-if="element.type === 'image'" :id="elemId(i)" :class="['flex-item', getSizeClass(element.size)]" :style="getElementStyle(element)">
|
|
9
|
+
<LuminaElement v-if="element.type === 'image'" :id="elemId(i)" :class="['flex-item', getSizeClass(element.size), element.class]" :style="{ ...getElementStyle(element), ...(element.style || {}) }">
|
|
10
10
|
<FlexImage :src="element.src" :alt="element.alt"
|
|
11
|
-
:fill="element.fill" :
|
|
12
|
-
|
|
11
|
+
:fill="element.fill" :fit="element.fit" :position="element.position"
|
|
12
|
+
:rounded="element.rounded" :href="element.href" :target="element.target"
|
|
13
|
+
container-class="w-full h-full min-h-0" :container-style="{}" :style="element.style" />
|
|
13
14
|
</LuminaElement>
|
|
14
15
|
|
|
15
16
|
<!-- Video Element -->
|
|
@@ -22,12 +23,39 @@
|
|
|
22
23
|
:object-fit="element.fill !== false ? 'cover' : 'contain'" />
|
|
23
24
|
</LuminaElement>
|
|
24
25
|
|
|
25
|
-
<!--
|
|
26
|
+
<!-- HTML Element -->
|
|
27
|
+
<LuminaElement v-else-if="element.type === 'html'" :id="elemId(i)" :class="['flex-item', getSizeClass(element.size), element.class]" :style="{ ...getElementStyle(element), ...(element.style || {}) }">
|
|
28
|
+
<FlexHtml :html="element.html" :class="element.class" :style="element.style" />
|
|
29
|
+
</LuminaElement>
|
|
30
|
+
|
|
31
|
+
<!-- Content Container (supports nested content) -->
|
|
26
32
|
<LuminaElement v-else-if="element.type === 'content'" :id="elemId(i)"
|
|
27
|
-
:class="['flex-item flex flex-
|
|
33
|
+
:class="['flex-item flex h-full', element.direction === 'horizontal' ? 'flex-row' : 'flex-col', getSizeClass(element.size), element.class]"
|
|
28
34
|
:style="getContentStyle(element)">
|
|
29
35
|
<template v-for="(child, j) in element.elements" :key="j">
|
|
30
|
-
|
|
36
|
+
<!-- Nested content containers (recursive) -->
|
|
37
|
+
<LuminaElement v-if="child.type === 'content'" :id="childId(i, j)"
|
|
38
|
+
:class="['flex', (child as FlexElementContent).direction === 'horizontal' ? 'flex-row' : 'flex-col']"
|
|
39
|
+
:style="getNestedContentStyle(child as FlexElementContent)">
|
|
40
|
+
<template v-for="(grandchild, k) in (child as FlexElementContent).elements" :key="k">
|
|
41
|
+
<LuminaElement :id="resolveId(props.data, slideIndex.value, ['elements', i, 'elements', j, 'elements', k])">
|
|
42
|
+
<!-- Recursively handle nested content -->
|
|
43
|
+
<LuminaElement v-if="grandchild.type === 'content'"
|
|
44
|
+
:class="['flex', (grandchild as FlexElementContent).direction === 'horizontal' ? 'flex-row' : 'flex-col']"
|
|
45
|
+
:style="getNestedContentStyle(grandchild as FlexElementContent)">
|
|
46
|
+
<template v-for="(greatGrandchild, l) in (grandchild as FlexElementContent).elements" :key="l">
|
|
47
|
+
<LuminaElement :id="resolveId(props.data, slideIndex.value, ['elements', i, 'elements', j, 'elements', k, 'elements', l])">
|
|
48
|
+
<component :is="getChildComponent(greatGrandchild.type)" v-bind="greatGrandchild" @action="handleAction" />
|
|
49
|
+
</LuminaElement>
|
|
50
|
+
</template>
|
|
51
|
+
</LuminaElement>
|
|
52
|
+
<!-- Regular grandchild elements -->
|
|
53
|
+
<component v-else :is="getChildComponent(grandchild.type)" v-bind="grandchild" @action="handleAction" />
|
|
54
|
+
</LuminaElement>
|
|
55
|
+
</template>
|
|
56
|
+
</LuminaElement>
|
|
57
|
+
<!-- Regular child elements -->
|
|
58
|
+
<LuminaElement v-else :id="childId(i, j)">
|
|
31
59
|
<component :is="getChildComponent(child.type)" v-bind="child" @action="handleAction" />
|
|
32
60
|
</LuminaElement>
|
|
33
61
|
</template>
|
|
@@ -60,6 +88,7 @@ import FlexOrdered from '../parts/FlexOrdered.vue';
|
|
|
60
88
|
import FlexTimeline from '../parts/FlexTimeline.vue';
|
|
61
89
|
import FlexStepper from '../parts/FlexStepper.vue';
|
|
62
90
|
import FlexSpacer from '../parts/FlexSpacer.vue';
|
|
91
|
+
import FlexHtml from '../parts/FlexHtml.vue';
|
|
63
92
|
import { bus } from '../../core/events';
|
|
64
93
|
import {
|
|
65
94
|
spacingVarMap,
|
|
@@ -102,13 +131,23 @@ const getElementStyle = (element: FlexElement & { size?: FlexSize }) => {
|
|
|
102
131
|
|
|
103
132
|
const getContentStyle = (element: FlexElementContent & { size?: FlexSize }) => {
|
|
104
133
|
const isVertical = props.data.direction === 'vertical';
|
|
134
|
+
const contentDirection = element.direction || 'vertical';
|
|
105
135
|
return {
|
|
106
136
|
...getFlexItemStyle(element, isVertical),
|
|
107
137
|
...getFlexContainerStyle(element),
|
|
108
138
|
// Force full height/width for alignment space
|
|
109
139
|
height: isVertical ? 'auto' : '100%',
|
|
110
140
|
width: isVertical ? '100%' : 'auto',
|
|
111
|
-
minHeight: isVertical ? 'auto' : '100%'
|
|
141
|
+
minHeight: isVertical ? 'auto' : (contentDirection === 'vertical' ? '100%' : 'auto'),
|
|
142
|
+
...(element.style || {})
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const getNestedContentStyle = (element: FlexElementContent) => {
|
|
147
|
+
return {
|
|
148
|
+
...getFlexContainerStyle(element),
|
|
149
|
+
width: '100%',
|
|
150
|
+
...(element.style || {})
|
|
112
151
|
};
|
|
113
152
|
};
|
|
114
153
|
|
|
@@ -136,6 +175,7 @@ const getChildComponent = (type: string) => {
|
|
|
136
175
|
'timeline': FlexTimeline,
|
|
137
176
|
'stepper': FlexStepper,
|
|
138
177
|
'spacer': FlexSpacer,
|
|
178
|
+
'html': FlexHtml,
|
|
139
179
|
};
|
|
140
180
|
return components[type] || 'div';
|
|
141
181
|
};
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
3
|
<div :class="['w-full flex flex-col justify-center', data.sizing === 'container' ? 'min-h-full py-12 lg:py-16' : 'min-h-screen']"
|
|
4
4
|
:style="{ padding: 'var(--lumina-space-xl) var(--lumina-space-3xl)' }">
|
|
5
|
-
<!-- Header -->
|
|
5
|
+
<!-- Header: wrapper (header) + title and subtitle as separate controllable elements -->
|
|
6
6
|
<LuminaElement :id="headerId" :class="['text-center max-w-4xl mx-auto', data.class || '']"
|
|
7
7
|
:style="{ marginBottom: 'var(--lumina-space-2xl)' }">
|
|
8
|
-
<h2 class="font-bold" :style="{
|
|
8
|
+
<LuminaElement :id="titleId" tag="h2" class="font-bold" :style="{
|
|
9
9
|
fontFamily: 'var(--lumina-font-heading)',
|
|
10
10
|
fontSize: 'var(--lumina-text-5xl)',
|
|
11
11
|
marginBottom: 'var(--lumina-space-md)',
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))'
|
|
14
14
|
}">
|
|
15
15
|
{{ data.title }}
|
|
16
|
-
</
|
|
17
|
-
<
|
|
16
|
+
</LuminaElement>
|
|
17
|
+
<LuminaElement v-if="data.subtitle" :id="subtitleId" tag="p" :style="{
|
|
18
18
|
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
19
19
|
opacity: 0.9,
|
|
20
20
|
fontSize: 'var(--lumina-text-xl)'
|
|
21
21
|
}">
|
|
22
22
|
{{ data.subtitle }}
|
|
23
|
-
</
|
|
23
|
+
</LuminaElement>
|
|
24
24
|
</LuminaElement>
|
|
25
25
|
|
|
26
26
|
<!-- Steps Grid -->
|
|
@@ -102,5 +102,7 @@ const props = defineProps<{
|
|
|
102
102
|
const store = inject(StoreKey);
|
|
103
103
|
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
104
104
|
const headerId = computed(() => resolveId(props.data, slideIndex.value, ['header']));
|
|
105
|
+
const titleId = computed(() => resolveId(props.data, slideIndex.value, ['title']));
|
|
106
|
+
const subtitleId = computed(() => resolveId(props.data, slideIndex.value, ['subtitle']));
|
|
105
107
|
const stepId = (i: number) => resolveId(props.data, slideIndex.value, ['steps', i]);
|
|
106
108
|
</script>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-html="html"
|
|
4
|
+
:class="customClass"
|
|
5
|
+
:style="computedStyle"
|
|
6
|
+
></div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { computed } from 'vue';
|
|
11
|
+
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
html: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
style?: Record<string, string>;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const customClass = computed(() => props.class || '');
|
|
19
|
+
|
|
20
|
+
const computedStyle = computed(() => {
|
|
21
|
+
return {
|
|
22
|
+
width: '100%',
|
|
23
|
+
...props.style,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style scoped>
|
|
29
|
+
/* Ensure HTML content respects container boundaries */
|
|
30
|
+
:deep(*) {
|
|
31
|
+
max-width: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Style common HTML elements to match Lumina theme */
|
|
35
|
+
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
|
|
36
|
+
color: var(--lumina-color-text);
|
|
37
|
+
font-family: var(--lumina-font-heading);
|
|
38
|
+
margin-top: var(--lumina-space-md);
|
|
39
|
+
margin-bottom: var(--lumina-space-sm);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:deep(p) {
|
|
43
|
+
color: var(--lumina-color-text);
|
|
44
|
+
line-height: var(--lumina-leading-relaxed);
|
|
45
|
+
margin-bottom: var(--lumina-space-md);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:deep(a) {
|
|
49
|
+
color: var(--lumina-color-primary);
|
|
50
|
+
text-decoration: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
:deep(a:hover) {
|
|
54
|
+
text-decoration: underline;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:deep(ul), :deep(ol) {
|
|
58
|
+
margin-left: var(--lumina-space-lg);
|
|
59
|
+
margin-bottom: var(--lumina-space-md);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:deep(li) {
|
|
63
|
+
margin-bottom: var(--lumina-space-xs);
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
:style="containerStyle">
|
|
6
6
|
<img :src="src" :alt="alt || ''" :class="[
|
|
7
7
|
'w-full h-full transition-opacity duration-700',
|
|
8
|
-
|
|
8
|
+
objectFitClass,
|
|
9
9
|
roundedClass,
|
|
10
10
|
isLoaded ? 'opacity-100' : 'opacity-0'
|
|
11
|
-
]" @load="onLoad" @error="onError" />
|
|
11
|
+
]" :style="imageStyle" @load="onLoad" @error="onError" />
|
|
12
12
|
</component>
|
|
13
13
|
</template>
|
|
14
14
|
|
|
@@ -19,12 +19,15 @@ const props = defineProps<{
|
|
|
19
19
|
src: string;
|
|
20
20
|
alt?: string;
|
|
21
21
|
fill?: boolean;
|
|
22
|
+
fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
|
|
23
|
+
position?: string;
|
|
22
24
|
rounded?: string;
|
|
23
25
|
containerClass?: any;
|
|
24
26
|
containerStyle?: any;
|
|
25
27
|
href?: string;
|
|
26
28
|
target?: '_blank' | '_self';
|
|
27
29
|
class?: any;
|
|
30
|
+
style?: Record<string, string>;
|
|
28
31
|
}>();
|
|
29
32
|
|
|
30
33
|
const isLoaded = ref(false);
|
|
@@ -39,6 +42,30 @@ const onError = (e: any) => {
|
|
|
39
42
|
e.target.src = 'https://placehold.co/800x600/1a1a1a/666?text=Image+Not+Found';
|
|
40
43
|
};
|
|
41
44
|
|
|
45
|
+
const objectFitClass = computed(() => {
|
|
46
|
+
if (props.fit) {
|
|
47
|
+
const fitMap: Record<string, string> = {
|
|
48
|
+
'cover': 'object-cover',
|
|
49
|
+
'contain': 'object-contain',
|
|
50
|
+
'fill': 'object-fill',
|
|
51
|
+
'none': 'object-none',
|
|
52
|
+
'scale-down': 'object-scale-down',
|
|
53
|
+
};
|
|
54
|
+
return fitMap[props.fit] || 'object-cover';
|
|
55
|
+
}
|
|
56
|
+
return props.fill !== false ? 'object-cover' : 'object-contain';
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const imageStyle = computed(() => {
|
|
60
|
+
const style: Record<string, string> = {
|
|
61
|
+
...props.style,
|
|
62
|
+
};
|
|
63
|
+
if (props.position) {
|
|
64
|
+
style.objectPosition = props.position;
|
|
65
|
+
}
|
|
66
|
+
return style;
|
|
67
|
+
});
|
|
68
|
+
|
|
42
69
|
const roundedClass = computed(() => {
|
|
43
70
|
if (props.fill !== false && !props.rounded) return '';
|
|
44
71
|
const map: Record<string, string> = {
|