Skip to content

Commit 1bd2ece

Browse files
committed
chore: update tabset plugin for clean default PDF export
1 parent 3300e4f commit 1bd2ece

1 file changed

Lines changed: 81 additions & 41 deletions

File tree

  • src/resources/formats/revealjs/plugins/tabset

src/resources/formats/revealjs/plugins/tabset/tabset.js

Lines changed: 81 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @module RevealJsTabset
3-
* @version 1.2.0
3+
* @version 1.3.0
44
* @license MIT
55
* @copyright 2026 Mickaël Canouil
66
* @author Mickaël Canouil
@@ -106,56 +106,96 @@ window.RevealJsTabset = function () {
106106
});
107107

108108
/**
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
111112
*/
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));
118116

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;
121120

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+
});
131125

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;
136128

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+
}
140138

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");
145149

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+
});
149167

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;
156196
}
157197
});
158-
});
198+
}
159199
});
160200
},
161201
};

0 commit comments

Comments
 (0)