lumina-slides 9.0.5 → 9.0.7

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 (38) hide show
  1. package/README.md +63 -0
  2. package/dist/lumina-slides.js +21750 -19334
  3. package/dist/lumina-slides.umd.cjs +223 -223
  4. package/dist/style.css +1 -1
  5. package/package.json +1 -1
  6. package/src/components/LandingPage.vue +1 -1
  7. package/src/components/LuminaDeck.vue +237 -232
  8. package/src/components/base/LuminaElement.vue +2 -0
  9. package/src/components/layouts/LayoutFeatures.vue +125 -123
  10. package/src/components/layouts/LayoutFlex.vue +212 -212
  11. package/src/components/layouts/LayoutStatement.vue +5 -2
  12. package/src/components/layouts/LayoutSteps.vue +110 -108
  13. package/src/components/parts/FlexHtml.vue +65 -65
  14. package/src/components/parts/FlexImage.vue +81 -81
  15. package/src/components/site/SiteDocs.vue +3313 -3314
  16. package/src/components/site/SiteExamples.vue +66 -66
  17. package/src/components/studio/EditorLayoutChart.vue +18 -0
  18. package/src/components/studio/EditorLayoutCustom.vue +18 -0
  19. package/src/components/studio/EditorLayoutVideo.vue +18 -0
  20. package/src/components/studio/LuminaStudioEmbed.vue +68 -0
  21. package/src/components/studio/StudioEmbedRoot.vue +19 -0
  22. package/src/components/studio/StudioInspector.vue +1113 -7
  23. package/src/components/studio/StudioJsonEditor.vue +10 -3
  24. package/src/components/studio/StudioSettings.vue +658 -7
  25. package/src/components/studio/StudioToolbar.vue +26 -7
  26. package/src/composables/useElementState.ts +12 -1
  27. package/src/composables/useFlexLayout.ts +128 -128
  28. package/src/core/Lumina.ts +174 -113
  29. package/src/core/animationConfig.ts +10 -0
  30. package/src/core/elementController.ts +18 -0
  31. package/src/core/elementResolver.ts +4 -2
  32. package/src/core/schema.ts +503 -503
  33. package/src/core/store.ts +465 -465
  34. package/src/core/types.ts +26 -11
  35. package/src/index.ts +2 -2
  36. package/src/utils/prepareDeckForExport.ts +47 -0
  37. package/src/utils/templateInterpolation.ts +52 -52
  38. package/src/views/DeckView.vue +313 -313
package/src/core/types.ts CHANGED
@@ -591,7 +591,7 @@ export interface VideoProperties {
591
591
  * "type": "statement",
592
592
  * "meta": { "orbColor": "#3b82f6" },
593
593
  * "tag": "Declarative Engine",
594
- * "title": "Lumina V8",
594
+ * "title": "Lumina V9",
595
595
  * "subtitle": "A highly modular, performance-first presentation engine."
596
596
  * }
