Skip to content

Commit

Permalink
feat: support initial and hide
Browse files Browse the repository at this point in the history
  • Loading branch information
v8tenko committed Aug 12, 2024
1 parent d36f6b6 commit e6ea43c
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const TAB_CLASSNAME = 'yfm-tab';
export const TAB_PANEL_CLASSNAME = 'yfm-tab-panel';
export const ACTIVE_CLASSNAME = 'active';
export const VERTICAL_TAB_CLASSNAME = 'yfm-vertical-tab';
export const VERTICAL_TAB_FORCED_OPEN = 'data-diplodoc-radio-forced';

export const GROUP_DATA_KEY = 'data-diplodoc-group';
export const TAB_DATA_KEY = 'data-diplodoc-key';
Expand Down
66 changes: 54 additions & 12 deletions src/plugin/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
TAB_DATA_VERTICAL_TAB,
TAB_PANEL_CLASSNAME,
VERTICAL_TAB_CLASSNAME,
VERTICAL_TAB_FORCED_OPEN,
} from '../common';

export type PluginOptions = {
Expand All @@ -30,7 +31,7 @@ export type PluginOptions = {

export type TabsOrientation = 'radio' | 'horizontal';

const TAB_RE = /`?{% list tabs( group=([^ ]*))?( (radio)|(horizontal))? %}`?/;
const TAB_RE = /`?{% list tabs .*%}`?/;

let runsCounter = 0;

Expand Down Expand Up @@ -125,6 +126,7 @@ function insertTabs(
tabs: Tab[],
state: StateCore,
align: TabsOrientation,
initial: number | undefined,
{start, end}: {start: number; end: number},
{
containerClasses,
Expand Down Expand Up @@ -227,16 +229,17 @@ function insertTabs(
if (align === 'radio') {
tabOpen.attrSet(TAB_DATA_VERTICAL_TAB, 'true');
tabOpen.attrJoin('class', VERTICAL_TAB_CLASSNAME);
}

if (i === 0) {
if (align === 'horizontal') {
tabOpen.attrJoin('class', ACTIVE_CLASSNAME);
tabOpen.attrSet('aria-selected', 'true');
} else {
if (i + 1 === initial) {
tabOpen.attrSet(VERTICAL_TAB_FORCED_OPEN, 'true');
verticalTabOpen.attrSet('checked', 'true');
tabPanelOpen.attrJoin('class', ACTIVE_CLASSNAME);
}
}

if (i === 0 && align === 'horizontal') {
tabOpen.attrJoin('class', ACTIVE_CLASSNAME);
tabOpen.attrSet('aria-selected', 'true');
tabPanelOpen.attrJoin('class', ACTIVE_CLASSNAME);
}

Expand Down Expand Up @@ -291,14 +294,53 @@ function matchCloseToken(tokens: Token[], i: number) {
);
}

type TabsProps = {
content: string;
orientation: TabsOrientation;
group: string;
initial?: number;
};

function matchOpenToken(tokens: Token[], i: number) {
return (
tokens[i].type === 'paragraph_open' &&
tokens[i + 1].type === 'inline' &&
tokens[i + 1].content.match(TAB_RE)
TAB_RE.test(tokens[i + 1].content) &&
matchProps(tokens[i + 1])
);
}

function matchProps(token: Token) {
const {content} = token;
const query = 'list tabs ';

let index = content.indexOf(query) + query.length;

let inner = '';

while (content[index] !== '%') {
inner += content[index];

index++;
}

const args = inner.split(' ');
const orientation: TabsOrientation = args.includes('radio') ? 'radio' : 'horizontal';
const group =
args.find((str) => str.startsWith('group'))?.slice('group'.length + 1) ||
`${DEFAULT_TABS_GROUP_PREFIX}${generateID()}`;
const initial = args.find((str) => str.startsWith('initial'))?.slice('initial'.length + 1);

const props: TabsProps = {
content,
orientation,
group,
initial: typeof initial === 'undefined' ? undefined : Number(initial),
};

return props;
}

export function transform({
runtimeJsPath = '_assets/tabs-extension.js',
runtimeCssPath = '_assets/tabs-extension.css',
Expand All @@ -318,7 +360,7 @@ export function transform({

while (i < tokens.length) {
const match = matchOpenToken(tokens, i);
const openTag = match && match[0];
const openTag = match && match.content;
const isNotEscaped = openTag && !(openTag.startsWith('`') && openTag.endsWith('`'));

if (!match || !isNotEscaped) {
Expand All @@ -334,16 +376,16 @@ export function transform({
continue;
}

const tabsGroup = match[2] || `${DEFAULT_TABS_GROUP_PREFIX}${generateID()}`;
const orientation = (match[4] || 'horizontal') as TabsOrientation;
const tabsGroup = match.group;

const {tabs} = findTabs(state.tokens, i + 3);

if (tabs.length > 0) {
insertTabs(
tabs,
state,
orientation,
match.orientation,
match.initial,
{start: i, end: closeTokenIdx + 2},
{
containerClasses,
Expand Down
62 changes: 51 additions & 11 deletions src/runtime/TabsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TAB_DATA_KEY,
TAB_PANEL_CLASSNAME,
Tab,
VERTICAL_TAB_FORCED_OPEN,
} from '../common';
import type {TabsOrientation} from '../plugin/transform';
import {
Expand Down Expand Up @@ -152,7 +153,7 @@ export class TabsController {
const previousTargetOffset =
scrollableParent && getOffsetByScrollableParent(targetTab, scrollableParent);

const updatedTabs = this.updateHTML({group, key, align}, align);
const updatedTabs = this.updateHTML({group, key, align}, targetTab, align);

if (updatedTabs > 0) {
this.fireSelectTabEvent({group, key, align}, targetTab?.dataset.diplodocId);
Expand All @@ -163,10 +164,14 @@ export class TabsController {
}
}

private updateHTML(tab: Required<Tab>, align: TabsOrientation) {
private updateHTML(
tab: Required<Tab>,
target: HTMLElement | undefined,
align: TabsOrientation,
) {
switch (align) {
case 'radio': {
return this.updateHTMLVertical(tab);
return this.updateHTMLRadio(tab, target);
}
case 'horizontal': {
return this.updateHTMLHorizontal(tab);
Expand All @@ -176,13 +181,23 @@ export class TabsController {
return 0;
}

private updateHTMLVertical(tab: Required<Tab>) {
private updateHTMLRadio(tab: Required<Tab>, target: HTMLElement | undefined) {
const {group, key} = tab;

const {isForced, root} = this.didTabOpenForced(target);

const singleTabSelector = isForced
? `.yfm-vertical-tab[${VERTICAL_TAB_FORCED_OPEN}="true"]`
: '';

const tabs = this._document.querySelectorAll(
`${Selector.TABS}[${GROUP_DATA_KEY}="${group}"] ${Selector.TAB}[${TAB_DATA_KEY}="${key}"]`,
`${Selector.TABS}[${GROUP_DATA_KEY}="${group}"] ${Selector.TAB}[${TAB_DATA_KEY}="${key}"]${singleTabSelector}`,
);

if (isForced) {
root?.removeAttribute(VERTICAL_TAB_FORCED_OPEN);
}

let updated = 0;

tabs.forEach((tab) => {
Expand All @@ -194,16 +209,29 @@ export class TabsController {

const input = title.children.item(0) as HTMLInputElement;

if (title === tab) {
const checked = input.checked;

if (checked) {
title.classList.remove('active');
content?.classList.remove('active');

input.removeAttribute('checked');
} else {
title.classList.add('active');
content?.classList.add('active');

input.setAttribute('checked', 'true');
}

continue;
}

if (input.hasAttribute('checked')) {
title.classList.remove('active');
content?.classList.remove('active');
input.removeAttribute('checked');
}

if (title === tab) {
title.classList.add('active');
content?.classList.add('active');
input.setAttribute('checked', 'true');
input.removeAttribute('checked');
}

updated++;
Expand Down Expand Up @@ -272,6 +300,18 @@ export class TabsController {
);
}

private didTabOpenForced(target?: HTMLElement) {
if (!target) {
return {};
}

const root = target.dataset.diplodocVerticalTab ? target : target.parentElement;

const isForced = typeof root?.dataset.diplodocRadioForced !== 'undefined';

return {root, isForced};
}

private fireSelectTabEvent(tab: Required<Tab>, diplodocId?: string) {
const {group, key, align} = tab;

Expand Down

0 comments on commit e6ea43c

Please sign in to comment.