lumina-slides 9.0.2 → 9.0.4

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.
@@ -433,28 +433,50 @@ export class Lumina {
433
433
  return createElementController(this.store, this, idOrSlide);
434
434
  }
435
435
 
436
+ /**
437
+ * Returns the element at the given path on the current slide. Shorthand for
438
+ * engine.element(engine.currentSlideIndex, path). Use when you don't need the slide index.
439
+ *
440
+ * @param path - Logical path (e.g. "title", "features.0", ["elements", 0, "elements", 1]).
441
+ * @returns ElementController for that element.
442
+ *
443
+ * @example
444
+ * engine.elementInCurrent("title").show();
445
+ * engine.elementInCurrent("features.0").animate({ from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.5 });
446
+ *
447
+ * @see element
448
+ * @see elements
449
+ */
450
+ public elementInCurrent(path: string | ElementPath): ElementController {
451
+ const i = this.store.state.currentIndex ?? 0;
452
+ return this.element(i, path);
453
+ }
454
+
436
455
  /**
437
456
  * Returns all element ids for a slide. Use to discover which ids exist (e.g. for
438
457
  * meta.initialElementState, or to iterate and control elements). Each id can be passed to
439
458
  * engine.element(id). Uses the same path→id logic as element(slideIndex, path); ids follow
440
- * resolveId (explicit id, slide.ids, or elemId(slideIndex, ...path)).
459
+ * resolveId (explicit id, slide.ids, or slide.id / elemId(slideIndex, ...path) as fallback).
441
460
  *
442
- * @param slideIndex - Zero-based slide index. If out of range or deck not loaded, returns [].
443
- * @returns Array of id strings (e.g. ["s0-slide","s0-tag","s0-title","s0-subtitle"] for a statement slide).
461
+ * @param slideIndex - Zero-based slide index. Omit to use the current slide. If out of range or deck not loaded, returns [].
462
+ * @returns Array of id strings (e.g. ["s0-slide","s0-tag","s0-title","s0-subtitle"] or ["intro-slide","intro-tag",...] when slide.id is set).
444
463
  *
445
464
  * @example
446
- * const ids = engine.elements(0);
447
- * ids.forEach(id => { if (id.endsWith('-title')) engine.element(id).hide(); });
465
+ * engine.elements(); // current slide
466
+ * engine.elements(0);
467
+ * engine.elements().forEach(id => { if (id.endsWith('-title')) engine.element(id).hide(); });
448
468
  *
449
469
  * @see element
470
+ * @see elementInCurrent
450
471
  * @see getElementIds
451
472
  * @see resolveId
452
473
  * @see DeckMeta.initialElementState
453
474
  */
