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
package/src/core/types.ts CHANGED
@@ -160,7 +160,9 @@ export interface FlexElementImage {
160
160
  /** Fill entire container edge-to-edge. Default: true */
161
161
  fill?: boolean;
162
162
  /** Object-fit mode when fill is true. Default: 'cover' */
163
- fit?: 'cover' | 'contain';
163
+ fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
164
+ /** Object-position for image placement. Default: 'center' */
165
+ position?: string;
164
166
  /** Border radius. Default: 'none' when fill, 'lg' otherwise */
165
167
  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
166
168
  /** Link URL when image is clicked. */
@@ -169,6 +171,8 @@ export interface FlexElementImage {
169
171
  target?: '_blank' | '_self';
170
172
  /** Custom CSS class */
171
173
  class?: string;
174
+ /** Custom CSS style object */
175
+ style?: Record<string, string>;
172
176
  }
173
177
 
174
178
  /**
@@ -248,13 +252,30 @@ export interface FlexElementSpacer {
248
252
  }
249
253
 
250
254
  /**
251
- * Content container - Groups child elements vertically with alignment control.
255
+ * HTML element - Raw HTML content.
256
+ */
257
+ export interface FlexElementHtml {
258
+ type: 'html';
259
+ /** Optional id for element control (engine.element(id)). */
260
+ id?: string;
261
+ /** Raw HTML string to render */
262
+ html: string;
263
+ /** Custom CSS class */
264
+ class?: string;
265
+ /** Custom CSS style object */
266
+ style?: Record<string, string>;
267
+ }
268
+
269
+ /**
270
+ * Content container - Groups child elements with alignment control.
252
271
  */
253
272
  export interface FlexElementContent {
254
273
  type: 'content';
255
274
  /** Optional id for element control (engine.element(id)). */
256
275
  id?: string;
257
276
  elements: FlexChildElement[];
277
+ /** Layout direction. Default: 'vertical' */
278
+ direction?: 'horizontal' | 'vertical';
258
279
  /** Vertical alignment of content. Default: 'center' */
259
280
  valign?: VAlign;
260
281
  /** Horizontal alignment of content. Default: 'left' */
@@ -263,10 +284,15 @@ export interface FlexElementContent {
263
284
  gap?: SpacingToken;
264
285
  /** Internal padding. Default: 'lg' */
265
286
  padding?: SpacingToken;
287
+ /** Custom CSS class */
288
+ class?: string;
289
+ /** Custom CSS style object */
290
+ style?: Record<string, string>;
266
291
  }
267
292
 
268
293
  /**
269
294
  * Child elements that can appear inside a content container.
295
+ * Supports nested content containers for complex layouts.
270
296
  */
271
297
  export type FlexChildElement =
272
298
  | FlexElementTitle
@@ -276,7 +302,10 @@ export type FlexChildElement =
276
302
  | FlexElementButton
277
303
  | FlexElementTimeline
278
304
  | FlexElementStepper
279
- | FlexElementSpacer;
305
+ | FlexElementSpacer
306
+ | FlexElementHtml
307
+ | FlexElementImage
308
+ | FlexElementContent;
280
309
 
281
310
  /**
282
311
  * Top-level flex elements that can have size.
@@ -285,6 +314,7 @@ export type FlexElement =
285
314
  | (FlexElementImage & { size?: FlexSize })
286
315
  | (FlexElementVideo & { size?: FlexSize })
287
316
  | (FlexElementContent & { size?: FlexSize })
317
+ | (FlexElementHtml & { size?: FlexSize })
288
318
  | (FlexElementTitle & { size?: FlexSize })
289
319
  | (FlexElementText & { size?: FlexSize })
290
320
  | (FlexElementBullets & { size?: FlexSize })
@@ -561,7 +591,7 @@ export interface VideoProperties {
561
591
  * "type": "statement",
562
592
  * "meta": { "orbColor": "#3b82f6" },
563
593
  * "tag": "Declarative Engine",
564
- * "title": "Lumina V8",
594
+ * "title": "Lumina V9",
565
595
  * "subtitle": "A highly modular, performance-first presentation engine."
566
596
  * }
567
597
  * ```
@@ -864,8 +894,7 @@ export interface DeckMeta {
864
894
  */
865
895
  initialElementState?: InitialElementState;
866
896
  /**
867
- * Element control defaults. Can be set in deck JSON so the deck is self-contained.
868
- * defaultVisible: false = start all elements hidden; override per-id via initialElementState.
897
+ * Element control defaults. Elements are hidden by default. Set defaultVisible: true to show all.
869
898
  */
870
899
  elementControl?: { defaultVisible?: boolean };
871
900
  /** Default reveal options for all slides. Overridden by slide.reveal. */
@@ -881,8 +910,8 @@ export interface DeckMeta {
881
910
  * Complete deck object. Primary input to `engine.load(deck)`.
882
911
  *
883
912
  * @description
884
- * Must have `meta.title` and `slides` (array of slide objects). meta.initialElementState and
885
- * meta.elementControl enable reveal-on-demand. meta.effects overrides animation (see LuminaAnimationOptions).
913
+ * Must have `meta.title` and `slides` (array of slide objects). meta.initialElementState overrides per-element
914
+ * initial state. meta.elementControl.defaultVisible: true shows all. meta.effects overrides animation.
886
915
  *
887
916
  * @example
888
917
  * { meta: { title: "Demo", initialElementState: { "s0-title": { visible: false } } }, slides: [{ type: "statement", title: "Hi" }] }
@@ -1358,6 +1387,12 @@ export interface LuminaAnimationOptions {
1358
1387
  /** GSAP ease for reveal. Default: 'power2.out'. */
1359
1388
  revealEase?: string;
1360
1389
 
1390
+ // --- Default cascade (when no meta.reveal or slide.reveal) ---
1391
+ /** Delay in ms between elements for implicit default cascade. Default: 120. Override via meta.effects. */
1392
+ defaultCascadeDelayMs?: number;
1393
+ /** Per-element duration (s) for implicit default cascade. Default: 0.3. Override via meta.effects. */
1394
+ defaultCascadeDuration?: number;
1395
+
1361
1396
  // --- element().animate() ---
1362
1397
  /** Default duration in seconds. Default: 0.5. */
1363
1398
  elementDuration?: number;
@@ -1420,7 +1455,7 @@ export interface LuminaAnimationOptions {
1420
1455
  *
1421
1456
  * @example
1422
1457
  * new Lumina("#app", { theme: "midnight", loop: true, animation: { durationIn: 1.2 } });
1423
- * new Lumina("#app", { elementControl: { defaultVisible: false } });
1458
+ * new Lumina("#app", { elementControl: { defaultVisible: true } }); // legacy: show all
1424
1459
  *
1425
1460
  * @see Lumina
1426
1461
  * @see LuminaAnimationOptions
@@ -1447,16 +1482,23 @@ export interface LuminaOptions {
1447
1482
  /** Animation settings. */
1448
1483
  animation?: LuminaAnimationOptions;
1449
1484
  /**
1450
- * Element control defaults. Use for reveal-on-demand: set defaultVisible to false so all
1451
- * elements start hidden; override per-id via meta.initialElementState. Then reveal with
1452
- * engine.element(id).show().
1485
+ * Element control defaults. Elements are hidden by default and cascade-reveal on slide enter.
1486
+ * Set defaultVisible: true to show all elements immediately (legacy behavior).
1453
1487
  */
1454
1488
  elementControl?: {
1455
- /** If false, all elements start hidden unless listed in meta.initialElementState. Default: true. */
1489
+ /** If true, all elements start visible. Default: false (elements hidden, cascade on enter). */
1456
1490
  defaultVisible?: boolean;
1457
1491
  };
1458
1492
  /** Enable Studio (Page Builder) mode. */
1459
1493
  studio?: boolean;
1494
+ /**
1495
+ * Enable Studio Embed mode: same editor as Studio but without back button;
1496
+ * Save button emits a "save" event with the deck JSON instead of persisting to Firestore.
1497
+ * Use with vanilla JS: new Lumina("#app", { studioEmbed: true, initialDeck: deck }); engine.on("save", (d) => { ... }).
1498
+ */
1499
+ studioEmbed?: boolean;
1500
+ /** Initial deck when using studioEmbed. Can be updated later with engine.load(deck). */
1501
+ initialDeck?: Deck;
1460
1502
  }
1461
1503
 
1462
1504
  // --- Events ---
@@ -1481,7 +1523,8 @@ export type LuminaEventType =
1481
1523
  | 'revealComplete'
1482
1524
  | 'revealElement'
1483
1525
  | 'diagram-node-update'
1484
- | 'studio:update';
1526
+ | 'studio:update'
1527
+ | 'save';
1485
1528
 
1486
1529
  /**
1487
1530
  * Payload for the 'slideChange' event.
@@ -1594,6 +1637,8 @@ export interface LuminaEventMap {
1594
1637
  'diagram-node-update': { slideIndex: number; nodeId: string; key: string; value: any };
1595
1638
  /** Fired when Studio updates a node (path, value). Internal. */
1596
1639
  'studio:update': { path: string; value: any };
1640
+ /** Fired when user clicks Save in Studio Embed mode; payload is the current deck JSON. */
1641
+ save: Deck;
1597
1642
  }
1598
1643
 
1599
1644
  /**
package/src/index.ts CHANGED
@@ -13,8 +13,7 @@
13
13
  * **Element control:** `engine.element(id)`, `engine.element(slideIndex, path)`, or `engine.elementInCurrent(path)` → ElementController:
14
14
  * `.show()`, `.hide()`, `.toggle()`, `.opacity(n)`, `.transform(s)`, `.animate({ preset?, from?, to?, duration?, ease? })`.
15
15
  * Presets: fadeUp, fadeIn, scaleIn, slideLeft, slideRight, zoomIn, blurIn, spring, drop, fadeOut. `to` optional when using preset.
16
- * Ids: `engine.elements(slideIndex)` or `s{N}-{path}` (e.g. s0-tag, s1-features-0). `meta.initialElementState`:
17
- * `{ [id]: { visible?: false } }` to start hidden; then `engine.element(id).show()`.
16
+ * Ids: `engine.elements(slideIndex)` or `s{N}-{path}` (e.g. s0-tag, s1-features-0). Elements hidden by default, cascade on enter. `meta.initialElementState` overrides per-id. `elementControl.defaultVisible: true` shows all.
18
17
  *
19
18
  * **Helpers:** `hideAll(slideIndex?)`, `showAll(slideIndex?)`, `showOnly(ids?)`, `hideAllExcept(exceptIds?)`,
20
19
  * `resetSlide(slideIndex?)`, `delay(ms)`, `revealInSequence(slideIndex?, { delayMs?, staggerMode?, preset?, from?, to?, duration?, ease?, hideFirst?, only?, exclude? })`.
@@ -175,4 +174,5 @@ export type {
175
174
 
176
175
  export { default as LuminaDeck } from './components/LuminaDeck.vue';
177
176
  export { default as LuminaStudio } from './components/studio/LuminaStudio.vue';
177
+ export { default as LuminaStudioEmbed } from './components/studio/LuminaStudioEmbed.vue';
178
178
  import './style/main.css';
@@ -1,52 +1,52 @@
1
- /**
2
- * Template interpolation for Lumina slide content.
3
- *
4
- * Replaces `{{key}}` placeholders in strings with values from a key-value store
5
- * (e.g. engine.data / store.userData). When the store changes, any component
6
- * that uses the interpolated result will reactively update.
7
- *
8
- * @example
9
- * interpolateString('Hello {{name}}', { name: 'World' }) // 'Hello World'
10
- * interpolateObject({ title: '{{product}}', subtitle: 'v{{version}}' }, { product: 'Lumina', version: 1 })
11
- * // { title: 'Lumina', subtitle: 'v1' }
12
- */
13
-
14
- /** Matches {{key}} or {{ key }}. Key: alphanumeric, underscore. */
15
- const TAG_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
16
-
17
- /**
18
- * Replaces all {{key}} placeholders in a string with values from `data`.
19
- * Missing keys render as empty string. Non-string values are coerced with String().
20
- *
21
- * @param str - Raw string possibly containing `{{key}}` tags.
22
- * @param data - Key-value map (e.g. engine.data / store.state.userData).
23
- * @returns Interpolated string.
24
- */
25
- export function interpolateString(str: string, data: Record<string, unknown>): string {
26
- if (typeof str !== 'string') return str;
27
- return str.replace(TAG_RE, (_, key: string) => {
28
- const v = data[key];
29
- return v == null ? '' : String(v);
30
- });
31
- }
32
-
33
- /**
34
- * Recursively interpolates all string values in an object/array with `{{key}}`
35
- * placeholders. Other types (number, boolean, null, etc.) are returned unchanged.
36
- * Does not mutate the input; returns a new structure.
37
- *
38
- * @param obj - Object, array, or primitive (strings are interpolated).
39
- * @param data - Key-value map (e.g. engine.data / store.state.userData).
40
- * @returns New structure with all strings interpolated.
41
- */
42
- export function interpolateObject<T>(obj: T, data: Record<string, unknown>): T {
43
- if (obj == null) return obj;
44
- if (typeof obj === 'string') return interpolateString(obj, data) as T;
45
- if (Array.isArray(obj)) return obj.map((item) => interpolateObject(item, data)) as T;
46
- if (typeof obj === 'object' && obj.constructor === Object) {
47
- return Object.fromEntries(
48
- Object.entries(obj).map(([k, v]) => [k, interpolateObject(v, data)])
49
- ) as T;
50
- }
51
- return obj;
52
- }
1
+ /**
2
+ * Template interpolation for Lumina slide content.
3
+ *
4
+ * Replaces `{{key}}` placeholders in strings with values from a key-value store
5
+ * (e.g. engine.data / store.userData). When the store changes, any component
6
+ * that uses the interpolated result will reactively update.
7
+ *
8
+ * @example
9
+ * interpolateString('Hello {{name}}', { name: 'World' }) // 'Hello World'
10
+ * interpolateObject({ title: '{{product}}', subtitle: 'v{{version}}' }, { product: 'Lumina', version: 1 })
11
+ * // { title: 'Lumina', subtitle: 'v1' }
12
+ */
13
+
14
+ /** Matches {{key}} or {{ key }}. Key: alphanumeric, underscore. */
15
+ const TAG_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
16
+
17
+ /**
18
+ * Replaces all {{key}} placeholders in a string with values from `data`.
19
+ * Missing keys render as empty string. Non-string values are coerced with String().
20
+ *
21
+ * @param str - Raw string possibly containing `{{key}}` tags.
22
+ * @param data - Key-value map (e.g. engine.data / store.state.userData).
23
+ * @returns Interpolated string.
24
+ */
25
+ export function interpolateString(str: string, data: Record<string, unknown>): string {
26
+ if (typeof str !== 'string') return str;
27
+ return str.replace(TAG_RE, (_, key: string) => {
28
+ const v = data[key];
29
+ return v == null ? '' : String(v);
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Recursively interpolates all string values in an object/array with `{{key}}`
35
+ * placeholders. Other types (number, boolean, null, etc.) are returned unchanged.
36
+ * Does not mutate the input; returns a new structure.
37
+ *
38
+ * @param obj - Object, array, or primitive (strings are interpolated).
39
+ * @param data - Key-value map (e.g. engine.data / store.state.userData).
40
+ * @returns New structure with all strings interpolated.
41
+ */
42
+ export function interpolateObject<T>(obj: T, data: Record<string, unknown>): T {
43
+ if (obj == null) return obj;
44
+ if (typeof obj === 'string') return interpolateString(obj, data) as T;
45
+ if (Array.isArray(obj)) return obj.map((item) => interpolateObject(item, data)) as T;
46
+ if (typeof obj === 'object' && obj.constructor === Object) {
47
+ return Object.fromEntries(
48
+ Object.entries(obj).map(([k, v]) => [k, interpolateObject(v, data)])
49
+ ) as T;
50
+ }
51
+ return obj;
52
+ }