lumina-slides 9.0.4 → 9.0.6
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 +21984 -19455
- package/dist/lumina-slides.umd.cjs +223 -223
- package/dist/style.css +1 -1
- package/package.json +3 -1
- package/src/components/LandingPage.vue +1 -1
- package/src/components/LuminaDeck.vue +237 -232
- package/src/components/base/LuminaElement.vue +2 -0
- package/src/components/layouts/LayoutFeatures.vue +123 -123
- package/src/components/layouts/LayoutFlex.vue +212 -172
- package/src/components/layouts/LayoutStatement.vue +5 -2
- package/src/components/layouts/LayoutSteps.vue +108 -108
- package/src/components/parts/FlexHtml.vue +65 -0
- package/src/components/parts/FlexImage.vue +81 -54
- package/src/components/site/SiteDocs.vue +3313 -3182
- package/src/components/site/SiteExamples.vue +66 -66
- package/src/components/studio/EditorLayoutChart.vue +18 -0
- package/src/components/studio/EditorLayoutCustom.vue +18 -0
- package/src/components/studio/EditorLayoutVideo.vue +18 -0
- package/src/components/studio/LuminaStudioEmbed.vue +68 -0
- package/src/components/studio/StudioEmbedRoot.vue +19 -0
- package/src/components/studio/StudioInspector.vue +1113 -7
- package/src/components/studio/StudioSettings.vue +658 -7
- package/src/components/studio/StudioToolbar.vue +20 -2
- package/src/composables/useElementState.ts +12 -1
- package/src/composables/useFlexLayout.ts +128 -122
- package/src/core/Lumina.ts +174 -113
- package/src/core/animationConfig.ts +10 -0
- package/src/core/elementController.ts +18 -0
- package/src/core/elementResolver.ts +4 -2
- package/src/core/schema.ts +503 -478
- package/src/core/store.ts +465 -465
- package/src/core/types.ts +59 -14
- package/src/index.ts +2 -2
- package/src/utils/templateInterpolation.ts +52 -52
- package/src/views/DeckView.vue +313 -313
|
@@ -1,172 +1,212 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
-
<div class="flex-layout w-full h-screen flex flex-col overflow-y-auto lg:overflow-hidden"
|
|
4
|
-
:style="containerStyle">
|
|
5
|
-
<div :class="['flex-1 flex w-full min-h-full lg:h-full', directionClass]"
|
|
6
|
-
:style="mainFlexStyle">
|
|
7
|
-
<template v-for="(element, i) in data.elements" :key="i">
|
|
8
|
-
<!-- Image Element -->
|
|
9
|
-
<LuminaElement v-if="element.type === 'image'" :id="elemId(i)" :class="['flex-item', getSizeClass(element.size)]" :style="getElementStyle(element)">
|
|
10
|
-
<FlexImage :src="element.src" :alt="element.alt"
|
|
11
|
-
:fill="element.fill" :
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
:
|
|
22
|
-
:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
:style="
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<BaseSlide :data="data" :slide-index="slideIndex">
|
|
3
|
+
<div class="flex-layout w-full h-screen flex flex-col overflow-y-auto lg:overflow-hidden"
|
|
4
|
+
:style="containerStyle">
|
|
5
|
+
<div :class="['flex-1 flex w-full min-h-full lg:h-full', directionClass]"
|
|
6
|
+
:style="mainFlexStyle">
|
|
7
|
+
<template v-for="(element, i) in data.elements" :key="i">
|
|
8
|
+
<!-- Image 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
|
+
<FlexImage :src="element.src" :alt="element.alt"
|
|
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" />
|
|
14
|
+
</LuminaElement>
|
|
15
|
+
|
|
16
|
+
<!-- Video Element -->
|
|
17
|
+
<LuminaElement v-else-if="element.type === 'video'" :id="elemId(i)"
|
|
18
|
+
:class="['flex-item', getSizeClass(element.size), element.class]"
|
|
19
|
+
:style="getElementStyle(element)">
|
|
20
|
+
<VideoPlayer :class="getRoundedClass(element.rounded, element.fill)" :src="element.src"
|
|
21
|
+
:poster="element.poster" :autoplay="element.autoplay ?? false" :loop="element.loop ?? false"
|
|
22
|
+
:muted="element.muted ?? false" :controls="element.controls ?? true"
|
|
23
|
+
:object-fit="element.fill !== false ? 'cover' : 'contain'" />
|
|
24
|
+
</LuminaElement>
|
|
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) -->
|
|
32
|
+
<LuminaElement v-else-if="element.type === 'content'" :id="elemId(i)"
|
|
33
|
+
:class="['flex-item flex h-full', element.direction === 'horizontal' ? 'flex-row' : 'flex-col', getSizeClass(element.size), element.class]"
|
|
34
|
+
:style="getContentStyle(element)">
|
|
35
|
+
<template v-for="(child, j) in element.elements" :key="j">
|
|
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)">
|
|
59
|
+
<component :is="getChildComponent(child.type)" v-bind="child" @action="handleAction" />
|
|
60
|
+
</LuminaElement>
|
|
61
|
+
</template>
|
|
62
|
+
</LuminaElement>
|
|
63
|
+
|
|
64
|
+
<!-- Top-level elements (title, text, bullets, etc.) -->
|
|
65
|
+
<LuminaElement v-else :id="elemId(i)" :class="['flex-item', getSizeClass(element.size)]"
|
|
66
|
+
:style="{ ...getTopLevelStyle(), width: data.direction === 'vertical' ? '100%' : 'auto' }">
|
|
67
|
+
<component :is="getChildComponent(element.type)" v-bind="element" @action="handleAction" />
|
|
68
|
+
</LuminaElement>
|
|
69
|
+
</template>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</BaseSlide>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
import { computed, inject } from 'vue';
|
|
77
|
+
import BaseSlide from '../base/BaseSlide.vue';
|
|
78
|
+
import VideoPlayer from '../base/VideoPlayer.vue';
|
|
79
|
+
import { resolveId } from '../../core/elementResolver';
|
|
80
|
+
import { StoreKey } from '../../core/store';
|
|
81
|
+
import type { SlideFlex, FlexElement, FlexSize, FlexElementContent } from '../../core/types';
|
|
82
|
+
import FlexImage from '../parts/FlexImage.vue';
|
|
83
|
+
import FlexTitle from '../parts/FlexTitle.vue';
|
|
84
|
+
import FlexText from '../parts/FlexText.vue';
|
|
85
|
+
import FlexButton from '../parts/FlexButton.vue';
|
|
86
|
+
import FlexBullets from '../parts/FlexBullets.vue';
|
|
87
|
+
import FlexOrdered from '../parts/FlexOrdered.vue';
|
|
88
|
+
import FlexTimeline from '../parts/FlexTimeline.vue';
|
|
89
|
+
import FlexStepper from '../parts/FlexStepper.vue';
|
|
90
|
+
import FlexSpacer from '../parts/FlexSpacer.vue';
|
|
91
|
+
import FlexHtml from '../parts/FlexHtml.vue';
|
|
92
|
+
import { bus } from '../../core/events';
|
|
93
|
+
import {
|
|
94
|
+
spacingVarMap,
|
|
95
|
+
getSizeClass,
|
|
96
|
+
getFlexItemStyle,
|
|
97
|
+
getFlexContainerStyle,
|
|
98
|
+
getTopLevelStyle,
|
|
99
|
+
getFlexSlideMainStyle,
|
|
100
|
+
getRoundedClass
|
|
101
|
+
} from '../../composables/useFlexLayout';
|
|
102
|
+
|
|
103
|
+
const props = defineProps<{
|
|
104
|
+
data: SlideFlex;
|
|
105
|
+
slideIndex?: number;
|
|
106
|
+
}>();
|
|
107
|
+
|
|
108
|
+
const emit = defineEmits(['action']);
|
|
109
|
+
const store = inject(StoreKey);
|
|
110
|
+
const slideIndex = computed(() => props.slideIndex ?? store?.state.currentIndex ?? 0);
|
|
111
|
+
const elemId = (i: number) => resolveId(props.data, slideIndex.value, ['elements', i]);
|
|
112
|
+
const childId = (i: number, j: number) => resolveId(props.data, slideIndex.value, ['elements', i, 'elements', j]);
|
|
113
|
+
|
|
114
|
+
const containerStyle = computed(() => ({
|
|
115
|
+
padding: spacingVarMap[props.data.padding || 'none'],
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
const directionClass = computed(() =>
|
|
119
|
+
props.data.direction === 'vertical' ? 'flex-col' : 'flex-col lg:flex-row'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const mainFlexStyle = computed(() => ({
|
|
123
|
+
gap: spacingVarMap[props.data.gap || 'none'],
|
|
124
|
+
...getFlexSlideMainStyle(props.data.direction, props.data.halign, props.data.valign),
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
// Wrapper for shared logic requiring vertical flag
|
|
128
|
+
const getElementStyle = (element: FlexElement & { size?: FlexSize }) => {
|
|
129
|
+
return getFlexItemStyle(element, props.data.direction === 'vertical');
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const getContentStyle = (element: FlexElementContent & { size?: FlexSize }) => {
|
|
133
|
+
const isVertical = props.data.direction === 'vertical';
|
|
134
|
+
const contentDirection = element.direction || 'vertical';
|
|
135
|
+
return {
|
|
136
|
+
...getFlexItemStyle(element, isVertical),
|
|
137
|
+
...getFlexContainerStyle(element),
|
|
138
|
+
// Force full height/width for alignment space
|
|
139
|
+
height: isVertical ? 'auto' : '100%',
|
|
140
|
+
width: isVertical ? '100%' : 'auto',
|
|
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 || {})
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleAction = (payload: any) => {
|
|
155
|
+
emit('action', payload);
|
|
156
|
+
bus.emit('action', payload);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// CHILD ELEMENT COMPONENTS - All using CSS variables
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
// Child components extracted to ../parts/
|
|
165
|
+
|
|
166
|
+
const getChildComponent = (type: string) => {
|
|
167
|
+
const components: Record<string, any> = {
|
|
168
|
+
'title': FlexTitle,
|
|
169
|
+
'text': FlexText,
|
|
170
|
+
'image': FlexImage,
|
|
171
|
+
'video': VideoPlayer,
|
|
172
|
+
'bullets': FlexBullets,
|
|
173
|
+
'ordered': FlexOrdered,
|
|
174
|
+
'button': FlexButton,
|
|
175
|
+
'timeline': FlexTimeline,
|
|
176
|
+
'stepper': FlexStepper,
|
|
177
|
+
'spacer': FlexSpacer,
|
|
178
|
+
'html': FlexHtml,
|
|
179
|
+
};
|
|
180
|
+
return components[type] || 'div';
|
|
181
|
+
};
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<style scoped>
|
|
185
|
+
.flex-layout {
|
|
186
|
+
min-height: 100vh;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@media (max-width: 1023px) {
|
|
190
|
+
.flex-item {
|
|
191
|
+
flex: 0 0 auto !important;
|
|
192
|
+
width: 100% !important;
|
|
193
|
+
max-width: 100% !important;
|
|
194
|
+
max-height: none !important;
|
|
195
|
+
min-height: auto !important;
|
|
196
|
+
height: auto !important;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Images in stacked flex should have a reasonable height */
|
|
200
|
+
.flex-item:has(img),
|
|
201
|
+
.flex-item img {
|
|
202
|
+
height: 40vh !important;
|
|
203
|
+
min-height: 300px !important;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.flex-layout {
|
|
207
|
+
overflow-y: auto !important;
|
|
208
|
+
height: auto !important;
|
|
209
|
+
min-height: 100vh;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
</style>
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
<div
|
|
4
4
|
:class="['flex flex-col justify-center items-center text-center relative', data.sizing === 'container' ? 'h-full' : 'min-h-screen']"
|
|
5
5
|
:style="{ paddingTop: 'var(--lumina-space-xl)', paddingLeft: 'var(--lumina-space-xl)', paddingRight: 'var(--lumina-space-xl)', paddingBottom: 'calc(var(--lumina-space-xl) + var(--lumina-safe-area-bottom))' }">
|
|
6
|
-
<div :class="[data.class || '', 'max-w-5xl z-10']">
|
|
6
|
+
<div :class="[data.class || '', 'flex flex-col items-center max-w-5xl z-10']">
|
|
7
7
|
<!-- Tag -->
|
|
8
|
-
<LuminaElement v-if="data.tag" :id="tagId" tag="span" class="
|
|
8
|
+
<LuminaElement v-if="data.tag" :id="tagId" tag="span" class="rounded-full font-medium uppercase" :style="{
|
|
9
|
+
display: 'inline-block',
|
|
9
10
|
color: 'var(--lumina-color-primary)',
|
|
10
11
|
border: '1px solid var(--lumina-color-border)',
|
|
11
12
|
backgroundColor: 'rgba(var(--lumina-color-text-rgb), 0.05)',
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
|
|
25
26
|
<!-- Subtitle -->
|
|
26
27
|
<LuminaElement v-if="data.subtitle" :id="subtitleId" tag="p" class="max-w-3xl mx-auto" :style="{
|
|
28
|
+
display: 'block',
|
|
27
29
|
color: 'var(--lumina-color-text-safe, var(--lumina-color-text))',
|
|
28
30
|
opacity: 0.8,
|
|
29
31
|
fontWeight: 'normal',
|
|
@@ -57,6 +59,7 @@ const titleId = computed(() => resolveId(props.data, slideIndex.value, ['title']
|
|
|
57
59
|
const subtitleId = computed(() => resolveId(props.data, slideIndex.value, ['subtitle']));
|
|
58
60
|
|
|
59
61
|
const titleStyle = computed(() => ({
|
|
62
|
+
display: 'block',
|
|
60
63
|
fontSize: props.data.sizing === 'container'
|
|
61
64
|
? 'var(--lumina-text-5xl)'
|
|
62
65
|
: 'clamp(var(--lumina-text-4xl), 10vw, var(--lumina-text-7xl))',
|