1414 * { type: 'od:slide-state', active: number, count: number }
1515 * after every navigation so the host can render its own counter / dots.
1616 */
17- import { injectDeckStageFallback } from '@open-design/contracts/runtime/deck-stage-fallback' ;
17+ import {
18+ DECK_EXPLICIT_SLIDE_SELECTOR ,
19+ DECK_LEGACY_SCREEN_LABEL_RE_SOURCE ,
20+ DECK_LEGACY_SCREEN_SLIDE_SELECTOR ,
21+ DECK_SLIDE_SELECTOR ,
22+ DECK_STRUCTURED_SLIDE_SELECTOR ,
23+ injectDeckStageFallback ,
24+ } from '@open-design/contracts/runtime/deck-stage-fallback' ;
1825import { buildPreviewObservabilityBridge } from '@open-design/contracts/runtime/preview-observability' ;
1926
2027import {
@@ -2882,15 +2889,97 @@ function injectDeckBridge(
28822889 }, true);
28832890 }
28842891 window.__odDeckBridgeOwnListenerInstall = false;
2892+ function deckStageSlides(stage){
2893+ if (!stage) return [];
2894+ var nested = stage.querySelectorAll(${ JSON . stringify ( DECK_SLIDE_SELECTOR ) } );
2895+ var direct = [];
2896+ for (var i = 0; i < nested.length; i++) {
2897+ if (nested[i].parentElement === stage) direct.push(nested[i]);
2898+ }
2899+ return direct.length ? direct : nested;
2900+ }
2901+ function legacyScreenSlides(){
2902+ var candidates = document.querySelectorAll(${ JSON . stringify ( DECK_LEGACY_SCREEN_SLIDE_SELECTOR ) } );
2903+ var labelRe = new RegExp(${ JSON . stringify ( DECK_LEGACY_SCREEN_LABEL_RE_SOURCE ) } , 'i');
2904+ var groups = [];
2905+ for (var i = 0; i < candidates.length; i++) {
2906+ var match = labelRe.exec(candidates[i].getAttribute('data-screen-label') || '');
2907+ if (!match || !candidates[i].parentNode) continue;
2908+ var group = null;
2909+ for (var j = 0; j < groups.length; j++) {
2910+ if (groups[j].parent === candidates[i].parentNode) { group = groups[j]; break; }
2911+ }
2912+ if (!group) {
2913+ group = { parent: candidates[i].parentNode, slides: [], numbers: {} };
2914+ groups.push(group);
2915+ }
2916+ group.slides.push(candidates[i]);
2917+ group.numbers[String(Number(match[1]))] = true;
2918+ }
2919+ var best = [];
2920+ for (var k = 0; k < groups.length; k++) {
2921+ if (Object.keys(groups[k].numbers).length > 1 && groups[k].slides.length > best.length) {
2922+ best = groups[k].slides;
2923+ }
2924+ }
2925+ return best;
2926+ }
28852927 function slides(){
2886- // Structured selectors first so decorative .slide markup in non-deck
2887- // pages (icons, badges, code samples) is not counted as deck slides;
2888- // fall back to all .slide only when nothing structured matched, so
2889- // freeform decks that nest slides under an extra wrapper still report
2890- // the real count instead of leaving the host counter at 1 / 0.
2891- var structured = document.querySelectorAll('deck-stage > .slide, .deck > .slide, .deck-stage > .slide, .deck-shell > .slide, body > .slide');
2928+ // An explicit deck-stage owns its descendants, so unrelated annotated
2929+ // screens elsewhere in the document cannot leak into its count. Otherwise
2930+ // prefer direct children of recognized legacy containers before falling
2931+ // back to every supported persisted slide marker.
2932+ var stageSlides = deckStageSlides(document.querySelector('deck-stage'));
2933+ if (stageSlides.length) return stageSlides;
2934+ var structured = document.querySelectorAll(${ JSON . stringify ( DECK_STRUCTURED_SLIDE_SELECTOR ) } );
28922935 if (structured.length) return structured;
2893- return document.querySelectorAll('.slide');
2936+ var explicit = document.querySelectorAll(${ JSON . stringify ( DECK_EXPLICIT_SLIDE_SELECTOR ) } );
2937+ if (explicit.length) return explicit;
2938+ return legacyScreenSlides();
2939+ }
2940+ function deckStageForSlides(list){
2941+ var stage = document.querySelector('deck-stage');
2942+ if (!stage) return null;
2943+ if (list && list.length && !stage.contains(list[0])) return null;
2944+ return stage;
2945+ }
2946+ function activeIndexFromDeckStage(list){
2947+ var stage = deckStageForSlides(list);
2948+ if (!stage) return -1;
2949+ var index = Number(stage.index);
2950+ if (!Number.isFinite(index)) return -1;
2951+ index = Math.floor(index);
2952+ return index >= 0 && index < list.length ? index : -1;
2953+ }
2954+ function navigateViaDeckStage(list, action, index){
2955+ if (!list.length) return false;
2956+ var stage = deckStageForSlides(list);
2957+ if (!stage) return false;
2958+ try {
2959+ if (action === 'go' && typeof index === 'number' && typeof stage.goTo === 'function') {
2960+ stage.goTo(index);
2961+ } else if (action === 'next' && typeof stage.next === 'function') {
2962+ stage.next();
2963+ } else if (action === 'prev' && typeof stage.prev === 'function') {
2964+ stage.prev();
2965+ } else if (action === 'first' && typeof stage.reset === 'function') {
2966+ stage.reset();
2967+ } else if (action === 'first' && typeof stage.goTo === 'function') {
2968+ stage.goTo(0);
2969+ } else if (action === 'last' && typeof stage.goTo === 'function') {
2970+ stage.goTo(list.length - 1);
2971+ } else {
2972+ return false;
2973+ }
2974+ // Public deck-stage methods are synchronous in the authored runtime.
2975+ // Report here for older implementations that do not emit slidechange;
2976+ // modern implementations also emit the event and are harmlessly
2977+ // coalesced by React state equality in the host.
2978+ report();
2979+ return true;
2980+ } catch (_) {
2981+ return false;
2982+ }
28942983 }
28952984 function scrollOverflow(el){
28962985 if (!el) return 0;
@@ -2989,6 +3078,8 @@ function injectDeckBridge(
29893078 var w = Math.max(1, window.innerWidth);
29903079 return Math.max(0, Math.min(list.length - 1, Math.round(maxScrollLeft() / w)));
29913080 }
3081+ var byDeckStage = activeIndexFromDeckStage(list);
3082+ if (byDeckStage >= 0) return byDeckStage;
29923083 var byTransform = activeIndexFromTransform(list);
29933084 if (byTransform >= 0) return byTransform;
29943085 var byClass = findActiveByClass(list);
@@ -3287,6 +3378,7 @@ function injectDeckBridge(
32873378 function go(action){
32883379 var list = slides();
32893380 if (!list.length) return;
3381+ if (navigateViaDeckStage(list, action)) return;
32903382 if (isScrollDeck()) {
32913383 scrollGo(Math.max(0, Math.min(list.length - 1, targetFor(action, list))));
32923384 return;
@@ -3307,6 +3399,7 @@ function injectDeckBridge(
33073399 var list = slides();
33083400 if (!list.length) return;
33093401 var target = Math.max(0, Math.min(list.length - 1, i));
3402+ if (navigateViaDeckStage(list, 'go', target)) return;
33103403 if (isScrollDeck()) { scrollGo(target); return; }
33113404 if (activeIndex(list) === target) { report(); return; }
33123405 stepToIndexViaKeys(target, function(stepped){
@@ -3390,6 +3483,17 @@ function injectDeckBridge(
33903483 }
33913484 } catch (e) {}
33923485 }
3486+ // Runtime-managed decks can also move themselves through their own tap
3487+ // zones, keyboard handler, or public API. Normalize both generations of
3488+ // deck-stage state notification back into the host-owned slide protocol.
3489+ document.addEventListener('slidechange', function(ev){
3490+ var stage = document.querySelector('deck-stage');
3491+ if (stage && ev.target === stage) report();
3492+ });
3493+ window.addEventListener('message', function(ev){
3494+ var data = ev && ev.data;
3495+ if (data && typeof data.slideIndexChanged === 'number') report();
3496+ });
33933497 window.__odDeckSlideState = function(){
33943498 var list = slides();
33953499 return { active: activeIndex(list), count: list.length };
0 commit comments