|
1 | 1 | /** |
2 | 2 | * @module RevealJsTabset |
3 | | - * @version 1.2.0 |
| 3 | + * @version 1.3.0 |
4 | 4 | * @license MIT |
5 | 5 | * @copyright 2026 Mickaël Canouil |
6 | 6 | * @author Mickaël Canouil |
@@ -106,56 +106,96 @@ window.RevealJsTabset = function () { |
106 | 106 | }); |
107 | 107 |
|
108 | 108 | /** |
109 | | - * Handle PDF export mode. |
110 | | - * Ensures the correct tab is visible based on fragment state. |
| 109 | + * Update tab link and pane states for a given active tab index. |
| 110 | + * @param {Element} tabset - The tabset container element |
| 111 | + * @param {number} activeTabIndex - The index of the tab to activate |
111 | 112 | */ |
112 | | - deck.on("pdf-ready", function () { |
113 | | - const slides = document.querySelectorAll(".reveal .slides section"); |
114 | | - |
115 | | - slides.forEach(function (slide) { |
116 | | - const tabset = slide.querySelector(".panel-tabset"); |
117 | | - if (!tabset) return; |
| 113 | + function updateTabState(tabset, activeTabIndex) { |
| 114 | + const tabLinks = tabset.querySelectorAll(TAB_LINK_SELECTOR); |
| 115 | + const tabPanesArray = Array.from(getTabPanes(tabset)); |
118 | 116 |
|
119 | | - const fragments = slide.querySelectorAll(".panel-tabset-fragment"); |
120 | | - let activeTabIndex = 0; |
| 117 | + tabLinks.forEach(function (link, index) { |
| 118 | + const li = link.parentElement; |
| 119 | + const isActive = index === activeTabIndex; |
121 | 120 |
|
122 | | - // Find the highest visible tab index |
123 | | - fragments.forEach(function (fragment) { |
124 | | - if (fragment.classList.contains("visible")) { |
125 | | - const tabIndex = parseInt(fragment.dataset.tabIndex, 10); |
126 | | - if (!isNaN(tabIndex) && tabIndex > activeTabIndex) { |
127 | | - activeTabIndex = tabIndex; |
128 | | - } |
129 | | - } |
130 | | - }); |
| 121 | + li.classList.toggle("active", isActive); |
| 122 | + link.setAttribute("aria-selected", isActive ? "true" : "false"); |
| 123 | + link.setAttribute("tabindex", isActive ? "0" : "-1"); |
| 124 | + }); |
131 | 125 |
|
132 | | - // Update tab states |
133 | | - const tabLinks = tabset.querySelectorAll(TAB_LINK_SELECTOR); |
134 | | - const tabPanes = getTabPanes(tabset); |
135 | | - const tabPanesArray = Array.from(tabPanes); |
| 126 | + tabPanesArray.forEach(function (panel, index) { |
| 127 | + const isActive = index === activeTabIndex; |
136 | 128 |
|
137 | | - tabLinks.forEach(function (link, index) { |
138 | | - const li = link.parentElement; |
139 | | - const isActive = index === activeTabIndex; |
| 129 | + panel.classList.toggle("active", isActive); |
| 130 | + panel.style.display = isActive ? "block" : "none"; |
| 131 | + if (isActive) { |
| 132 | + panel.removeAttribute("hidden"); |
| 133 | + } else { |
| 134 | + panel.setAttribute("hidden", ""); |
| 135 | + } |
| 136 | + }); |
| 137 | + } |
140 | 138 |
|
141 | | - li.classList.toggle("active", isActive); |
142 | | - link.setAttribute("aria-selected", isActive ? "true" : "false"); |
143 | | - link.setAttribute("tabindex", isActive ? "0" : "-1"); |
144 | | - }); |
| 139 | + /** |
| 140 | + * Handle PDF export mode. |
| 141 | + * When pdfSeparateFragments is enabled, updates tab visibility based |
| 142 | + * on fragment state (existing behaviour). |
| 143 | + * Otherwise, clones slides for each tab so every tab appears on its |
| 144 | + * own PDF page without affecting other fragments in the deck. |
| 145 | + */ |
| 146 | + deck.on("pdf-ready", function () { |
| 147 | + const config = deck.getConfig(); |
| 148 | + const slides = document.querySelectorAll(".reveal .slides section"); |
145 | 149 |
|
146 | | - // Update pane visibility |
147 | | - tabPanesArray.forEach(function (panel, index) { |
148 | | - const isActive = index === activeTabIndex; |
| 150 | + if (config.pdfSeparateFragments) { |
| 151 | + // Existing behaviour: determine active tab from visible fragments |
| 152 | + slides.forEach(function (slide) { |
| 153 | + const tabset = slide.querySelector(".panel-tabset"); |
| 154 | + if (!tabset) return; |
| 155 | + |
| 156 | + const fragments = slide.querySelectorAll(".panel-tabset-fragment"); |
| 157 | + let activeTabIndex = 0; |
| 158 | + |
| 159 | + fragments.forEach(function (fragment) { |
| 160 | + if (fragment.classList.contains("visible")) { |
| 161 | + const tabIndex = parseInt(fragment.dataset.tabIndex, 10); |
| 162 | + if (!isNaN(tabIndex) && tabIndex > activeTabIndex) { |
| 163 | + activeTabIndex = tabIndex; |
| 164 | + } |
| 165 | + } |
| 166 | + }); |
149 | 167 |
|
150 | | - panel.classList.toggle("active", isActive); |
151 | | - panel.style.display = isActive ? "block" : "none"; |
152 | | - if (isActive) { |
153 | | - panel.removeAttribute("hidden"); |
154 | | - } else { |
155 | | - panel.setAttribute("hidden", ""); |
| 168 | + updateTabState(tabset, activeTabIndex); |
| 169 | + }); |
| 170 | + } else { |
| 171 | + // Clone slides so each tab gets its own PDF page |
| 172 | + slides.forEach(function (slide) { |
| 173 | + const tabset = slide.querySelector(".panel-tabset"); |
| 174 | + if (!tabset) return; |
| 175 | + |
| 176 | + const tabLinks = tabset.querySelectorAll(TAB_LINK_SELECTOR); |
| 177 | + const tabCount = tabLinks.length; |
| 178 | + if (tabCount <= 1) return; |
| 179 | + |
| 180 | + const pageElement = slide.closest(".pdf-page") || slide; |
| 181 | + |
| 182 | + // Show tab 0 on the original slide |
| 183 | + updateTabState(tabset, 0); |
| 184 | + |
| 185 | + // Clone for each remaining tab |
| 186 | + let insertAfter = pageElement; |
| 187 | + for (let i = 1; i < tabCount; i++) { |
| 188 | + const clone = pageElement.cloneNode(true); |
| 189 | + const cloneTabset = clone.querySelector(".panel-tabset"); |
| 190 | + updateTabState(cloneTabset, i); |
| 191 | + insertAfter.parentNode.insertBefore( |
| 192 | + clone, |
| 193 | + insertAfter.nextSibling, |
| 194 | + ); |
| 195 | + insertAfter = clone; |
156 | 196 | } |
157 | 197 | }); |
158 | | - }); |
| 198 | + } |
159 | 199 | }); |
160 | 200 | }, |
161 | 201 | }; |
|
0 commit comments