Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make title/tabbar's close icon customable. #668

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion packages/widgets/src/tabbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1752,7 +1752,11 @@ export namespace TabBar {
* @returns A virtual element representing the tab close icon.
*/
renderCloseIcon(data: IRenderData<any>): VirtualElement {
return h.div({ className: 'lm-TabBar-tabCloseIcon' });
const { title } = data;
let className = this.createCloseIconClass(data);

// If title.closeIcon is undefined, it will be ignored.
return h.div({ className }, title.closeIcon!);
}

/**
Expand Down Expand Up @@ -1847,6 +1851,19 @@ export namespace TabBar {
return extra ? `${name} ${extra}` : name;
}

/**
* Create the class name for the tab closeIcon.
*
* @param data - The data to use for the tab.
*
* @returns The full class name for the tab closeIcon.
*/
createCloseIconClass(data: IRenderData<any>): string {
let name = 'lm-TabBar-tabCloseIcon';
let extra = data.title.closeIconClass;
return extra ? `${name} ${extra}` : name;
}

private static _nInstance = 0;
private readonly _uuid: number;
private _tabID = 0;
Expand Down
68 changes: 67 additions & 1 deletion packages/widgets/src/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class Title<T> implements IDisposable {
if (options.icon !== undefined) {
this._icon = options.icon;
}

if (options.iconClass !== undefined) {
this._iconClass = options.iconClass;
}
Expand All @@ -56,6 +55,12 @@ export class Title<T> implements IDisposable {
if (options.closable !== undefined) {
this._closable = options.closable;
}
if (options.closeIcon !== undefined) {
this._closeIcon = options.closeIcon;
}
if (options.closeIconClass !== undefined) {
this._closeIconClass = options.closeIconClass;
}
this._dataset = options.dataset || {};
}

Expand Down Expand Up @@ -254,6 +259,55 @@ export class Title<T> implements IDisposable {
this._changed.emit(undefined);
}

/**
* Get the closeIcon renderer for the title.
*
* #### Notes
* The default value is undefined.
*/
get closeIcon() {
return this._closeIcon;
}

/**
* Set the closeIcon renderer for the title.
*
* #### Notes
* A renderer is an object that supplies a render and unrender function.
*/
set closeIcon(value) {
if (this._closeIcon === value) {
return;
}
this._closeIcon = value;
this._changed.emit(undefined);
}

/**
* Get the closeIcon class name for the title.
*
* #### Notes
* The default value is an empty string.
*/

get closeIconClass() {
return this._closeIconClass;
}

/**
* Set the closeIcon class name for the title.
*
* #### Notes
* Multiple class names can be separated with whitespace.
*/
set closeIconClass(value) {
if (this._closeIconClass === value) {
return;
}
this._closeIconClass = value;
this._changed.emit(undefined);
}

/**
* Get the dataset for the title.
*
Expand Down Expand Up @@ -308,6 +362,8 @@ export class Title<T> implements IDisposable {
private _iconLabel = '';
private _className = '';
private _closable = false;
private _closeIcon: VirtualElement.IRenderer | undefined = undefined;
private _closeIconClass = '';
private _dataset: Title.Dataset;
private _changed = new Signal<this, void>(this);
private _isDisposed = false;
Expand Down Expand Up @@ -371,6 +427,16 @@ export namespace Title {
*/
closable?: boolean;

/**
* The closeIcon renderer for the title.
*/
closeIcon?: VirtualElement.IRenderer;

/**
* The closeIcon class name for the title.
*/
closeIconClass?: string;

/**
* The dataset for the title.
*/
Expand Down
28 changes: 28 additions & 0 deletions packages/widgets/tests/src/tabbar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,11 @@ describe('@lumino/widgets', () => {
'lm-TabBar-tabIcon'
)[0] as HTMLElement;
expect(icon.classList.contains(title.iconClass)).to.equal(true);

let closeIcon = node.getElementsByClassName(
'lm-TabBar-tabCloseIcon'
)[0] as HTMLElement;
expect(closeIcon.classList.contains(title.closeIconClass)).to.equal(true);
});
});

Expand All @@ -2080,6 +2085,16 @@ describe('@lumino/widgets', () => {
});
});

describe('#renderCloseIcon()', () => {
it('should render the closeIcon element for a tab', () => {
let renderer = new TabBar.Renderer();
let vNode = renderer.renderCloseIcon({ title, current: true, zIndex: 1 });
let node = VirtualDOM.realize(vNode as VirtualElement);
expect(node.className).to.contain('lm-TabBar-tabCloseIcon');
expect(node.classList.contains(title.closeIconClass)).to.equal(true);
});
});

describe('#renderLabel()', () => {
it('should render the label element for a tab', () => {
let renderer = new TabBar.Renderer();
Expand Down Expand Up @@ -2154,6 +2169,19 @@ describe('@lumino/widgets', () => {
expect(className).to.contain(title.iconClass);
});
});

describe('#createCloseIconClass()', () => {
it('should create class name for the tab close icon', () => {
let renderer = new TabBar.Renderer();
let className = renderer.createCloseIconClass({
title,
current: true,
zIndex: 1
});
expect(className).to.contain('lm-TabBar-tabCloseIcon');
expect(className).to.contain(title.closeIconClass);
});
});
});

describe('.defaultRenderer', () => {
Expand Down
49 changes: 49 additions & 0 deletions packages/widgets/tests/src/title.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,55 @@ describe('@lumino/widgets', () => {
});
});

describe('#closeIcon', () => {
const closeIconRenderer = {
render: (host: HTMLElement, options?: any) => {
const renderNode = document.createElement('div');
renderNode.className = 'foo';
host.appendChild(renderNode);
}
};

it('should default to undefined', () => {
let title = new Title({ owner });
expect(title.closeIcon).to.equal(undefined);
});

it('should initialize from the options', () => {
let title = new Title({ owner, closeIcon: closeIconRenderer });
expect(title.closeIcon).to.equal(closeIconRenderer);
});

it('should be writable', () => {
let title = new Title({ owner });
expect(title.closeIcon).to.equal(undefined);
title.closeIcon = closeIconRenderer;
expect(title.closeIcon).to.equal(closeIconRenderer);
});

it('should emit the changed signal when the value changes', () => {
let called = false;
let title = new Title({ owner });
title.changed.connect((sender, arg) => {
expect(sender).to.equal(title);
expect(arg).to.equal(undefined);
called = true;
});
title.closeIcon = closeIconRenderer;
expect(called).to.equal(true);
});

it('should not emit the changed signal when the value does not change', () => {
let called = false;
let title = new Title({ owner, closeIcon: closeIconRenderer });
title.changed.connect((sender, arg) => {
called = true;
});
title.closeIcon = closeIconRenderer;
expect(called).to.equal(false);
});
});

describe('#caption', () => {
it('should default to an empty string', () => {
let title = new Title({ owner });
Expand Down