diff --git a/dist/shoelace.js b/dist/shoelace.js index 9ba99531c..6afec1ab8 100644 --- a/dist/shoelace.js +++ b/dist/shoelace.js @@ -5,11 +5,11 @@ Released under the MIT license Source: https://github.com/claviska/shoelace-css */ -!function(){"use strict";if("undefined"==typeof jQuery&&"undefined"==typeof Zepto)throw new Error("Shoelace dropdowns require either jQuery or Zepto.");("function"==typeof jQuery?jQuery:Zepto)(function(e){e(document).on("click",function(t){var i,o,r;if(e(t.target).closest(".dropdown-trigger")){if(i=e(t.target).closest(".dropdown"),r=t.target,e(".dropdown.active").not(i).removeClass("active").trigger("hide"),e(r).is(".disabled, :disabled"))return;e(i).toggleClass("active").trigger(e(i).is(".active")?"show":"hide")}else e(t.target).closest(".dropdown-menu").length&&(i=e(t.target).closest(".dropdown"),(o=e(t.target).closest("a").get(0))&&!e(o).is(".disabled")&&e(i).trigger("select",o),t.preventDefault()),e(".dropdown.active").removeClass("active").trigger("hide")}).on("keydown",function(t){27===t.keyCode&&e(".dropdown.active").removeClass("active").trigger("hide")})})}(),/*! +!function(){"use strict";if("undefined"==typeof jQuery&&"undefined"==typeof Zepto)throw new Error("Shoelace dropdowns require either jQuery or Zepto.");("function"==typeof jQuery?jQuery:Zepto)(function(e){e(document).on("click",function(t){var r,i,o;if(e(t.target).closest(".dropdown-trigger")){if(r=e(t.target).closest(".dropdown"),o=t.target,e(".dropdown.active").not(r).removeClass("active").trigger("hide"),e(o).is(".disabled, :disabled"))return;e(r).toggleClass("active").trigger(e(r).is(".active")?"show":"hide")}else e(t.target).closest(".dropdown-menu").length&&(r=e(t.target).closest(".dropdown"),(i=e(t.target).closest("a").get(0))&&!e(i).is(".disabled")&&e(r).trigger("select",i),t.preventDefault()),e(".dropdown.active").removeClass("active").trigger("hide")}).on("keydown",function(t){27===t.keyCode&&e(".dropdown.active").removeClass("active").trigger("hide")})})}(),/*! Shoelace.css tabs 1.0.0-beta20 (c) A Beautiful Site, LLC Released under the MIT license Source: https://github.com/claviska/shoelace-css */ -function(){"use strict";if("undefined"==typeof jQuery&&"undefined"==typeof Zepto)throw new Error("Shoelace tabs require either jQuery or Zepto.");(window.jQuery||window.Zepto)(function(e){e(document).on("click",".tabs-nav a",function(t){var i=e(this).closest(".tabs"),o=this,r=e(i).find(o.hash).get(0);t.preventDefault(),o.hash&&!e(o).is(".disabled")&&(e(o).siblings().removeClass("active"),e(o).addClass("active"),e(i).find(".tabs-pane.active").not(r).each(function(){e(this).removeClass("active"),e(i).trigger("hide",this)}),r&&!e(r).is(".active")&&(e(r).addClass("active"),e(i).trigger("show",r)))})})}(); \ No newline at end of file +function(){"use strict";if("undefined"==typeof jQuery&&"undefined"==typeof Zepto)throw new Error("Shoelace tabs require either jQuery or Zepto.");(window.jQuery||window.Zepto)(function(e){new MutationObserver(function(t){t.forEach(function(t){if("attributes"===t.type&&"class"===t.attributeName){var r=e(t.target).get(0),i=e(t.target).closest(".tabs").get(0),o=e(t.target).closest(".tabs-nav").get(0),a="A"===r.tagName?e(i).find(r.hash):null;if(!e(r).is("a")||!i||!o)return;if(e(r).is(".disabled.active"))return void e(r).removeClass("active");e(r).is(".active")?(e(r).siblings(".active").removeClass("active"),e(a).addClass("active"),e(i).trigger("show",a)):(e(a).removeClass("active"),e(i).trigger("hide",a))}})}).observe(document.body,{attributes:!0,attributeFilter:["class"],attributeOldValue:!0,subtree:!0}),e(document).on("click",".tabs-nav a",function(t){var r=this;t.preventDefault(),e(r).is(".disabled")||e(r).addClass("active")})})}(); \ No newline at end of file diff --git a/docs/tabs.html b/docs/tabs.html index 02e7a0344..816a9c791 100644 --- a/docs/tabs.html +++ b/docs/tabs.html @@ -53,8 +53,8 @@