454
- public elements(slideIndex: number): string[] {
455
- const slide = this.store.state.deck?.slides[slideIndex];
475
+ public elements(slideIndex?: number): string[] {
476
+ const i = slideIndex ?? this.store.state.currentIndex ?? 0;
477
+ const slide = this.store.state.deck?.slides[i];
456
478
  if (!slide) return [];
457
- return getElementIds(slide, slideIndex);
479
+ return getElementIds(slide, i);
458
480
  }
459
481
 
460
482
  /**
@@ -71,7 +71,8 @@ export function parsePath(input: string | ElementPath): ElementPath {
71
71
  * 1. Path [] or ['slide'] → slide.id or elemId(slideIndex, 'slide').
72
72
  * 2. If the value at path is an object with string `id` → use it (e.g. feature.id, node.id).
73
73
  * 3. If slide.ids[pathToKey(path)] exists → use it (supports compound keys: 'tag', 'features.0', 'elements.0.elements.1').
74
- * 4. Otherwise → elemId(slideIndex, ...path) (e.g. "s0-tag", "s1-elements-0-elements-1").
74
+ * 4. Otherwise → if slide.id is set, "{slide.id}-{path}"; else elemId(slideIndex, ...path)
75
+ * (e.g. "intro-tag" with slide.id "intro", or "s0-tag" when no slide.id).
75
76
  *
76
77
  * @param slide - The slide data.
77
78
  * @param slideIndex - Zero-based slide index (for elemId fallback).
@@ -99,6 +100,11 @@ export function resolveId(
99
100
  if (key && slideAny?.ids?.[key]) {
100
101
  return slideAny.ids[key];
101
102
  }
103
+ // Fallback: use slide.id as namespace when present, so IDs stay stable when slides are
104
+ // inserted, removed or reordered. Otherwise s{N}-{path} (same as before).
105
+ if (slideAny?.id != null && slideAny.id !== '') {
106
+ return `${slideAny.id}-${path.map(String).join('-')}`;
107
+ }
102
108
  return elemId(slideIndex, ...path);
103
109
  }
104
110
 
@@ -107,22 +113,38 @@ export type PathGenerator = (slide: Readonly<BaseSlideData>) => ElementPath[];
107
113
 
108
114
  /** Map of slide.type → path generator. Extend to support new layouts. */
109
115
  const PATH_GENERATORS: Record<string, PathGenerator> = {
110
- statement: () => [['tag'], ['title'], ['subtitle']],
116
+ statement: (s) => {
117
+ const paths: ElementPath[] = [['title']];
118
+ if (getValueAt(s, ['tag'])) paths.unshift(['tag']);
119
+ if (getValueAt(s, ['subtitle'])) paths.push(['subtitle']);
120
+ return paths;
121
+ },
111
122
  features: (s) => {
112
123
  const arr = getValueAt(s, ['features']);
113
124
  const list = Array.isArray(arr) ? arr : [];
114
- return [['header'], ...list.map((_: unknown, i: number) => ['features', i] as ElementPath)];
125
+ const paths: ElementPath[] = [['header'], ['title']];
126
+ if (getValueAt(s, ['description'])) paths.push(['description']);
127
+ return [...paths, ...list.map((_: unknown, i: number) => ['features', i] as ElementPath)];
128
+ },
129
+ half: (s) => {
130
+ const paths: ElementPath[] = [['media'], ['title'], ['paragraphs']];
131
+ if (getValueAt(s, ['tag'])) paths.splice(1, 0, ['tag']); // after media, before title
132
+ if (getValueAt(s, ['cta'])) paths.push(['cta']);
133
+ return paths;
115
134
  },
116
- half: () => [['media'], ['tag'], ['title'], ['paragraphs'], ['cta']],
117
135
  timeline: (s) => {
118
136
  const arr = getValueAt(s, ['timeline']);
119
137
  const list = Array.isArray(arr) ? arr : [];
120
- return [['title'], ['subtitle'], ...list.map((_: unknown, i: number) => ['timeline', i] as ElementPath)];
138
+ const paths: ElementPath[] = [['title']];
139
+ if (getValueAt(s, ['subtitle'])) paths.push(['subtitle']);
140
+ return [...paths, ...list.map((_: unknown, i: number) => ['timeline', i] as ElementPath)];
121
141
  },
122
142
  steps: (s) => {
123
143
  const arr = getValueAt(s, ['steps']);
124
144
  const list = Array.isArray(arr) ? arr : [];
125
- return [['header'], ...list.map((_: unknown, i: number) => ['steps', i] as ElementPath)];
145
+ const paths: ElementPath[] = [['header'], ['title']];
146
+ if (getValueAt(s, ['subtitle'])) paths.push(['subtitle']);
147
+ return [...paths, ...list.map((_: unknown, i: number) => ['steps', i] as ElementPath)];
126
148
  },
127
149
  flex: (s) => {
128
150
  const paths: ElementPath[] = [];
@@ -137,7 +159,13 @@ const PATH_GENERATORS: Record<string, PathGenerator> = {
137
159
  });
138
160
  return paths;
139
161
  },
140
- chart: () => [['title'], ['subtitle'], ['chart']],
162
+ chart: (s) => {
163
+ const paths: ElementPath[] = [];
164
+ if (getValueAt(s, ['title'])) paths.push(['title']);
165
+ if (getValueAt(s, ['subtitle'])) paths.push(['subtitle']);
166
+ paths.push(['chart']);
167
+ return paths;
168
+ },
141
169
  diagram: (s) => {
142
170
  const p: ElementPath[] = [];
143
171
  const nodes = getValueAt(s, ['nodes']);
@@ -147,7 +175,11 @@ const PATH_GENERATORS: Record<string, PathGenerator> = {
147
175
  return p;
148
176
  },
149
177
  custom: () => [],
150
- video: () => [['video'], ['title']],
178
+ video: (s) => {
179
+ const paths: ElementPath[] = [['video']];
180
+ if (getValueAt(s, ['title'])) paths.push(['title']);
181
+ return paths;
182
+ },
151
183
  free: (s) => {
152
184
  const arr = getValueAt(s, ['elements']);
153
185
  const list = Array.isArray(arr) ? arr : [];
package/src/core/types.ts CHANGED
@@ -448,8 +448,8 @@ export type TimelineKeyframes = Record<string, TimelineKeyframeState>;
448
448
  export type TimelineTracks = Record<string, TimelineKeyframes>;
449
449
 
450
450
  /**
451
- * Fluent API to control one slide element in real time. Returned by `engine.element(id)`
452
- * and `engine.element(slideIndex, path)`. All methods return `this` for chaining.
451
+ * Fluent API to control one slide element in real time. Returned by `engine.element(id)`,
452
+ * `engine.element(slideIndex, path)`, and `engine.elementInCurrent(path)`. All methods return `this` for chaining.
453
453
  *
454
454
  * Use for: starting with an empty slide and revealing items in order, hiding highlights,
455
455
  * animating entrances on demand, or syncing with voice/agent.
@@ -462,6 +462,7 @@ export type TimelineTracks = Record<string, TimelineKeyframes>;
462
462
  * engine.element(0, 'subtitle').show();
463
463
  *
464
464
  * @see Lumina.element
465
+ * @see Lumina.elementInCurrent
465
466
  * @see Lumina.elements
466
467
  * @see ElementState
467
468
  * @see AnimateOptions
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * **Deck:** `{ meta: { title, initialElementState?, elementControl?, effects? }, slides: [...] }`
11
11
  * Slide types: statement, features, half, timeline, steps, flex, chart, diagram, custom, video.
12
12
  *
13
- * **Element control:** `engine.element(id)` or `engine.element(slideIndex, path)` → ElementController:
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
16
  * Ids: `engine.elements(slideIndex)` or `s{N}-{path}` (e.g. s0-tag, s1-features-0). `meta.initialElementState`: