tabs.ts 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. type TabGroup = HTMLDivElement[];
  2. const TAB_CLASS = 'tab';
  3. const TAB_CONTROL_CLASS = 'tab-control';
  4. const TAB_CONTROL_WRAPPER_CLASS = 'tab-controls';
  5. const CONTAINER_CLASS = 'tab-container';
  6. const ACTIVE_CLASS = 'active';
  7. export function initTabs() {
  8. const tabs = document.querySelectorAll<HTMLDivElement>(`.${TAB_CLASS}`);
  9. const tabGroups = groupTabs(Array.from(tabs));
  10. const containers = tabGroups.map(g => wrapTabGroup(g));
  11. containers.forEach(container => {
  12. container.addEventListener('click', e => {
  13. const target = e.target as HTMLElement;
  14. if (target.classList.contains(TAB_CONTROL_CLASS)) {
  15. const tabId = target.dataset.id;
  16. // deactivate all sibling tabs & controls
  17. const tabsInGroup = Array.from(container.querySelectorAll(`.${TAB_CLASS}`));
  18. const controlsInGroup = Array.from(container.querySelectorAll(`.${TAB_CONTROL_CLASS}`));
  19. [...tabsInGroup, ...controlsInGroup].forEach(tab => tab.classList.remove(ACTIVE_CLASS));
  20. // activate the newly selected tab & control
  21. target.classList.add(ACTIVE_CLASS);
  22. tabsInGroup.filter(t => t.id === tabId).forEach(tab => tab.classList.add(ACTIVE_CLASS));
  23. }
  24. });
  25. });
  26. }
  27. /**
  28. * Groups sibling tabs together.
  29. */
  30. function groupTabs(tabs: HTMLDivElement[]): TabGroup[] {
  31. const tabGroups: TabGroup[] = [];
  32. const remainingTabs = tabs.slice();
  33. let next: HTMLDivElement | undefined;
  34. do {
  35. next = remainingTabs.shift();
  36. if (next) {
  37. const group: TabGroup = [next];
  38. let nextSibling = next.nextElementSibling;
  39. while (nextSibling === remainingTabs[0]) {
  40. if (nextSibling) {
  41. // tslint:disable-next-line:no-non-null-assertion
  42. group.push(remainingTabs.shift()!);
  43. }
  44. nextSibling = next.nextElementSibling;
  45. }
  46. tabGroups.push(group);
  47. }
  48. } while (next);
  49. return tabGroups;
  50. }
  51. /**
  52. * Wrap the tabs of the group in a container div and add the tab controls
  53. */
  54. function wrapTabGroup(tabGroup: TabGroup): HTMLDivElement {
  55. const tabControls = tabGroup.map(({ title }, i) => {
  56. return `<button class="${TAB_CONTROL_CLASS} ${i === 0 ? ACTIVE_CLASS : ''}" data-id="${toId(title)}">${title}</button>`;
  57. }).join('');
  58. const wrapper = document.createElement('div');
  59. wrapper.classList.add(CONTAINER_CLASS);
  60. wrapper.innerHTML = `<div class="${TAB_CONTROL_WRAPPER_CLASS}">${tabControls}</div>`;
  61. const parent = tabGroup[0].parentElement;
  62. if (parent) {
  63. parent.insertBefore(wrapper, tabGroup[0]);
  64. }
  65. tabGroup.forEach((tab, i) => {
  66. tab.id = toId(tab.title);
  67. if (i === 0) {
  68. tab.classList.add(ACTIVE_CLASS);
  69. }
  70. wrapper.appendChild(tab);
  71. });
  72. return wrapper;
  73. }
  74. /**
  75. * Generate a normalized ID based on the title
  76. */
  77. function toId(title: string): string {
  78. return 'tab-' + title.toLowerCase().replace(/[^a-zA-Z]/g, '-');
  79. }