tabs.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  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. next = remainingTabs.shift();
  42. // tslint:disable-next-line:no-non-null-assertion
  43. group.push(next!);
  44. }
  45. // tslint:disable-next-line:no-non-null-assertion
  46. nextSibling = next!.nextElementSibling;
  47. }
  48. tabGroups.push(group);
  49. }
  50. } while (next);
  51. return tabGroups;
  52. }
  53. /**
  54. * Wrap the tabs of the group in a container div and add the tab controls
  55. */
  56. function wrapTabGroup(tabGroup: TabGroup): HTMLDivElement {
  57. const tabControls = tabGroup.map(({ title }, i) => {
  58. return `<button class="${TAB_CONTROL_CLASS} ${i === 0 ? ACTIVE_CLASS : ''}" data-id="${toId(title)}">${title}</button>`;
  59. }).join('');
  60. const wrapper = document.createElement('div');
  61. wrapper.classList.add(CONTAINER_CLASS);
  62. wrapper.innerHTML = `<div class="${TAB_CONTROL_WRAPPER_CLASS}">${tabControls}</div>`;
  63. const parent = tabGroup[0].parentElement;
  64. if (parent) {
  65. parent.insertBefore(wrapper, tabGroup[0]);
  66. }
  67. tabGroup.forEach((tab, i) => {
  68. tab.id = toId(tab.title);
  69. if (i === 0) {
  70. tab.classList.add(ACTIVE_CLASS);
  71. }
  72. wrapper.appendChild(tab);
  73. });
  74. return wrapper;
  75. }
  76. /**
  77. * Generate a normalized ID based on the title
  78. */
  79. function toId(title: string): string {
  80. return 'tab-' + title.toLowerCase().replace(/[^a-zA-Z]/g, '-');
  81. }