Skip to content

Commit

Permalink
🔨 make title/tabbar's close icon/class customable.
Browse files Browse the repository at this point in the history
  • Loading branch information
novrain committed Dec 14, 2023
1 parent 7f4bd51 commit b2011cf
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 2 deletions.
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 @@ -1846,6 +1850,19 @@ export namespace TabBar {
let extra = data.title.iconClass;
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;
Expand Down
64 changes: 63 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 @@ -253,6 +258,51 @@ export class Title<T> implements IDisposable {
this._closable = value;
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 +358,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 +423,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

0 comments on commit b2011cf

Please sign in to comment.