597
597
  * ```
@@ -894,8 +894,7 @@ export interface DeckMeta {
894
894
  */
895
895
  initialElementState?: InitialElementState;
896
896
  /**
897
- * Element control defaults. Can be set in deck JSON so the deck is self-contained.
898
- * 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.
899
898
  */
900
899
  elementControl?: { defaultVisible?: boolean };
901
900
  /** Default reveal options for all slides. Overridden by slide.reveal. */
@@ -911,8 +910,8 @@ export interface DeckMeta {
911
910
  * Complete deck object. Primary input to `engine.load(deck)`.
912
911
  *
913
912
  * @description
914
- * Must have `meta.title` and `slides` (array of slide objects). meta.initialElementState and
915
- * 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.
916
915
  *
917
916
  * @example
918
917
  * { meta: { title: "Demo", initialElementState: { "s0-title": { visible: false } } }, slides: [{ type: "statement", title: "Hi" }] }
@@ -1388,6 +1387,12 @@ export interface LuminaAnimationOptions {
1388
1387
  /** GSAP ease for reveal. Default: 'power2.out'. */
1389
1388
  revealEase?: string;
1390
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
+
1391
1396
  // --- element().animate() ---
1392
1397
  /** Default duration in seconds. Default: 0.5. */
1393
1398
  elementDuration?: number;
@@ -1450,7 +1455,7 @@ export interface LuminaAnimationOptions {
1450
1455
  *
1451
1456
  * @example
1452
1457
  * new Lumina("#app", { theme: "midnight", loop: true, animation: { durationIn: 1.2 } });
1453
- * new Lumina("#app", { elementControl: { defaultVisible: false } });
1458
+ * new Lumina("#app", { elementControl: { defaultVisible: true } }); // legacy: show all
1454
1459
  *
1455
1460
  * @see Lumina
1456
1461
  * @see LuminaAnimationOptions
@@ -1477,16 +1482,23 @@ export interface LuminaOptions {
1477
1482
  /** Animation settings. */
1478
1483
  animation?: LuminaAnimationOptions;
1479
1484
  /**
1480
- * Element control defaults. Use for reveal-on-demand: set defaultVisible to false so all
1481
- * elements start hidden; override per-id via meta.initialElementState. Then reveal with
1482
- * 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).
1483
1487
  */
1484
1488
  elementControl?: {
1485
- /** 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). */
1486
1490
  defaultVisible?: boolean;
1487
1491
  };
1488
1492
  /** Enable Studio (Page Builder) mode. */
1489
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;
1490
1502
  }
1491
1503
 
1492
1504
  // --- Events ---
@@ -1511,7 +1523,8 @@ export type LuminaEventType =
1511
1523
  | 'revealComplete'
1512
1524
  | 'revealElement'
1513
1525
  | 'diagram-node-update'
1514
- | 'studio:update';
1526
+ | 'studio:update'
1527
+ | 'save';
1515
1528
 
1516
1529
  /**
1517
1530
  * Payload for the 'slideChange' event.
@@ -1624,6 +1637,8 @@ export interface LuminaEventMap {
1624
1637
  'diagram-node-update': { slideIndex: number; nodeId: string; key: string; value: any };
1625
1638
  /** Fired when Studio updates a node (path, value). Internal. */
1626
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;
1627
1642
  }
1628
1643
 
1629
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';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Prepares a deck for export: strips internal dragKey and adds explicit element ids.
3
+ * Used by Studio export, save, and JSON view.
4
+ * - slide.ids: path → id for every element
5
+ * - each element object that can have an id (features[i], timeline[i], elements[i], nodes[i], etc.) gets id set
6
+ */
7
+ import { toRaw } from 'vue';
8
+ import type { Deck, BaseSlideData } from '../core/types';
9
+ import { getElementPaths, resolveId, pathToKey, getValueAt } from '../core/elementResolver';
10
+
11
+ /** Recursively remove all dragKey properties from an object (mutates in place). */
12
+ export function stripDragKeys(obj: unknown): void {
13
+ if (obj == null || typeof obj !== 'object') return;
14
+ if (Array.isArray(obj)) {
15
+ obj.forEach(stripDragKeys);
16
+ return;
17
+ }
18
+ delete (obj as Record<string, unknown>)['dragKey'];
19
+ Object.values(obj as Record<string, unknown>).forEach(stripDragKeys);
20
+ }
21
+
22
+ /** Prepare deck for export: clone, strip dragKey, add slide.ids, and set id on each element object. */
23
+ export function prepareDeckForExport(deck: Deck): Deck {
24
+ const clone = JSON.parse(JSON.stringify(toRaw(deck))) as Deck;
25
+ stripDragKeys(clone);
26
+ const slides = clone.slides ?? [];
27
+ slides.forEach((slide: BaseSlideData, slideIndex: number) => {
28
+ const paths = getElementPaths(slide);
29
+ if (paths.length === 0) return;
30
+ const slideAny = slide as { ids?: Record<string, string> };
31
+ if (!slideAny.ids) slideAny.ids = {};
32
+ paths.forEach((path) => {
33
+ const pathKey = pathToKey(path);
34
+ const resolvedId = resolveId(slide, slideIndex, path);
35
+ slideAny.ids![pathKey] = resolvedId;
36
+
37
+ // Set id on the element object when it's an object (e.g. features[0], elements[0], nodes[0])
38
+ if (path.length > 0 && path[0] !== 'slide') {
39
+ const value = getValueAt(slide, path);
40
+ if (value != null && typeof value === 'object' && !Array.isArray(value)) {
41
+ (value as Record<string, string>).id = resolvedId;
42
+ }
43
+ }
44
+ });
45
+ });
46
+ return clone;
47
+ }
@@ -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
+ }