diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index bcedd63e6..c66714c1e 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -1752,7 +1752,11 @@ export namespace TabBar { * @returns A virtual element representing the tab close icon. */ renderCloseIcon(data: IRenderData): 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!); } /** @@ -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): string { + let name = 'lm-TabBar-tabCloseIcon'; + let extra = data.title.closeIconClass; + return extra ? `${name} ${extra}` : name; + } private static _nInstance = 0; private readonly _uuid: number; diff --git a/packages/widgets/src/title.ts b/packages/widgets/src/title.ts index a2f5bbb45..e6cb20525 100644 --- a/packages/widgets/src/title.ts +++ b/packages/widgets/src/title.ts @@ -40,7 +40,6 @@ export class Title implements IDisposable { if (options.icon !== undefined) { this._icon = options.icon; } - if (options.iconClass !== undefined) { this._iconClass = options.iconClass; } @@ -56,6 +55,12 @@ export class Title 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 || {}; } @@ -253,6 +258,51 @@ export class Title 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. @@ -308,6 +358,8 @@ export class Title 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); private _isDisposed = false; @@ -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. */ diff --git a/packages/widgets/tests/src/tabbar.spec.ts b/packages/widgets/tests/src/tabbar.spec.ts index 0479aad45..fcecc58e8 100644 --- a/packages/widgets/tests/src/tabbar.spec.ts +++ b/packages/widgets/tests/src/tabbar.spec.ts @@ -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); }); }); @@ -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(); @@ -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', () => { diff --git a/packages/widgets/tests/src/title.spec.ts b/packages/widgets/tests/src/title.spec.ts index f4f03ef6c..3b7d0882d 100644 --- a/packages/widgets/tests/src/title.spec.ts +++ b/packages/widgets/tests/src/title.spec.ts @@ -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 });