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