Tabs

Tab sets can be created using the markup below. By default, Shoelace renders tabs as pills because they respond better than traditional tabs when rendered on smaller screens.

-

Note the class names used for the main container, the tab navs, and the tab panes. Also note that each tab links to its respective tab pane’s id.

-

To disable a tab, add disabled to the appropriate tab nav.

+

Note the class names used for the main container, the tabs, and the tab panes. Also note that each tab links to its respective tab pane’s id.

+

For initial rendering, make sure the appropriate tab and tab pane have the active class.

<div class="tabs">
   <nav class="tabs-nav">
     <a href="#tab-1" class="active">Tab 1</a>
@@ -113,7 +113,7 @@
 

Vertical Tabs

-

Tabs can be made vertical when used along with the grid system.

+

Tabs can be made vertical when used with the grid system.

<div class="tabs">
   <div class="row">
     <div class="col-4">
@@ -172,9 +172,9 @@
   
 
 
-

Events

-

Tabs require shoelace.js to make them interactive. You don’t need to initialize them. Simply include the script and everything “just works.”

-

There is no JavaScript API. Shoelace’s philosophy believes that custom components should act like native components as much as possible. You can, however, listen for various events:

+

Interactivity

+

Tabs require shoelace.js for interactivity. You don’t need to initialize anything. Just include the script and everything “just works.”

+

There is no JavaScript API. Shoelace’s philosophy believes that custom components should act like native components as much as possible. You can, however, listen for various events.

+

To activate a tab programmatically, just add the active class to it. We use a mutation observer to remove the active class on other tabs and to show/hide the appropriate tab panes automatically.

+
$('#tab-id').addClass('active');
+
+

To disable a tab, add the disabled class to the appropriate tab.

