Skip to content

Commit

Permalink
Fix enabling and disabling active tabs from custom shortcode (#11731)
Browse files Browse the repository at this point in the history
The previous logic kept track of the tabs when building the children,
but didn't account for there being nested tabs. Now children tabs are
determined when building the parent wrapper and inspecting its children
contents. The corresponding JS is also updated to only disable sibling
tabs rather than all children when toggling between them.

Some additional tab validation is added as well.

This fixes tab logic in a few places, but particularly /get-started/install/macos/mobile-ios/#configure-ios-development
  • Loading branch information
parlough authored Feb 19, 2025
1 parent 207a81f commit 0369088
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 36 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"firebase-tools": "^13.29.3",
"hast-util-from-html": "^2.0.3",
"hast-util-select": "^6.0.3",
"hast-util-to-html": "^9.0.4",
"hast-util-to-text": "^4.0.2",
"html-minifier-terser": "^7.2.0",
"js-yaml": "^4.1.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 39 additions & 31 deletions src/_11ty/shortcodes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { UserConfig } from '@11ty/eleventy';
import { slugify } from './utils/slugify.js';
import {UserConfig} from '@11ty/eleventy';
import {slugify} from './utils/slugify.js';
import {fromHtml} from 'hast-util-from-html';
import {selectAll} from 'hast-util-select';
import {toHtml} from 'hast-util-to-html';

export function registerShortcodes(eleventyConfig: UserConfig): void {
_setupTabs(eleventyConfig);
Expand Down Expand Up @@ -33,55 +36,60 @@ function _setupMedia(eleventyConfig: UserConfig): void {
}

function _setupTabs(eleventyConfig: UserConfig) {
// Variable shared between all tabs to ensure each has a unique ID.
let currentTabId = 0;

// Variables that are shared between the tabs within a tabs shortcode,
// and should be reset before returning from tabs.
let tabs = [];
let markTabAsActive = true;
// Counter shared between all tabs and wrappers to
// ensure each has a unique ID.
let currentTabWrapperId = 0;
let currentTabPaneId = 0;

eleventyConfig.addPairedShortcode('tabs', function (content: string, saveKey: string) {
let tabMarkup = `<div class="tabs-wrapper" ${saveKey ? `data-tab-save-key="${slugify(saveKey)}"` : ''}><ul class="nav nav-tabs" role="tablist">`;
let activeTab = true;
const tabWrapperId = currentTabWrapperId++;
let tabMarkup = `<div id="${tabWrapperId}" class="tabs-wrapper" ${saveKey ? `data-tab-save-key="${slugify(saveKey)}"` : ''}><ul class="nav-tabs" role="tablist">`;

tabs.forEach((tab) => {
const tabName = tab.name;
const tabSaveId = slugify(tabName);
const tabId = `${tab.id}-tab`;
// Only select child tab panes that don't already have a parent wrapper.
const tabPanes = selectAll('.tab-pane[data-tab-wrapper-id="undefined"]', fromHtml(content));
if (tabPanes.length <= 1) {
throw new Error(`Tabs with save key of ${saveKey} needs more than one tab!`);
}

let setTabToActive = true;
for (const tabPane of tabPanes) {
// Keep track of the tab wrapper ID to avoid including
// a duplicate of this tab's contents in a parent wrapper.
tabPane.properties.dataTabWrapperId = tabWrapperId;

const tabId = tabPane.properties.dataTabId! as string;
const tabPanelId = `${tabId}-panel`;
const tabName = tabPane.properties.dataTabName! as string;
const tabIsActive = setTabToActive;

// Only set the first tab of a wrapper to active initially.
if (tabIsActive) {
tabPane.properties['className'] += ' active';
setTabToActive = false;
}

const tabSaveId = slugify(tabName);
tabMarkup += `<li class="nav-item">
<a class="nav-link ${activeTab ? "active" : ""}" data-tab-save-id="${tabSaveId}" id="${tabId}" href="#${tabPanelId}" role="tab" aria-controls="${tabPanelId}" aria-selected="${activeTab ? "true" : "false"}">${tabName}</a>
<a class="nav-link ${tabIsActive ? "active" : ""}" data-tab-save-id="${tabSaveId}" id="${tabId}" href="#${tabPanelId}" role="tab" aria-controls="${tabPanelId}" aria-selected="${tabIsActive ? "true" : "false"}">${tabName}</a>
</li>`;
activeTab = false;
});
}

tabMarkup += '</ul><div class="tab-content">\n';
tabMarkup += content;
tabMarkup += toHtml(tabPanes);
tabMarkup += '\n</div></div>';

// Reset shared variables.
tabs = [];
markTabAsActive = true;

return tabMarkup;
});

eleventyConfig.addPairedShortcode('tab', function (content: string, tabName: string) {
const tabIdNumber = currentTabId++;
tabs.push({name: tabName, id: tabIdNumber});
const tabIdNumber = currentTabPaneId++;
const tabId = `${tabIdNumber}-tab`;
const tabPanelId = `${tabId}-panel`;
const tabContent = `<div class="tab-pane ${markTabAsActive ? "active" : ""}" id="${tabPanelId}" role="tabpanel" aria-labelledby="${tabId}">
return `<div class="tab-pane" id="${tabPanelId}" role="tabpanel" aria-labelledby="${tabId}" data-tab-id="${tabId}" data-tab-name="${tabName}" data-tab-wrapper-id="undefined">
${content}
</div>
`;

// No other tabs should be marked as active.
markTabAsActive = false;

return tabContent;
});
}
2 changes: 1 addition & 1 deletion src/_includes/docs/install/deprecated/ios-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ Enabling certificates varies in different versions of iOS.

1. Tap your Certificate.

1. Tap **Trust "\<certificate\>"**.
1. Tap **Trust "&lt;certificate&gt;"**.

1. When the dialog displays, tap **Trust**.

Expand Down
2 changes: 1 addition & 1 deletion src/_includes/docs/install/devices/ios-physical.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Enabling certificates varies in different versions of iOS.

1. Tap your Certificate.

1. Tap **Trust "\<certificate\>"**.
1. Tap **Trust "&lt;certificate&gt;"**.

1. When the dialog displays, tap **Trust**.

Expand Down
7 changes: 4 additions & 3 deletions src/content/assets/js/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function setupTabs() {
tabsWrappers.forEach(function (tabWrapper) {
const saveKey = tabWrapper.dataset.tabSaveKey;
const localStorageKey = `tab-save-${saveKey}`;
const tabs = tabWrapper.querySelectorAll('a.nav-link');
const tabs = tabWrapper.querySelectorAll(':scope > .nav-tabs a.nav-link');
let tabToChangeTo;

tabs.forEach(function (tab) {
Expand Down Expand Up @@ -87,9 +87,10 @@ function _findAndActivateTabsWithSaveId(saveKey, saveId) {
}

function _activateTabWithSaveId(tabWrapper, saveId) {
const tabToActivate = tabWrapper.querySelector(`a.nav-link[data-tab-save-id="${saveId}"]`);
const tabsNav = tabWrapper.querySelector(':scope > .nav-tabs');
const tabToActivate = tabsNav.querySelector(`a.nav-link[data-tab-save-id="${saveId}"]`);
if (tabToActivate) {
const tabs = tabWrapper.querySelectorAll('a.nav-link');
const tabs = tabsNav.querySelectorAll('a.nav-link');
_clearActiveTabs(tabs);
_setActiveTab(tabToActivate);
}
Expand Down

0 comments on commit 0369088

Please sign in to comment.