lumina-slides 9.0.3 → 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.
@@ -454,30 +454,52 @@ engine.<span class="text-yellow-400">on</span>('navigate', ({ direction, toIndex
454
454
  <ol class="list-decimal list-inside mb-6 space-y-1 text-white/70">
455
455
  <li><strong>Slide root</strong> (path <code>[]</code> or <code>['slide']</code>): <code>slide.id</code> if present, otherwise <code>s{N}-slide</code>.</li>
456
456
  <li><strong>Object with <code>id</code></strong>: if the value at that path is an object with <code>id: string</code> (e.g. <code>features[0].id</code>, a diagram node), that <code>id</code> is used.</li>
457
- <li><strong><code>slide.ids</code></strong>: if the path has a single segment (e.g. <code>tag</code>) and <code>slide.ids.tag</code> exists, that value is used.</li>
458
- <li><strong>Fallback</strong>: <code>elemId(slideIndex, ...path)</code> → format <code>s{N}-{path0}-{path1}-...</code> (e.g. <code>s0-tag</code>, <code>s1-features-2</code>, <code>s2-elements-0-elements-1</code>).</li>
457
+ <li><strong><code>slide.ids[key]</code></strong>: e.g. <code>slide.ids["tag"]</code>, <code>slide.ids["features.0"]</code>. Overrides the fallback when the object has no <code>id</code>.</li>
458
+ <li><strong>Fallback</strong>: if <code>slide.id</code> is set → <code>{slide.id}-{path}</code> (e.g. <code>intro-tag</code>, <code>intro-title</code>); these IDs stay stable when you insert, remove or reorder slides. Otherwise → <code>s{N}-{path}</code> (e.g. <code>s0-tag</code>, <code>s1-features-0</code>).</li>
459
459
  </ol>
460
460
 
461
- <p><strong>Ids by slide type</strong> (when there is no <code>id</code> or <code>slide.ids</code>):</p>
461
+ <p><strong>Stable IDs with <code>slide.id</code></strong>: set <code>id: "intro"</code> on a slide so the fallback becomes <code>intro-tag</code>, <code>intro-title</code>, etc. Recommended when using <code>meta.initialElementState</code> or reorderable decks.</p>
462
+
463
+ <h3>Element IDs per layout</h3>
464
+ <p>Each row is one controllable element. <strong>Path</strong> is used in <code>engine.element(slideIndex, "tag")</code> or <code>engine.elementInCurrent("title")</code>. <strong>Fallback ID</strong> when no <code>slide.id</code>, <code>slide.ids</code> or <code>object.id</code>; with <code>slide.id: "x"</code> it becomes <code>x-tag</code>, <code>x-title</code>, etc. <strong>When</strong> = always, or only when that field exists in the slide JSON.</p>
462
465
  <div class="overflow-x-auto mb-6">
463
466
  <table class="w-full text-sm border border-white/10 rounded-lg">
464
- <thead><tr class="border-b border-white/10"><th class="text-left py-2 px-3 text-white/90">Type</th><th class="text-left py-2 px-3 text-white/90">Paths (path id fallback)</th></tr></thead>
467
+ <thead><tr class="border-b border-white/10"><th class="text-left py-2 px-3 text-white/90">Layout</th><th class="text-left py-2 px-3 text-white/90">Path</th><th class="text-left py-2 px-3 text-white/90">Fallback ID</th><th class="text-left py-2 px-3 text-white/90">When</th></tr></thead>
465
468
  <tbody class="text-white/70">
466
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">statement</td><td class="py-2 px-3"><code>tag</code>→s{N}-tag, <code>title</code>→s{N}-title, <code>subtitle</code>→s{N}-subtitle</td></tr>
467
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">features</td><td class="py-2 px-3"><code>header</code>→s{N}-header, <code>features.i</code>→s{N}-features-{i}</td></tr>
468
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">half</td><td class="py-2 px-3"><code>media</code>, <code>tag</code>, <code>title</code>, <code>paragraphs</code>, <code>cta</code> s{N}-media, s{N}-tag, etc.</td></tr>
469
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">timeline</td><td class="py-2 px-3"><code>title</code>, <code>subtitle</code>, <code>timeline.i</code> → s{N}-title, s{N}-subtitle, s{N}-timeline-{i}</td></tr>
470
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">steps</td><td class="py-2 px-3"><code>header</code>, <code>steps.i</code> → s{N}-header, s{N}-steps-{i}</td></tr>
471
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">flex</td><td class="py-2 px-3"><code>elements.i</code> s{N}-elements-{i}; nested <code>elements.i.elements.j</code> s{N}-elements-{i}-elements-{j}</td></tr>
472
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">chart</td><td class="py-2 px-3"><code>title</code>, <code>subtitle</code>, <code>chart</code> → s{N}-title, s{N}-subtitle, s{N}-chart</td></tr>
473
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">video</td><td class="py-2 px-3"><code>video</code>, <code>title</code> → s{N}-video, s{N}-title</td></tr>
474
- <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">diagram</td><td class="py-2 px-3"><code>nodes.i</code>, <code>edges.i</code>; if the node has <code>id</code>, it is used</td></tr>
475
- <tr><td class="py-2 px-3 font-mono">free</td><td class="py-2 px-3"><code>elements.i</code> s{N}-elements-0, s{N}-elements-1, …</td></tr>
469
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">statement</td><td class="py-2 px-3"><code>tag</code></td><td class="py-2 px-3"><code>s{N}-tag</code></td><td class="py-2 px-3">only when <code>tag</code> is set</td></tr>
470
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">statement</td><td class="py-2 px-3"><code>title</code></td><td class="py-2 px-3"><code>s{N}-title</code></td><td class="py-2 px-3">always</td></tr>
471
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">statement</td><td class="py-2 px-3"><code>subtitle</code></td><td class="py-2 px-3"><code>s{N}-subtitle</code></td><td class="py-2 px-3">only when <code>subtitle</code> is set</td></tr>
472
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">features</td><td class="py-2 px-3"><code>header</code></td><td class="py-2 px-3"><code>s{N}-header</code></td><td class="py-2 px-3">always (wrapper for title + description)</td></tr>
473
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">features</td><td class="py-2 px-3"><code>title</code></td><td class="py-2 px-3"><code>s{N}-title</code></td><td class="py-2 px-3">always</td></tr>
474
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">features</td><td class="py-2 px-3"><code>description</code></td><td class="py-2 px-3"><code>s{N}-description</code></td><td class="py-2 px-3">only when <code>description</code> is set</td></tr>
475
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">features</td><td class="py-2 px-3"><code>features.0</code>, <code>features.1</code>, …</td><td class="py-2 px-3"><code>s{N}-features-{i}</code> or <code>features[i].id</code></td><td class="py-2 px-3">one per feature</td></tr>
476
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">half</td><td class="py-2 px-3"><code>media</code></td><td class="py-2 px-3"><code>s{N}-media</code></td><td class="py-2 px-3">always</td></tr>
477
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">half</td><td class="py-2 px-3"><code>tag</code></td><td class="py-2 px-3"><code>s{N}-tag</code></td><td class="py-2 px-3">only when <code>tag</code> is set</td></tr>
478
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">half</td><td class="py-2 px-3"><code>title</code></td><td class="py-2 px-3"><code>s{N}-title</code></td><td class="py-2 px-3">always</td></tr>
479
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">half</td><td class="py-2 px-3"><code>paragraphs</code></td><td class="py-2 px-3"><code>s{N}-paragraphs</code></td><td class="py-2 px-3">always</td></tr>
480
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">half</td><td class="py-2 px-3"><code>cta</code></td><td class="py-2 px-3"><code>s{N}-cta</code></td><td class="py-2 px-3">only when <code>cta</code> is set</td></tr>
481
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">timeline</td><td class="py-2 px-3"><code>title</code></td><td class="py-2 px-3"><code>s{N}-title</code></td><td class="py-2 px-3">always</td></tr>
482
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">timeline</td><td class="py-2 px-3"><code>subtitle</code></td><td class="py-2 px-3"><code>s{N}-subtitle</code></td><td class="py-2 px-3">only when <code>subtitle</code> is set</td></tr>
483
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">timeline</td><td class="py-2 px-3"><code>timeline.0</code>, <code>timeline.1</code>, …</td><td class="py-2 px-3"><code>s{N}-timeline-{i}</code> or <code>timeline[i].id</code></td><td class="py-2 px-3">one per item</td></tr>
484
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">steps</td><td class="py-2 px-3"><code>header</code></td><td class="py-2 px-3"><code>s{N}-header</code></td><td class="py-2 px-3">always (wrapper for title + subtitle)</td></tr>
485
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">steps</td><td class="py-2 px-3"><code>title</code></td><td class="py-2 px-3"><code>s{N}-title</code></td><td class="py-2 px-3">always</td></tr>
486
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">steps</td><td class="py-2 px-3"><code>subtitle</code></td><td class="py-2 px-3"><code>s{N}-subtitle</code></td><td class="py-2 px-3">only when <code>subtitle</code> is set</td></tr>
487
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">steps</td><td class="py-2 px-3"><code>steps.0</code>, <code>steps.1</code>, …</td><td class="py-2 px-3"><code>s{N}-steps-{i}</code> or <code>steps[i].id</code></td><td class="py-2 px-3">one per step</td></tr>
488
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">flex</td><td class="py-2 px-3"><code>elements.0</code>, <code>elements.1</code>, …</td><td class="py-2 px-3"><code>s{N}-elements-{i}</code> or <code>elements[i].id</code></td><td class="py-2 px-3">one per element</td></tr>
489
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">flex</td><td class="py-2 px-3"><code>elements.i.elements.j</code></td><td class="py-2 px-3"><code>s{N}-elements-{i}-elements-{j}</code></td><td class="py-2 px-3">content children only</td></tr>
490
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">chart</td><td class="py-2 px-3"><code>title</code></td><td class="py-2 px-3"><code>s{N}-title</code></td><td class="py-2 px-3">only when <code>title</code> is set</td></tr>
491
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">chart</td><td class="py-2 px-3"><code>subtitle</code></td><td class="py-2 px-3"><code>s{N}-subtitle</code></td><td class="py-2 px-3">only when <code>subtitle</code> is set</td></tr>
492
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">chart</td><td class="py-2 px-3"><code>chart</code></td><td class="py-2 px-3"><code>s{N}-chart</code></td><td class="py-2 px-3">always (may be absent in DOM if Chart.js fails)</td></tr>
493
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">video</td><td class="py-2 px-3"><code>video</code></td><td class="py-2 px-3"><code>s{N}-video</code></td><td class="py-2 px-3">always</td></tr>
494
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">video</td><td class="py-2 px-3"><code>title</code></td><td class="py-2 px-3"><code>s{N}-title</code></td><td class="py-2 px-3">only when <code>title</code> is set</td></tr>
495
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">diagram</td><td class="py-2 px-3"><code>nodes.0</code>, <code>nodes.1</code>, …</td><td class="py-2 px-3"><code>nodes[i].id</code> or <code>s{N}-nodes-{i}</code></td><td class="py-2 px-3">set <code>nodes[i].id</code> in JSON for control</td></tr>
496
+ <tr class="border-b border-white/5"><td class="py-2 px-3 font-mono">diagram</td><td class="py-2 px-3"><code>edges.0</code>, <code>edges.1</code>, …</td><td class="py-2 px-3"><code>edges[i].id</code> or <code>s{N}-edges-{i}</code></td><td class="py-2 px-3">set <code>edges[i].id</code> in JSON for control</td></tr>
497
+ <tr><td class="py-2 px-3 font-mono">free</td><td class="py-2 px-3"><code>elements.0</code>, <code>elements.1</code>, …</td><td class="py-2 px-3"><code>s{N}-elements-{i}</code></td><td class="py-2 px-3">one per element</td></tr>
476
498
  </tbody>
477
499
  </table>
478
500
  </div>
479
501
 
480
- <p>To see a slide's ids at runtime: <code>engine.elements(slideIndex)</code>. To use by path: <code>engine.element(slideIndex, "tag")</code> or <code>engine.element(slideIndex, "features.0")</code>; the id is resolved with the same rules.</p>
502
+ <p><strong>Discover and target</strong>: <code>engine.elements()</code> or <code>engine.elements(slideIndex)</code> lists all ids for a slide (omit <code>slideIndex</code> for the current slide). <code>engine.element(id)</code>, <code>engine.element(slideIndex, "tag")</code>, or <code>engine.elementInCurrent("title")</code> for the current slide by path. Override any path via <code>slide.ids</code> (e.g. <code>ids: { "title": "hero-title" }</code>) or <code>object.id</code> on items (e.g. <code>features[0].id: "hero"</code>).</p>
481
503
 
482
504
  <h2>Option 1: meta.initialElementState</h2>
483
505
  <p>Define in the deck which elements start hidden. Ids follow the pattern
@@ -1903,7 +1925,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
1903
1925
  </tbody>
1904
1926
  </table>
1905
1927
  </div>
1906
- <p class="text-white/50 text-sm mt-2">Element ids: <code>s{N}-tag</code>, <code>s{N}-title</code>, <code>s{N}-subtitle</code>. Override via <code>ids</code>.</p>
1928
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>tag</code> (if set)→<code>s{N}-tag</code>, <code>title</code>→<code>s{N}-title</code>, <code>subtitle</code> (if set)→<code>s{N}-subtitle</code>. With <code>slide.id</code>: <code>{id}-tag</code>, etc. Override via <code>slide.ids</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
1907
1929
  </div>
1908
1930
 
1909
1931
  <div class="my-8">
@@ -1937,7 +1959,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
1937
1959
  </tbody>
1938
1960
  </table>
1939
1961
  </div>
1940
- <p class="text-white/50 text-sm mt-2">Element ids: <code>s{N}-media</code>, <code>s{N}-tag</code>, <code>s{N}-title</code>, <code>s{N}-paragraphs</code>, <code>s{N}-cta</code>.</p>
1962
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>media</code>, <code>tag</code> (if set), <code>title</code>, <code>paragraphs</code>, <code>cta</code> (if set) → <code>s{N}-media</code>, <code>s{N}-tag</code>, etc. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
1941
1963
  </div>
1942
1964
 
1943
1965
  <div class="my-8">
@@ -1982,7 +2004,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
1982
2004
  </tbody>
1983
2005
  </table>
1984
2006
  </div>
1985
- <p class="text-white/50 text-sm mt-2">Element ids: <code>s{N}-header</code>, <code>s{N}-features-0</code>, <code>s{N}-features-1</code>, …</p>
2007
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>header</code>, <code>title</code>, <code>description</code> (if set), <code>features.0</code>, <code>features.1</code>, … → <code>s{N}-header</code>, <code>s{N}-title</code>, <code>s{N}-features-0</code>, etc. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
1986
2008
  </div>
1987
2009
 
1988
2010
  <div class="my-8">
@@ -2028,7 +2050,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
2028
2050
  </tbody>
2029
2051
  </table>
2030
2052
  </div>
2031
- <p class="text-white/50 text-sm mt-2">Element ids: <code>s{N}-title</code>, <code>s{N}-subtitle</code>, <code>s{N}-timeline-0</code>, …</p>
2053
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>title</code>, <code>subtitle</code> (if set), <code>timeline.0</code>, <code>timeline.1</code>, … → <code>s{N}-title</code>, <code>s{N}-subtitle</code>, <code>s{N}-timeline-{i}</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
2032
2054
  </div>
2033
2055
 
2034
2056
  <div class="my-8">
@@ -2074,7 +2096,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
2074
2096
  </tbody>
2075
2097
  </table>
2076
2098
  </div>
2077
- <p class="text-white/50 text-sm mt-2">Element ids: <code>s{N}-header</code>, <code>s{N}-steps-0</code>, <code>s{N}-steps-1</code>, …</p>
2099
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>header</code>, <code>title</code>, <code>subtitle</code> (if set), <code>steps.0</code>, <code>steps.1</code>, … → <code>s{N}-header</code>, <code>s{N}-title</code>, <code>s{N}-steps-{i}</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
2078
2100
  </div>
2079
2101
 
2080
2102
  <div class="my-8">
@@ -2135,7 +2157,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
2135
2157
  </tbody>
2136
2158
  </table>
2137
2159
  </div>
2138
- <p class="text-white/50 text-sm mt-2">SpacingToken: <code>none | xs | sm | md | lg | xl | 2xl</code>. Element ids: <code>s{N}-elements-{i}</code>, <code>s{N}-elements-{i}-elements-{j}</code> for content children.</p>
2160
+ <p class="text-white/50 text-sm mt-2">SpacingToken: <code>none | xs | sm | md | lg | xl | 2xl</code>. Element ids: <code>elements.0</code>, <code>elements.1</code>, …, <code>elements.i.elements.j</code> (content) → <code>s{N}-elements-{i}</code>, <code>s{N}-elements-{i}-elements-{j}</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
2139
2161
  </div>
2140
2162
 
2141
2163
  <div class="my-8">
@@ -2195,6 +2217,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
2195
2217
  </tbody>
2196
2218
  </table>
2197
2219
  </div>
2220
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>title</code> (if set), <code>subtitle</code> (if set), <code>chart</code> → <code>s{N}-title</code>, <code>s{N}-subtitle</code>, <code>s{N}-chart</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
2198
2221
  </div>
2199
2222
 
2200
2223
  <div class="my-8">
@@ -2507,7 +2530,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
2507
2530
  <LivePreview :initial-code="EXAMPLES.video_slide" />
2508
2531
  </div>
2509
2532
 
2510
- <p class="text-white/60 text-sm">Element control ids: <code>s{N}-video</code>, <code>s{N}-title</code>.</p>
2533
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>video</code>, <code>title</code> (if set) → <code>s{N}-video</code>, <code>s{N}-title</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
2511
2534
  </section>
2512
2535
 
2513
2536
  <!-- REF: CUSTOM HTML -->
@@ -2608,7 +2631,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
2608
2631
  </tbody>
2609
2632
  </table>
2610
2633
  </div>
2611
- <p class="text-white/50 text-sm mt-2">Element control: <code>s{N}-nodes-{i}</code>, <code>s{N}-edges-{i}</code>. Studio: palette, drag, connect, resize.</p>
2634
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>nodes.0</code>, <code>nodes.1</code>, … and <code>edges.0</code>, … Use <code>nodes[i].id</code> and <code>edges[i].id</code> in JSON so <code>engine.elements()</code> aligns with the DOM. Fallback: <code>s{N}-nodes-{i}</code>, <code>s{N}-edges-{i}</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table. Studio: palette, drag, connect, resize.</p>
2612
2635
  </div>
2613
2636
  </section>
2614
2637
 
@@ -2651,6 +2674,7 @@ engine.data.set(<span class="text-green-400">"user"</span>, <span class="text-gr
2651
2674
  </tbody>
2652
2675
  </table>
2653
2676
  </div>
2677
+ <p class="text-white/50 text-sm mt-2">Element ids: <code>elements.0</code>, <code>elements.1</code>, … → <code>s{N}-elements-{i}</code>. Override with <code>elements[i].id</code>. <a href="#element-control" class="text-blue-400 hover:underline">Element Control</a> has the full table.</p>
2654
2678
 
2655
2679
  <h3 class="text-lg font-bold text-white mt-8 mb-4">Positioning and animation (timelineTracks)</h3>
2656
2680
  <p>Elements are placed at <code>left: 0; top: 0</code> by default. To move or animate them, set <code>slide.timelineTracks</code> on the slide (SlideBase). Keys are element ids: <code>s{N}-elements-0</code>, <code>s{N}-elements-1</code>, or the element’s <code>id</code>. Value: keyframes from progress string to state:</p>
@@ -113,22 +113,38 @@ export type PathGenerator = (slide: Readonly<BaseSlideData>) => ElementPath[];
113
113
 
114
114
  /** Map of slide.type → path generator. Extend to support new layouts. */
115
115
  const PATH_GENERATORS: Record<string, PathGenerator> = {
116
- 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
+ },
117
122
  features: (s) => {
118
123
  const arr = getValueAt(s, ['features']);
119
124
  const list = Array.isArray(arr) ? arr : [];
120
- 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;
121
134
  },
122
- half: () => [['media'], ['tag'], ['title'], ['paragraphs'], ['cta']],
123
135
  timeline: (s) => {
124
136
  const arr = getValueAt(s, ['timeline']);
125
137
  const list = Array.isArray(arr) ? arr : [];
126
- 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)];
127
141
  },
128
142
  steps: (s) => {
129
143
  const arr = getValueAt(s, ['steps']);
130
144
  const list = Array.isArray(arr) ? arr : [];
131
- 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)];
132
148
  },
133
149
  flex: (s) => {
134
150
  const paths: ElementPath[] = [];
@@ -143,7 +159,13 @@ const PATH_GENERATORS: Record<string, PathGenerator> = {
143
159
  });
144
160
  return paths;
145
161
  },
146
- 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
+ },
147
169
  diagram: (s) => {
148
170
  const p: ElementPath[] = [];
149
171
  const nodes = getValueAt(s, ['nodes']);
@@ -153,7 +175,11 @@ const PATH_GENERATORS: Record<string, PathGenerator> = {
153
175
  return p;
154
176
  },
155
177
  custom: () => [],
156
- video: () => [['video'], ['title']],
178
+ video: (s) => {
179
+ const paths: ElementPath[] = [['video']];
180
+ if (getValueAt(s, ['title'])) paths.push(['title']);
181
+ return paths;
182
+ },
157
183
  free: (s) => {
158
184
  const arr = getValueAt(s, ['elements']);
159
185
  const list = Array.isArray(arr) ? arr : [];