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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<!-- Logo -->
|
|
4
4
|
<div class="flex items-center gap-3">
|
|
5
5
|
<span class="font-heading font-bold text-white tracking-wider">LUMINA STUDIO</span>
|
|
6
|
-
<router-link :to="{ name: 'dashboard' }" class="text-white/40 hover:text-white text-xs transition">
|
|
6
|
+
<router-link v-if="!embedMode" :to="{ name: 'dashboard' }" class="text-white/40 hover:text-white text-xs transition">
|
|
7
7
|
<i class="ph-thin ph-arrow-left mr-1"></i> Exit
|
|
8
8
|
</router-link>
|
|
9
9
|
</div>
|
|
@@ -52,7 +52,13 @@
|
|
|
52
52
|
<i class="ph-thin ph-arrow-u-up-right"></i>
|
|
53
53
|
</button>
|
|
54
54
|
<div class="w-px h-4 bg-[#444] mx-1"></div>
|
|
55
|
-
<button @click="
|
|
55
|
+
<button v-if="embedMode" @click="emitSave" :disabled="isSaving"
|
|
56
|
+
class="min-w-[80px] px-3 py-1 bg-green-600 hover:bg-green-500 disabled:bg-green-800 text-white text-xs font-bold rounded transition flex items-center justify-center gap-1">
|
|
57
|
+
<i v-if="isSaving" class="ph-thin ph-spinner ph-spin"></i>
|
|
58
|
+
<i v-else class="ph-thin ph-floppy-disk"></i>
|
|
59
|
+
{{ isSaving ? 'SAVING...' : 'SAVE' }}
|
|
60
|
+
</button>
|
|
61
|
+
<button v-else @click="saveToFirestore" :disabled="isSaving"
|
|
56
62
|
class="min-w-[80px] px-3 py-1 bg-green-600 hover:bg-green-500 disabled:bg-green-800 text-white text-xs font-bold rounded transition flex items-center justify-center gap-1">
|
|
57
63
|
<i v-if="isSaving" class="ph-thin ph-spinner ph-spin"></i>
|
|
58
64
|
<i v-else-if="showSavedMessage" class="ph-thin ph-check"></i>
|
|
@@ -86,6 +92,9 @@ import type { Deck } from '../../core/types';
|
|
|
86
92
|
// import SaveSuccessModal from './SaveSuccessModal.vue';
|
|
87
93
|
import StudioJsonEditor from './StudioJsonEditor.vue';
|
|
88
94
|
|
|
95
|
+
const props = withDefaults(defineProps<{ embedMode?: boolean }>(), { embedMode: false });
|
|
96
|
+
const emit = defineEmits<{ save: [deck: Deck] }>();
|
|
97
|
+
|
|
89
98
|
const editor = useEditor();
|
|
90
99
|
const route = useRoute();
|
|
91
100
|
const { user } = useAuth();
|
|
@@ -150,6 +159,15 @@ const addElement = (type: string) => {
|
|
|
150
159
|
}
|
|
151
160
|
};
|
|
152
161
|
|
|
162
|
+
const emitSave = () => {
|
|
163
|
+
const rawDeck = toRaw(editor.store.state.deck);
|
|
164
|
+
if (!rawDeck) return;
|
|
165
|
+
isSaving.value = true;
|
|
166
|
+
const deckCopy = JSON.parse(JSON.stringify(rawDeck)) as Deck;
|
|
167
|
+
emit('save', deckCopy);
|
|
168
|
+
isSaving.value = false;
|
|
169
|
+
};
|
|
170
|
+
|
|
153
171
|
const saveToFirestore = async () => {
|
|
154
172
|
isSaving.value = true;
|
|
155
173
|
try {
|
|
@@ -66,7 +66,18 @@ export function useElementState(id: MaybeRefOrGetter<string>): UseElementStateRe
|
|
|
66
66
|
out.opacity = s.opacity;
|
|
67
67
|
}
|
|
68
68
|
if (s?.transform) out.transform = s.transform;
|
|
69
|
-
|
|
69
|
+
// Only apply safe animation/visual styles from state, not layout-critical properties
|
|
70
|
+
// This prevents state from breaking component layouts (e.g., display, position, flex properties)
|
|
71
|
+
if (s?.style && typeof s.style === 'object') {
|
|
72
|
+
const safeStyleProps = ['opacity', 'transform', 'transition', 'filter', 'backdropFilter',
|
|
73
|
+
'willChange', 'pointerEvents', 'zIndex', 'color', 'backgroundColor', 'borderColor'];
|
|
74
|
+
Object.entries(s.style).forEach(([key, value]) => {
|
|
75
|
+
// Only copy safe properties that don't affect layout structure
|
|
76
|
+
if (safeStyleProps.includes(key) || key.startsWith('--')) {
|
|
77
|
+
out[key] = value;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
70
81
|
return out;
|
|
71
82
|
});
|
|
72
83
|
|
|
@@ -1,122 +1,128 @@
|
|
|
1
|
-
import type { FlexElementContent } from '../core/types';
|
|
2
|
-
import type { CSSProperties } from 'vue';
|
|
3
|
-
|
|
4
|
-
// Constants
|
|
5
|
-
export const spacingVarMap: Record<string, string> = {
|
|
6
|
-
'none': 'var(--lumina-space-none, 0)',
|
|
7
|
-
'xs': 'var(--lumina-space-xs)',
|
|
8
|
-
'sm': 'var(--lumina-space-sm)',
|
|
9
|
-
'md': 'var(--lumina-space-md)',
|
|
10
|
-
'lg': 'var(--lumina-space-lg)',
|
|
11
|
-
'xl': 'var(--lumina-space-xl)',
|
|
12
|
-
'2xl': 'var(--lumina-space-2xl)',
|
|
13
|
-
'3xl': 'var(--lumina-space-3xl)',
|
|
14
|
-
'4xl': 'var(--lumina-space-4xl)',
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const sizeMap: Record<string, string> = {
|
|
18
|
-
'auto': 'auto',
|
|
19
|
-
'quarter': '25%',
|
|
20
|
-
'third': '33.333%',
|
|
21
|
-
'half': '50%',
|
|
22
|
-
'two-thirds': '66.666%',
|
|
23
|
-
'three-quarters': '75%',
|
|
24
|
-
'full': '100%',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const valignMap: Record<string, string> = {
|
|
28
|
-
'top': 'flex-start',
|
|
29
|
-
'Top': 'flex-start',
|
|
30
|
-
'center': 'center',
|
|
31
|
-
'Center': 'center',
|
|
32
|
-
'bottom': 'flex-end',
|
|
33
|
-
'Bottom': 'flex-end',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const halignMap: Record<string, string> = {
|
|
37
|
-
'left': 'flex-start',
|
|
38
|
-
'Left': 'flex-start',
|
|
39
|
-
'center': 'center',
|
|
40
|
-
'Center': 'center',
|
|
41
|
-
'right': 'flex-end',
|
|
42
|
-
'Right': 'flex-end',
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Helpers
|
|
46
|
-
export const getSizeClass = (size?: string): string => {
|
|
47
|
-
if (!size || size === 'auto') return 'flex-1';
|
|
48
|
-
return '';
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export const getFlexItemStyle = (element: { size?: string }, directionVertical: boolean): CSSProperties => {
|
|
52
|
-
const size = element.size || 'auto';
|
|
53
|
-
if (size === 'auto') return { flex: '1 1 auto' };
|
|
54
|
-
return {
|
|
55
|
-
flex: `0 0 ${sizeMap[size]}`,
|
|
56
|
-
maxWidth: !directionVertical ? sizeMap[size] : undefined,
|
|
57
|
-
maxHeight: directionVertical ? sizeMap[size] : undefined,
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export const getFlexContainerStyle = (element: FlexElementContent): CSSProperties => {
|
|
62
|
-
const valign = element.valign || 'center';
|
|
63
|
-
const halign = element.halign || 'left';
|
|
64
|
-
const gap = element.gap || 'md';
|
|
65
|
-
const padding = element.padding || 'lg';
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
1
|
+
import type { FlexElementContent } from '../core/types';
|
|
2
|
+
import type { CSSProperties } from 'vue';
|
|
3
|
+
|
|
4
|
+
// Constants
|
|
5
|
+
export const spacingVarMap: Record<string, string> = {
|
|
6
|
+
'none': 'var(--lumina-space-none, 0)',
|
|
7
|
+
'xs': 'var(--lumina-space-xs)',
|
|
8
|
+
'sm': 'var(--lumina-space-sm)',
|
|
9
|
+
'md': 'var(--lumina-space-md)',
|
|
10
|
+
'lg': 'var(--lumina-space-lg)',
|
|
11
|
+
'xl': 'var(--lumina-space-xl)',
|
|
12
|
+
'2xl': 'var(--lumina-space-2xl)',
|
|
13
|
+
'3xl': 'var(--lumina-space-3xl)',
|
|
14
|
+
'4xl': 'var(--lumina-space-4xl)',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const sizeMap: Record<string, string> = {
|
|
18
|
+
'auto': 'auto',
|
|
19
|
+
'quarter': '25%',
|
|
20
|
+
'third': '33.333%',
|
|
21
|
+
'half': '50%',
|
|
22
|
+
'two-thirds': '66.666%',
|
|
23
|
+
'three-quarters': '75%',
|
|
24
|
+
'full': '100%',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const valignMap: Record<string, string> = {
|
|
28
|
+
'top': 'flex-start',
|
|
29
|
+
'Top': 'flex-start',
|
|
30
|
+
'center': 'center',
|
|
31
|
+
'Center': 'center',
|
|
32
|
+
'bottom': 'flex-end',
|
|
33
|
+
'Bottom': 'flex-end',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const halignMap: Record<string, string> = {
|
|
37
|
+
'left': 'flex-start',
|
|
38
|
+
'Left': 'flex-start',
|
|
39
|
+
'center': 'center',
|
|
40
|
+
'Center': 'center',
|
|
41
|
+
'right': 'flex-end',
|
|
42
|
+
'Right': 'flex-end',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Helpers
|
|
46
|
+
export const getSizeClass = (size?: string): string => {
|
|
47
|
+
if (!size || size === 'auto') return 'flex-1';
|
|
48
|
+
return '';
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const getFlexItemStyle = (element: { size?: string }, directionVertical: boolean): CSSProperties => {
|
|
52
|
+
const size = element.size || 'auto';
|
|
53
|
+
if (size === 'auto') return { flex: '1 1 auto' };
|
|
54
|
+
return {
|
|
55
|
+
flex: `0 0 ${sizeMap[size]}`,
|
|
56
|
+
maxWidth: !directionVertical ? sizeMap[size] : undefined,
|
|
57
|
+
maxHeight: directionVertical ? sizeMap[size] : undefined,
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const getFlexContainerStyle = (element: FlexElementContent): CSSProperties => {
|
|
62
|
+
const valign = element.valign || 'center';
|
|
63
|
+
const halign = element.halign || 'left';
|
|
64
|
+
const gap = element.gap || 'md';
|
|
65
|
+
const padding = element.padding || 'lg';
|
|
66
|
+
const direction = element.direction || 'vertical';
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
display: 'flex',
|
|
70
|
+
flexDirection: direction === 'horizontal' ? 'row' : 'column',
|
|
71
|
+
justifyContent: direction === 'horizontal'
|
|
72
|
+
? halignMap[halign] as CSSProperties['justifyContent']
|
|
73
|
+
: valignMap[valign] as CSSProperties['justifyContent'],
|
|
74
|
+
alignItems: direction === 'horizontal'
|
|
75
|
+
? valignMap[valign] as CSSProperties['alignItems']
|
|
76
|
+
: halignMap[halign] as CSSProperties['alignItems'],
|
|
77
|
+
gap: spacingVarMap[gap],
|
|
78
|
+
padding: spacingVarMap[padding],
|
|
79
|
+
...(element.style || {}),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Deprecated: kept for backward compat if needed, but we will update consumers
|
|
84
|
+
export const getElementStyle = getFlexItemStyle;
|
|
85
|
+
export const getContentStyle = (element: FlexElementContent & { size?: string }): CSSProperties => {
|
|
86
|
+
// Legacy combined style
|
|
87
|
+
return {
|
|
88
|
+
...getFlexItemStyle(element, false), // Default assumption
|
|
89
|
+
...getFlexContainerStyle(element)
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const getTopLevelStyle = (): CSSProperties => ({
|
|
94
|
+
display: 'flex',
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
padding: spacingVarMap['lg'],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Alignment for the main flex slide container (all elements as a group).
|
|
101
|
+
* When direction is horizontal: justify-content = halign, align-items = valign.
|
|
102
|
+
* When direction is vertical: justify-content = valign, align-items = halign.
|
|
103
|
+
*/
|
|
104
|
+
export const getFlexSlideMainStyle = (
|
|
105
|
+
direction: 'horizontal' | 'vertical' | undefined,
|
|
106
|
+
halign: string | undefined,
|
|
107
|
+
valign: string | undefined
|
|
108
|
+
): CSSProperties => {
|
|
109
|
+
const jc = direction === 'vertical' ? valignMap[valign || 'top'] : halignMap[halign || 'left'];
|
|
110
|
+
const ai = direction === 'vertical' ? halignMap[halign || 'left'] : valignMap[valign || 'top'];
|
|
111
|
+
return {
|
|
112
|
+
justifyContent: jc as CSSProperties['justifyContent'],
|
|
113
|
+
alignItems: ai as CSSProperties['alignItems'],
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const getRoundedClass = (rounded?: string, fill?: boolean) => {
|
|
118
|
+
if (fill !== false && !rounded) return 'w-full h-full';
|
|
119
|
+
const map: Record<string, string> = {
|
|
120
|
+
'none': 'w-full h-full rounded-none',
|
|
121
|
+
'sm': 'w-full h-full rounded-sm',
|
|
122
|
+
'md': 'w-full h-full rounded-md',
|
|
123
|
+
'lg': 'w-full h-full rounded-lg',
|
|
124
|
+
'xl': 'w-full h-full rounded-xl',
|
|
125
|
+
'full': 'w-full h-full rounded-full',
|
|
126
|
+
};
|
|
127
|
+
return map[rounded || 'lg'] || 'w-full h-full rounded-lg';
|
|
128
|
+
};
|