diff --git a/source/docs/tabs.md b/source/docs/tabs.md index 3406a1046..560ebb6a9 100644 --- a/source/docs/tabs.md +++ b/source/docs/tabs.md @@ -8,9 +8,9 @@ description: Add tabs to your app with the tabs component. Tab sets can be created using the markup below. By default, Shoelace renders tabs as pills because they respond better than traditional tabs when rendered on smaller screens. -Note the class names used for the main container, the tab navs, and the tab panes. Also note that each tab links to its respective tab pane’s `id`. +Note the class names used for the main container, the tabs, and the tab panes. Also note that each tab links to its respective tab pane’s `id`. -To disable a tab, add `disabled` to the appropriate tab nav. +For initial rendering, make sure the appropriate tab and tab pane have the `active` class. ```html
@@ -73,7 +73,7 @@ To disable a tab, add `disabled` to the appropriate tab nav. ### Vertical Tabs -Tabs can be made vertical when used along with the [grid system](grid-system.html). +Tabs can be made vertical when used with the [grid system](grid-system.html). ```html
@@ -135,11 +135,11 @@ Tabs can be made vertical when used along with the [grid system](grid-system.htm
-### Events +### Interactivity -Tabs require `shoelace.js` to make them interactive. You don’t need to initialize them. Simply include the script and everything “just works.” +Tabs require `shoelace.js` for interactivity. You don’t need to initialize anything. Just include the script and everything “just works.” -There is no JavaScript API. Shoelace’s philosophy believes that custom components should act like native components as much as possible. You can, however, listen for various events: +There is no JavaScript API. Shoelace’s philosophy believes that custom components should act like native components as much as possible. You can, however, listen for various events. - `show` – Fires when a tab is shown. The second callback argument is a reference to the respective tab pane. - `hide` – Fires when a tab is hidden. The second callback argument is a reference to the respective tab pane. @@ -155,3 +155,11 @@ $('#my-tabs') console.log('hide', event.target, tabPane); }); ``` + +To activate a tab programmatically, just add the `active` class to it. We use a [mutation observer](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to remove the active class on other tabs and to show/hide the appropriate tab panes automatically. + +```javascript +$('#tab-id').addClass('active'); +``` + +To disable a tab, add the `disabled` class to the appropriate tab. diff --git a/source/js/tabs.js b/source/js/tabs.js index 2f4924b4a..4c2cf1fbf 100644 --- a/source/js/tabs.js +++ b/source/js/tabs.js @@ -23,7 +23,7 @@ // Tabs not toggling? // - Make sure you've loaded jQuery or Zepto before this script // - Make sure your tabs are structured properly per the docs -// - Make sure your tab navs and tab panes have the correct IDs +// - Make sure your tab navs and tab panes have the correct href and id attributes // (function() { /* eslint-env browser, jquery */ @@ -35,34 +35,62 @@ } (window.jQuery || window.Zepto)(function($) { + // Watch for mutations on tabs and show the appropriate tab pane + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + // Only observe class changes + if(mutation.type === 'attributes' && mutation.attributeName === 'class') { + var tab = $(mutation.target).get(0); + var tabs = $(mutation.target).closest('.tabs').get(0); + var tabsNav = $(mutation.target).closest('.tabs-nav').get(0); + var tabPane = tab.tagName === 'A' ? $(tabs).find(tab.hash) : null; + + // The mutation must be on a tab + if(!$(tab).is('a') || !tabs || !tabsNav) return; + + // Disabled tabs can't receive the active class, so we just remove it + if($(tab).is('.disabled.active')) { + $(tab).removeClass('active'); + return; + } + + // Toggle the tab pane based on the tab's active state + if($(tab).is('.active')) { + // Remove the active class from other tabs + $(tab).siblings('.active').removeClass('active'); + + // Show the selected tab + $(tabPane).addClass('active'); + $(tabs).trigger('show', tabPane); + } else { + // Hide the previously selected tab + $(tabPane).removeClass('active'); + $(tabs).trigger('hide', tabPane); + } + } + }); + }); + + // Observe the body so we can handle dynamically created elements + observer.observe(document.body, { + attributes: true, + attributeFilter: ['class'], + attributeOldValue: true, + subtree: true + }); + // Watch for clicks on tabs $(document).on('click', '.tabs-nav a', function(event) { - var tabs = $(this).closest('.tabs'); - var tabNav = this; - var selectedPane = $(tabs).find(tabNav.hash).get(0); + var tab = this; event.preventDefault(); - // Ignore tabs without an href or with the "disabled" class - if(!tabNav.hash || $(tabNav).is('.disabled')) { - return; - } + // Ignore disabled tabs + if($(tab).is('.disabled')) return; - // Make the selected tab active - $(tabNav).siblings().removeClass('active'); - $(tabNav).addClass('active'); - - // Hide active tab panes that aren't getting selected - $(tabs).find('.tabs-pane.active').not(selectedPane).each(function() { - $(this).removeClass('active'); - $(tabs).trigger('hide', this); - }); - - // Show the selected tab pane - if(selectedPane && !$(selectedPane).is('.active')) { - $(selectedPane).addClass('active'); - $(tabs).trigger('show', selectedPane); - } + // Make the selected tab active. No need to worry about showing the tab pane or making other + // tabs inactive because the mutation observer will handle that. + $(tab).addClass('active'); }); }); })();