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
@@ -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="saveToFirestore" :disabled="isSaving"
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
- if (s?.style && typeof s.style === 'object') Object.assign(out, s.style);
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
- return {
68
- display: 'flex',
69
- flexDirection: 'column', // Content containers are always column based for children
70
- justifyContent: valignMap[valign] as CSSProperties['justifyContent'],
71
- alignItems: halignMap[halign] as CSSProperties['alignItems'],
72
- gap: spacingVarMap[gap],
73
- padding: spacingVarMap[padding],
74
- };
75
- };
76
-
77
- // Deprecated: kept for backward compat if needed, but we will update consumers
78
- export const getElementStyle = getFlexItemStyle;
79
- export const getContentStyle = (element: FlexElementContent & { size?: string }): CSSProperties => {
80
- // Legacy combined style
81
- return {
82
- ...getFlexItemStyle(element, false), // Default assumption
83
- ...getFlexContainerStyle(element)
84
- };
85
- };
86
-
87
- export const getTopLevelStyle = (): CSSProperties => ({
88
- display: 'flex',
89
- alignItems: 'center',
90
- padding: spacingVarMap['lg'],
91
- });
92
-
93
- /**
94
- * Alignment for the main flex slide container (all elements as a group).
95
- * When direction is horizontal: justify-content = halign, align-items = valign.
96
- * When direction is vertical: justify-content = valign, align-items = halign.
97
- */
98
- export const getFlexSlideMainStyle = (
99
- direction: 'horizontal' | 'vertical' | undefined,
100
- halign: string | undefined,
101
- valign: string | undefined
102
- ): CSSProperties => {
103
- const jc = direction === 'vertical' ? valignMap[valign || 'top'] : halignMap[halign || 'left'];
104
- const ai = direction === 'vertical' ? halignMap[halign || 'left'] : valignMap[valign || 'top'];
105
- return {
106
- justifyContent: jc as CSSProperties['justifyContent'],
107
- alignItems: ai as CSSProperties['alignItems'],
108
- };
109
- };
110
-
111
- export const getRoundedClass = (rounded?: string, fill?: boolean) => {
112
- if (fill !== false && !rounded) return 'w-full h-full';
113
- const map: Record<string, string> = {
114
- 'none': 'w-full h-full rounded-none',
115
- 'sm': 'w-full h-full rounded-sm',
116
- 'md': 'w-full h-full rounded-md',
117
- 'lg': 'w-full h-full rounded-lg',
118
- 'xl': 'w-full h-full rounded-xl',
119
- 'full': 'w-full h-full rounded-full',
120
- };
121
- return map[rounded || 'lg'] || 'w-full h-full rounded-lg';
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
+ };