From 92a305c08464e6cac813951bd0907af0fd265c60 Mon Sep 17 00:00:00 2001 From: telamonian Date: Fri, 7 Feb 2020 16:02:44 -0500 Subject: [PATCH 01/13] added some vscode stuff to gitignore --- .gitignore | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d5df56f92..6fa243c24 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,12 @@ build review/api/temp *.tsbuildinfo -# jetbrains IDE stuff +# jetbrains ide stuff *.iml .idea/ -# ms IDE stuff +# ms ide stuff +*.code-workspace +.history .vscode From 20fc19ddb3ecbb325bb5dda521bfbac85f272040 Mon Sep 17 00:00:00 2001 From: telamonian Date: Sat, 8 Feb 2020 19:54:25 -0500 Subject: [PATCH 02/13] added iconRenderer field to command and menu item --- packages/commands/package.json | 3 ++- packages/commands/src/index.ts | 36 ++++++++++++++++++++++++++++++++ packages/commands/tsconfig.json | 3 +++ packages/virtualdom/src/index.ts | 3 ++- packages/widgets/src/menu.ts | 21 ++++++++++++++++++- 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/commands/package.json b/packages/commands/package.json index cf89fb9d5..ac053a9b4 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -46,7 +46,8 @@ "@lumino/disposable": "^1.3.4", "@lumino/domutils": "^1.1.7", "@lumino/keyboard": "^1.1.6", - "@lumino/signaling": "^1.3.4" + "@lumino/signaling": "^1.3.4", + "@lumino/virtualdom": "^1.5.0" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 02fcda5cf..8a8c2912f 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -31,6 +31,9 @@ import { ISignal, Signal } from '@lumino/signaling'; +import { + VirtualElement +} from '@lumino/virtualdom'; /** * An object which manages a collection of commands. @@ -225,6 +228,21 @@ class CommandRegistry { return cmd ? cmd.iconLabel.call(undefined, args) : ''; } + /** + * Get the icon renderer for a specific command. + * + * @param id - The id of the command of interest. + * + * @param args - The arguments for the command. + * + * @returns The icon renderer for the command, or undefined if + * the command is not registered. + */ + iconRenderer(id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject): VirtualElement.IRenderer { + let cmd = this._commands[id]; + return cmd ? cmd.iconRenderer.call(undefined, args) : undefined; + } + /** * Get the short form caption for a specific command. * @@ -681,6 +699,17 @@ namespace CommandRegistry { */ iconLabel?: string | CommandFunc; + /** + * The icon renderer for the command. + * + * #### Notes + * This can be an IRenderer object, or a function which returns the + * renderer based on the provided command arguments. + * + * The default value is undefined. + */ + iconRenderer?: VirtualElement.IRenderer | CommandFunc; + /** * The caption for the command. * @@ -1167,6 +1196,7 @@ namespace Private { readonly mnemonic: CommandFunc; readonly iconClass: CommandFunc; readonly iconLabel: CommandFunc; + readonly iconRenderer: CommandFunc; readonly caption: CommandFunc; readonly usage: CommandFunc; readonly className: CommandFunc; @@ -1187,6 +1217,7 @@ namespace Private { mnemonic: asFunc(options.mnemonic, negativeOneFunc), iconClass: asFunc(options.iconClass || options.icon, emptyStringFunc), iconLabel: asFunc(options.iconLabel, emptyStringFunc), + iconRenderer: asFunc(options.iconRenderer, undefinedFunc), caption: asFunc(options.caption, emptyStringFunc), usage: asFunc(options.usage, emptyStringFunc), className: asFunc(options.className, emptyStringFunc), @@ -1325,6 +1356,11 @@ namespace Private { */ const emptyDatasetFunc = () => ({}); + /** + * A singleton undefined function + */ + const undefinedFunc = () => undefined; + /** * Cast a value or command func to a command func. */ diff --git a/packages/commands/tsconfig.json b/packages/commands/tsconfig.json index 4720810f0..8ba3a68c0 100644 --- a/packages/commands/tsconfig.json +++ b/packages/commands/tsconfig.json @@ -40,6 +40,9 @@ }, { "path": "../signaling" + }, + { + "path": "../virtualdom" } ] } diff --git a/packages/virtualdom/src/index.ts b/packages/virtualdom/src/index.ts index 087d15852..ecb76c9f2 100644 --- a/packages/virtualdom/src/index.ts +++ b/packages/virtualdom/src/index.ts @@ -801,7 +801,8 @@ namespace VirtualElement { * * For example, if render calls `ReactDOM.render(..., host)`, then * there has to also be a corresponding implementation of unrender that - * calls `ReactDOM.unmountComponentAtNode(host)`. + * calls `ReactDOM.unmountComponentAtNode(host)` in order to prevent + * a memory leak. * * @param host - the DOM element to be removed. */ diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 48bd97dbe..4ae9fc2c2 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -1053,6 +1053,11 @@ namespace Menu { */ readonly iconLabel: string; + /** + * The icon renderer for the menu item. + */ + readonly iconRenderer?: VirtualElement.IRenderer; + /** * The display caption for the menu item. */ @@ -1167,7 +1172,8 @@ namespace Menu { */ renderIcon(data: IRenderData): VirtualElement { let className = this.createIconClass(data); - return h.div({ className }, data.item.iconLabel); + // if iconRenderer is undefined, it will just be ignored + return h.div({ className }, data.item.iconRenderer!, data.item.iconLabel); } /** @@ -1777,6 +1783,19 @@ namespace Private { return ''; } + /** + * The icon renderer for the menu item. + */ + get iconRenderer(): VirtualElement.IRenderer { + if (this.type === 'command') { + return this._commands.iconRenderer(this.command, this.args); + } + if (this.type === 'submenu' && this.submenu) { + return this.submenu.title.iconRenderer; + } + return undefined!; + } + /** * The display caption for the menu item. */ From de6c3381a644368659b974755a4e42d15860e6e4 Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 01:43:03 -0500 Subject: [PATCH 03/13] deprecated title.iconRenderer in favor of reclaimed title.icon --- packages/widgets/src/tabbar.ts | 9 +++- packages/widgets/src/title.ts | 85 ++++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index 137b2bff2..66c693dbb 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -1360,7 +1360,14 @@ namespace TabBar { const { title } = data; let className = this.createIconClass(data); - return h.div({className}, title.iconRenderer, title.iconLabel); + /* */ + if (typeof title.icon === 'string') { + return h.div({className}, title.iconLabel); + } + /* */ + + // if title.icon is undefined, it will be ignored + return h.div({className}, title.icon!, title.iconLabel); } /** diff --git a/packages/widgets/src/title.ts b/packages/widgets/src/title.ts index 151b1263c..0156b09c4 100644 --- a/packages/widgets/src/title.ts +++ b/packages/widgets/src/title.ts @@ -38,7 +38,7 @@ class Title { this._mnemonic = options.mnemonic; } if (options.icon !== undefined) { - this._iconClass = options.icon; + this.icon = options.icon; } if (options.iconClass !== undefined) { this._iconClass = options.iconClass; @@ -47,7 +47,7 @@ class Title { this._iconLabel = options.iconLabel; } if (options.iconRenderer !== undefined) { - this._iconRenderer = options.iconRenderer; + this._icon = options.iconRenderer; } if (options.caption !== undefined) { this._caption = options.caption; @@ -116,17 +116,52 @@ class Title { } /** - * @deprecated Use `iconClass` instead. + * Get the icon renderer for the title. + * + * #### Notes + * The default value is undefined. + * + * DEPRECATED: if set to a string value, the .icon field will function as + * an alias for the .iconClass field, for backwards compatibility */ - get icon(): string { - return this.iconClass; + get icon(): VirtualElement.IRenderer | string | undefined { + /* */ + if (this._icon === null) { + // only alias .iconClass if ._icon has been explicitly nulled + return this.iconClass + } + /* */ + + return this._icon; } /** - * @deprecated Use `iconClass` instead. + * Set the icon renderer for the title. + * + * #### Notes + * A renderer is an object that supplies a render and unrender function. + * + * DEPRECATED: if set to a string value, the .icon field will function as + * an alias for the .iconClass field, for backwards compatibility */ - set icon(value: string) { - this.iconClass = value; + set icon(value: VirtualElement.IRenderer | string | undefined ) { + /* */ + if (typeof value === "string") { + // when ._icon is null, the .icon getter will alias .iconClass + this._icon = null; + this.iconClass = value; + } else { + /* */ + + if (this._icon === value) { + return; + } + this._icon = value; + this._changed.emit(undefined); + + /* */ + } + /* */ } /** @@ -178,27 +213,17 @@ class Title { } /** - * Get the icon renderer for the title. - * - * #### Notes - * The default value is undefined. + * @deprecated Use `icon` instead. */ - get iconRenderer(): VirtualElement.IRenderer { - return this._iconRenderer; + get iconRenderer(): VirtualElement.IRenderer | undefined { + return this._icon || undefined; } /** - * Set the icon renderer for the title. - * - * #### Notes - * A renderer is an object that supplies a render and unrender function. + * @deprecated Use `icon` instead. */ - set iconRenderer(value: VirtualElement.IRenderer) { - if (this._iconRenderer === value) { - return; - } - this._iconRenderer = value; - this._changed.emit(undefined); + set iconRenderer(value: VirtualElement.IRenderer | undefined) { + this.icon = value; } /** @@ -297,9 +322,9 @@ class Title { private _label = ''; private _caption = ''; private _mnemonic = -1; + private _icon: VirtualElement.IRenderer | null | undefined; private _iconClass = ''; private _iconLabel = ''; - private _iconRenderer: VirtualElement.IRenderer; private _className = ''; private _closable = false; private _dataset: Title.Dataset; @@ -339,9 +364,12 @@ namespace Title { mnemonic?: number; /** - * @deprecated Use `iconClass` instead. + * The icon renderer for the title. + * + * DEPRECATED: if set to a string value, the .icon field will function as + * an alias for the .iconClass field, for backwards compatibility */ - icon?: string; + icon?: VirtualElement.IRenderer | string; /** * The icon class name for the title. @@ -354,8 +382,7 @@ namespace Title { iconLabel?: string; /** - * An object that supplies render and unrender functions used - * to create and cleanup the icon of the title. + * @deprecated Use `icon` instead. */ iconRenderer?: VirtualElement.IRenderer; From 16cfe95ea222d03cb109957065f9320b8037a765 Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 02:56:10 -0500 Subject: [PATCH 04/13] switched .iconRenderer => .icon in commands and menu items --- packages/commands/src/index.ts | 95 +++++++++++++++++++++------------- packages/widgets/src/menu.ts | 47 ++++++++--------- packages/widgets/src/title.ts | 18 ++++--- 3 files changed, 94 insertions(+), 66 deletions(-) diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 8a8c2912f..a5f32aa35 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -192,10 +192,23 @@ class CommandRegistry { } /** - * @deprecated Use `iconClass()` instead. + * Get the icon renderer for a specific command. + * + * @param id - The id of the command of interest. + * + * @param args - The arguments for the command. + * + * @returns The icon renderer for the command, or undefined if + * the command is not registered. + * + * DEPRECATED: if set to a string value, the .icon field will function as + * an alias for the .iconClass field, for backwards compatibility */ - icon(id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject): string { - return this.iconClass(id, args); + icon(id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject): VirtualElement.IRenderer | undefined + /* */ | string /* */ + { + let cmd = this._commands[id]; + return cmd ? cmd.icon.call(undefined, args) : undefined; } /** @@ -228,21 +241,6 @@ class CommandRegistry { return cmd ? cmd.iconLabel.call(undefined, args) : ''; } - /** - * Get the icon renderer for a specific command. - * - * @param id - The id of the command of interest. - * - * @param args - The arguments for the command. - * - * @returns The icon renderer for the command, or undefined if - * the command is not registered. - */ - iconRenderer(id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject): VirtualElement.IRenderer { - let cmd = this._commands[id]; - return cmd ? cmd.iconRenderer.call(undefined, args) : undefined; - } - /** * Get the short form caption for a specific command. * @@ -665,10 +663,24 @@ namespace CommandRegistry { mnemonic?: number | CommandFunc; /** - * @deprecated Use `iconClass` instead. + * The icon renderer for the command. + * + * #### Notes + * This can be an IRenderer object, or a function which returns the + * renderer based on the provided command arguments. + * + * The default value is undefined. + * + * DEPRECATED: if set to a string value, the .icon field will function as + * an alias for the .iconClass field, for backwards compatibility */ - icon?: string | CommandFunc; - + icon?: VirtualElement.IRenderer + /* */ | string /* */ + | CommandFunc< + VirtualElement.IRenderer + /* */ | string /* */ + >; + /** * The icon class for the command. * @@ -699,17 +711,6 @@ namespace CommandRegistry { */ iconLabel?: string | CommandFunc; - /** - * The icon renderer for the command. - * - * #### Notes - * This can be an IRenderer object, or a function which returns the - * renderer based on the provided command arguments. - * - * The default value is undefined. - */ - iconRenderer?: VirtualElement.IRenderer | CommandFunc; - /** * The caption for the command. * @@ -1194,9 +1195,15 @@ namespace Private { readonly execute: CommandFunc; readonly label: CommandFunc; readonly mnemonic: CommandFunc; + + readonly icon: CommandFunc< + VirtualElement.IRenderer + | undefined + /* */ | string /* */ + >; + readonly iconClass: CommandFunc; readonly iconLabel: CommandFunc; - readonly iconRenderer: CommandFunc; readonly caption: CommandFunc; readonly usage: CommandFunc; readonly className: CommandFunc; @@ -1211,13 +1218,31 @@ namespace Private { */ export function createCommand(options: CommandRegistry.ICommandOptions): ICommand { + let icon; + let iconClass; + + /* */ + if (typeof options.icon === 'string') { + // alias icon to iconClass + iconClass = asFunc(options.iconClass || options.icon, emptyStringFunc); + icon = iconClass; + } else { + /* / */ + + iconClass = asFunc(options.iconClass, emptyStringFunc); + icon = asFunc(options.icon, undefinedFunc); + + /* */ + } + /* */ + return { execute: options.execute, label: asFunc(options.label, emptyStringFunc), mnemonic: asFunc(options.mnemonic, negativeOneFunc), - iconClass: asFunc(options.iconClass || options.icon, emptyStringFunc), + icon, + iconClass, iconLabel: asFunc(options.iconLabel, emptyStringFunc), - iconRenderer: asFunc(options.iconRenderer, undefinedFunc), caption: asFunc(options.caption, emptyStringFunc), usage: asFunc(options.usage, emptyStringFunc), className: asFunc(options.className, emptyStringFunc), diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 4ae9fc2c2..825f9c5c9 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -1039,9 +1039,10 @@ namespace Menu { readonly mnemonic: number; /** - * @deprecated Use `iconClass` instead. + * The icon renderer for the menu item. */ - readonly icon: string; + readonly icon: VirtualElement.IRenderer | undefined + /* */ | string /* */; /** * The icon class for the menu item. @@ -1053,11 +1054,6 @@ namespace Menu { */ readonly iconLabel: string; - /** - * The icon renderer for the menu item. - */ - readonly iconRenderer?: VirtualElement.IRenderer; - /** * The display caption for the menu item. */ @@ -1172,8 +1168,15 @@ namespace Menu { */ renderIcon(data: IRenderData): VirtualElement { let className = this.createIconClass(data); - // if iconRenderer is undefined, it will just be ignored - return h.div({ className }, data.item.iconRenderer!, data.item.iconLabel); + + /* */ + if (typeof data.item.icon === 'string') { + return h.div({className}, data.item.iconLabel); + } + /* */ + + // if data.item.icon is undefined, it will be ignored + return h.div({className}, data.item.icon!, data.item.iconLabel); } /** @@ -1751,10 +1754,18 @@ namespace Private { } /** - * @deprecated Use `iconClass` instead. + * The icon renderer for the menu item. */ - get icon(): string { - return this.iconClass; + get icon(): VirtualElement.IRenderer | undefined + /* */ | string /* */ + { + if (this.type === 'command') { + return this._commands.icon(this.command, this.args); + } + if (this.type === 'submenu' && this.submenu) { + return this.submenu.title.icon; + } + return undefined!; } /** @@ -1783,18 +1794,6 @@ namespace Private { return ''; } - /** - * The icon renderer for the menu item. - */ - get iconRenderer(): VirtualElement.IRenderer { - if (this.type === 'command') { - return this._commands.iconRenderer(this.command, this.args); - } - if (this.type === 'submenu' && this.submenu) { - return this.submenu.title.iconRenderer; - } - return undefined!; - } /** * The display caption for the menu item. diff --git a/packages/widgets/src/title.ts b/packages/widgets/src/title.ts index 0156b09c4..ea1640760 100644 --- a/packages/widgets/src/title.ts +++ b/packages/widgets/src/title.ts @@ -124,7 +124,9 @@ class Title { * DEPRECATED: if set to a string value, the .icon field will function as * an alias for the .iconClass field, for backwards compatibility */ - get icon(): VirtualElement.IRenderer | string | undefined { + get icon(): VirtualElement.IRenderer| undefined + /* */ | string /* */ + { /* */ if (this._icon === null) { // only alias .iconClass if ._icon has been explicitly nulled @@ -144,7 +146,9 @@ class Title { * DEPRECATED: if set to a string value, the .icon field will function as * an alias for the .iconClass field, for backwards compatibility */ - set icon(value: VirtualElement.IRenderer | string | undefined ) { + set icon(value: VirtualElement.IRenderer | undefined + /* */ | string /* */ + ) { /* */ if (typeof value === "string") { // when ._icon is null, the .icon getter will alias .iconClass @@ -153,11 +157,11 @@ class Title { } else { /* */ - if (this._icon === value) { - return; - } - this._icon = value; - this._changed.emit(undefined); + if (this._icon === value) { + return; + } + this._icon = value; + this._changed.emit(undefined); /* */ } From 410c3ddbd9172d13721c0dd2d8641bcb641d35a1 Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 02:59:37 -0500 Subject: [PATCH 05/13] minor fix to title constructor --- packages/widgets/src/title.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/widgets/src/title.ts b/packages/widgets/src/title.ts index ea1640760..de619e7c8 100644 --- a/packages/widgets/src/title.ts +++ b/packages/widgets/src/title.ts @@ -38,7 +38,19 @@ class Title { this._mnemonic = options.mnemonic; } if (options.icon !== undefined) { - this.icon = options.icon; + /* */ + if (typeof options.icon === "string") { + // when ._icon is null, the .icon getter will alias .iconClass + this._icon = null; + this._iconClass = options.icon; + } else { + /* */ + + this._icon = options.icon; + + /* */ + } + /* */ } if (options.iconClass !== undefined) { this._iconClass = options.iconClass; From 76f6766c498ab4d34b37b9fdcadef26c50ac2119 Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 03:38:55 -0500 Subject: [PATCH 06/13] small type fix (added `| undefined`) --- packages/commands/src/index.ts | 7 +++---- packages/widgets/src/title.ts | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index a5f32aa35..0863697d3 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -674,10 +674,10 @@ namespace CommandRegistry { * DEPRECATED: if set to a string value, the .icon field will function as * an alias for the .iconClass field, for backwards compatibility */ - icon?: VirtualElement.IRenderer + icon?: VirtualElement.IRenderer | undefined /* */ | string /* */ | CommandFunc< - VirtualElement.IRenderer + VirtualElement.IRenderer | undefined /* */ | string /* */ >; @@ -1197,8 +1197,7 @@ namespace Private { readonly mnemonic: CommandFunc; readonly icon: CommandFunc< - VirtualElement.IRenderer - | undefined + VirtualElement.IRenderer | undefined /* */ | string /* */ >; diff --git a/packages/widgets/src/title.ts b/packages/widgets/src/title.ts index de619e7c8..68397b560 100644 --- a/packages/widgets/src/title.ts +++ b/packages/widgets/src/title.ts @@ -338,7 +338,10 @@ class Title { private _label = ''; private _caption = ''; private _mnemonic = -1; - private _icon: VirtualElement.IRenderer | null | undefined; + + private _icon: VirtualElement.IRenderer | undefined + /* */ | null /* */; + private _iconClass = ''; private _iconLabel = ''; private _className = ''; From 4b64274f2f962cad6abf03f3bcaa7fa9e202188a Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 20:02:16 -0500 Subject: [PATCH 07/13] fixed tests for tabbar and title --- packages/widgets/package.json | 2 +- packages/widgets/src/title.ts | 8 +++++++ packages/widgets/tests/src/tabbar.spec.ts | 27 +++++++++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/widgets/package.json b/packages/widgets/package.json index 4f0753b66..0d01c49bb 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -41,7 +41,7 @@ "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:debug": "npm run test:debug:firefox", "test:debug:chrome": "cd tests && karma start --browsers=Chrome --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", - "test:debug:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", + "test:debug:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", "test:debug:firefox": "cd tests && karma start --browsers=Firefox --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:ie": "cd tests && karma start --browsers=IE", diff --git a/packages/widgets/src/title.ts b/packages/widgets/src/title.ts index 68397b560..556587867 100644 --- a/packages/widgets/src/title.ts +++ b/packages/widgets/src/title.ts @@ -52,6 +52,14 @@ class Title { } /* */ } + + /* */ + else { + // if unset, default to aliasing .iconClass + this._icon = null; + } + /* */ + if (options.iconClass !== undefined) { this._iconClass = options.iconClass; } diff --git a/packages/widgets/tests/src/tabbar.spec.ts b/packages/widgets/tests/src/tabbar.spec.ts index 32334842e..573c193aa 100644 --- a/packages/widgets/tests/src/tabbar.spec.ts +++ b/packages/widgets/tests/src/tabbar.spec.ts @@ -1297,10 +1297,17 @@ describe('@lumino/widgets', () => { expect(node.classList.contains('lm-mod-closable')).to.equal(true); expect(node.title).to.equal(title.caption); - let icon = node.getElementsByClassName('lm-TabBar-tabIcon')[0] as HTMLElement; let label = node.getElementsByClassName('lm-TabBar-tabLabel')[0] as HTMLElement; - expect(icon.classList.contains(title.icon)).to.equal(true); expect(label.textContent).to.equal(title.label); + + let icon = node.getElementsByClassName('lm-TabBar-tabIcon')[0] as HTMLElement; + expect(icon.classList.contains(title.iconClass)).to.equal(true); + + /* */ + // since a string was assigned to .icon, it should alias .iconClass + expect(icon.classList.contains(title.icon as string)).to.equal(true); + expect(title.icon).to.equal(title.iconClass); + /* */ }); }); @@ -1312,7 +1319,13 @@ describe('@lumino/widgets', () => { let vNode = renderer.renderIcon({ title, current: true, zIndex: 1 }); let node = VirtualDOM.realize(vNode as VirtualElement); expect(node.className).to.contain('lm-TabBar-tabIcon'); - expect(node.classList.contains(title.icon)).to.equal(true); + expect(node.classList.contains(title.iconClass)).to.equal(true); + + /* */ + // make sure that icon and iconClass match + expect(node.classList.contains(title.icon as string)).to.equal(true); + expect(title.icon).to.equal(title.iconClass); + /* */ }); }); @@ -1383,7 +1396,13 @@ describe('@lumino/widgets', () => { title, current: true, zIndex: 1 }); expect(className).to.contain('lm-TabBar-tabIcon'); - expect(className).to.contain(title.icon); + expect(className).to.contain(title.iconClass); + + /* */ + // make sure that icon and iconClass match + expect(className).to.contain(title.icon as string); + expect(title.icon).to.equal(title.iconClass); + /* */ }); }); From e6acd5ce21cecd41cccc509ce201e49459ca5ee0 Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 23:23:28 -0500 Subject: [PATCH 08/13] fixed tests for menu --- packages/commands/src/index.ts | 16 +++++++++------- packages/widgets/src/menu.ts | 10 +++++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 0863697d3..b1261dc7b 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -194,21 +194,23 @@ class CommandRegistry { /** * Get the icon renderer for a specific command. * + * DEPRECATED: if set to a string value, the .icon field will + * function as an alias for the .iconClass field, for backwards + * compatibility. In the future when this is removed, the default + * return type will become undefined. + * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * - * @returns The icon renderer for the command, or undefined if - * the command is not registered. - * - * DEPRECATED: if set to a string value, the .icon field will function as - * an alias for the .iconClass field, for backwards compatibility + * @returns The icon renderer for the command, or + * an empty string if the command is not registered. */ icon(id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject): VirtualElement.IRenderer | undefined /* */ | string /* */ { let cmd = this._commands[id]; - return cmd ? cmd.icon.call(undefined, args) : undefined; + return cmd ? cmd.icon.call(undefined, args) : /* */ '' /* */ /* undefined */; } /** @@ -1221,7 +1223,7 @@ namespace Private { let iconClass; /* */ - if (typeof options.icon === 'string') { + if (!(options.icon) || typeof options.icon === 'string') { // alias icon to iconClass iconClass = asFunc(options.iconClass || options.icon, emptyStringFunc); icon = iconClass; diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 825f9c5c9..6113c7756 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -1765,7 +1765,15 @@ namespace Private { if (this.type === 'submenu' && this.submenu) { return this.submenu.title.icon; } - return undefined!; + + /* */ + // alias to icon class if not otherwise defined + return this.iconClass; + /* */ + + /* + return undefined; + */ } /** From b068940c4ab45a379da051351675346b23c4e44f Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 23:35:36 -0500 Subject: [PATCH 09/13] updated renderIcon for menubar to use title.icon --- packages/widgets/src/menubar.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/widgets/src/menubar.ts b/packages/widgets/src/menubar.ts index 71fdaef81..88a8cffc2 100644 --- a/packages/widgets/src/menubar.ts +++ b/packages/widgets/src/menubar.ts @@ -787,7 +787,15 @@ namespace MenuBar { */ renderIcon(data: IRenderData): VirtualElement { let className = this.createIconClass(data); - return h.div({ className }, data.title.iconLabel); + + /* */ + if (typeof data.title.icon === 'string') { + return h.div({className}, data.title.iconLabel); + } + /* */ + + // if data.title.icon is undefined, it will be ignored + return h.div({className}, data.title.icon!, data.title.iconLabel); } /** From dfa9a7249366c27e192fadf6bf0a3a5b6841834c Mon Sep 17 00:00:00 2001 From: telamonian Date: Sun, 9 Feb 2020 23:58:56 -0500 Subject: [PATCH 10/13] updated renderItemIcon for commandpalette to use command.icon --- packages/widgets/src/commandpalette.ts | 25 ++++++++++++++++++++++++- packages/widgets/src/menu.ts | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/widgets/src/commandpalette.ts b/packages/widgets/src/commandpalette.ts index d11053720..079f839b2 100644 --- a/packages/widgets/src/commandpalette.ts +++ b/packages/widgets/src/commandpalette.ts @@ -587,6 +587,12 @@ namespace CommandPalette { */ readonly caption: string; + /** + * The icon renderer for the command item. + */ + readonly icon: VirtualElement.IRenderer | undefined + /* */ | string /* */; + /** * The icon class for the command item. */ @@ -779,7 +785,15 @@ namespace CommandPalette { */ renderItemIcon(data: IItemRenderData): VirtualElement { let className = this.createIconClass(data); - return h.div({ className }, data.item.iconLabel); + + /* */ + if (typeof data.item.icon === 'string') { + return h.div({className}, data.item.iconLabel); + } + /* */ + + // if data.item.icon is undefined, it will be ignored + return h.div({className}, data.item.icon!, data.item.iconLabel); } /** @@ -1446,6 +1460,15 @@ namespace Private { return this._commands.label(this.command, this.args); } + /** + * The icon renderer for the command item. + */ + get icon(): VirtualElement.IRenderer | undefined + /* */ | string /* */ + { + return this._commands.icon(this.command, this.args); + } + /** * The icon class for the command item. */ diff --git a/packages/widgets/src/menu.ts b/packages/widgets/src/menu.ts index 6113c7756..49c3d19d1 100644 --- a/packages/widgets/src/menu.ts +++ b/packages/widgets/src/menu.ts @@ -1756,7 +1756,7 @@ namespace Private { /** * The icon renderer for the menu item. */ - get icon(): VirtualElement.IRenderer | undefined + get icon(): VirtualElement.IRenderer | undefined /* */ | string /* */ { if (this.type === 'command') { From d06928e0dc228e7d3b94649f284d73f29f5307d7 Mon Sep 17 00:00:00 2001 From: telamonian Date: Mon, 10 Feb 2020 05:36:10 -0500 Subject: [PATCH 11/13] attrs and children now passed consistently to custom vdom renderers --- packages/virtualdom/package.json | 19 +++++++++++++------ packages/virtualdom/src/index.ts | 9 ++++++--- packages/widgets/package.json | 19 ++++++++++++------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/packages/virtualdom/package.json b/packages/virtualdom/package.json index b5fdd6650..2be8925c3 100644 --- a/packages/virtualdom/package.json +++ b/packages/virtualdom/package.json @@ -34,13 +34,20 @@ "clean": "rimraf lib && rimraf *.tsbuildinfo", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", + "test": "npm run test:firefox", - "test:chrome": "cd tests && karma start --browsers=Chrome", - "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", - "test:debug": "cd tests && karma start --browsers=Chrome --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", - "test:debug:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", - "test:firefox": "cd tests && karma start --browsers=Firefox", - "test:ie": "cd tests && karma start --browsers=IE", + "test:chrome": "npm run test:nobrowser -- --browsers=Chrome", + "test:chrome-headless": "npm run test:nobrowser -- --browsers=ChromeHeadless", + "test:ie": "npm run test:nobrowser -- --browsers=IE", + "test:firefox": "npm run test:nobrowser -- --browsers=Firefox", + "test:nobrowser": "cd tests && karma start", + + "test:debug": "npm run test:debug:firefox", + "test:debug:chrome-headless": "npm run test:debug:nobrowser -- --browsers=ChromeHeadless", + "test:debug:chrome": "npm run test:debug:nobrowser -- --browsers=Chrome", + "test:debug:firefox": "npm run test:debug:nobrowser -- --browsers=Firefox", + "test:debug:nobrowser": "cd tests && karma start --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", + "watch": "tsc --build --watch" }, "dependencies": { diff --git a/packages/virtualdom/src/index.ts b/packages/virtualdom/src/index.ts index ecb76c9f2..c77dc09bc 100644 --- a/packages/virtualdom/src/index.ts +++ b/packages/virtualdom/src/index.ts @@ -805,8 +805,11 @@ namespace VirtualElement { * a memory leak. * * @param host - the DOM element to be removed. + * + * @param options - Will be populated with the .attrs and .children fields + * set on the VirtualElement being unrendered. */ - unrender?: (host: HTMLElement) => void; + unrender?: (host: HTMLElement, options?: {attrs?: ElementAttrs, children?: ReadonlyArray}) => void; }; } @@ -1313,7 +1316,7 @@ namespace Private { // Update the element content. if (newVNode.renderer) { - newVNode.renderer.render(currElem as HTMLElement); + newVNode.renderer.render(currElem as HTMLElement, {attrs: newVNode.attrs, children: newVNode.children}); } else { updateContent(currElem as HTMLElement, oldVNode.children, newVNode.children); } @@ -1342,7 +1345,7 @@ namespace Private { // recursively clean up host children if (oldNode.type === 'text') {} else if (oldNode.renderer && oldNode.renderer.unrender) { - oldNode.renderer.unrender(child!); + oldNode.renderer.unrender(child!, {attrs: oldNode.attrs, children: oldNode.children}); } else { removeContent(child!, oldNode.children, 0, false); } diff --git a/packages/widgets/package.json b/packages/widgets/package.json index 0d01c49bb..2db0fba68 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -36,15 +36,20 @@ "clean": "rimraf lib && rimraf *.tsbuildinfo", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", + "test": "npm run test:firefox", - "test:chrome": "cd tests && karma start --browsers=Chrome", - "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", + "test:chrome": "npm run test:nobrowser -- --browsers=Chrome", + "test:chrome-headless": "npm run test:nobrowser -- --browsers=ChromeHeadless", + "test:ie": "npm run test:nobrowser -- --browsers=IE", + "test:firefox": "npm run test:nobrowser -- --browsers=Firefox", + "test:nobrowser": "cd tests && karma start", + "test:debug": "npm run test:debug:firefox", - "test:debug:chrome": "cd tests && karma start --browsers=Chrome --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", - "test:debug:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", - "test:debug:firefox": "cd tests && karma start --browsers=Firefox --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", - "test:firefox": "cd tests && karma start --browsers=Firefox", - "test:ie": "cd tests && karma start --browsers=IE", + "test:debug:chrome-headless": "npm run test:debug:nobrowser -- --browsers=ChromeHeadless", + "test:debug:chrome": "npm run test:debug:nobrowser -- --browsers=Chrome", + "test:debug:firefox": "npm run test:debug:nobrowser -- --browsers=Firefox", + "test:debug:nobrowser": "cd tests && karma start --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", + "watch": "tsc --build --watch" }, "dependencies": { From fdcc50bf501ddcdff1e47d779b065e0770ef4bb7 Mon Sep 17 00:00:00 2001 From: telamonian Date: Mon, 10 Feb 2020 06:00:49 -0500 Subject: [PATCH 12/13] added test coverage for Title.icon backwards compatibility --- packages/widgets/tests/src/title.spec.ts | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/widgets/tests/src/title.spec.ts b/packages/widgets/tests/src/title.spec.ts index d38f6f738..f781299b0 100644 --- a/packages/widgets/tests/src/title.spec.ts +++ b/packages/widgets/tests/src/title.spec.ts @@ -143,6 +143,14 @@ describe('@lumino/widgets', () => { describe('#icon', () => { + const iconRenderer = { + render: (host: HTMLElement, options?: any) => { + const renderNode = document.createElement('div'); + renderNode.className = 'p-render'; + host.appendChild(renderNode); + } + }; + it('should default to an empty string', () => { let title = new Title({ owner }); expect(title.icon).to.equal(''); @@ -181,6 +189,29 @@ describe('@lumino/widgets', () => { expect(called).to.equal(false); }); + /* */ + it('should be able to switch string => renderer', () => { + let title = new Title({ owner, icon: 'foo' }); + expect(title.icon).to.equal('foo'); + + // when initialized with string, should alias .iconClass + expect(title.icon).to.equal(title.iconClass); + + title.icon = iconRenderer; + expect(title.icon).to.equal(iconRenderer); + }); + + it('should be able to switch renderer => string', () => { + let title = new Title({ owner, icon: iconRenderer }); + expect(title.icon).to.equal(iconRenderer); + title.icon = 'foo'; + expect(title.icon).to.equal('foo'); + + // when switched to string, should alias .iconClass + expect(title.icon).to.equal(title.iconClass); + }); + /* */ + }); describe('#caption', () => { From c8284dfb2142a589cf33d79f67d26da21e52d4c1 Mon Sep 17 00:00:00 2001 From: telamonian Date: Mon, 10 Feb 2020 06:14:54 -0500 Subject: [PATCH 13/13] added test coverage for CommandRegistry.icon backwards compatibility --- packages/commands/package.json | 16 +++++++++--- packages/commands/tests/src/index.spec.ts | 31 +++++++++++++++++++++-- packages/widgets/tests/src/title.spec.ts | 5 ++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/commands/package.json b/packages/commands/package.json index ac053a9b4..3729e6558 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -34,10 +34,20 @@ "clean": "rimraf lib && rimraf *.tsbuildinfo", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", + "test": "npm run test:firefox", - "test:chrome": "cd tests && karma start --browsers=Chrome", - "test:firefox": "cd tests && karma start --browsers=Firefox", - "test:ie": "cd tests && karma start --browsers=IE", + "test:chrome": "npm run test:nobrowser -- --browsers=Chrome", + "test:chrome-headless": "npm run test:nobrowser -- --browsers=ChromeHeadless", + "test:ie": "npm run test:nobrowser -- --browsers=IE", + "test:firefox": "npm run test:nobrowser -- --browsers=Firefox", + "test:nobrowser": "cd tests && karma start", + + "test:debug": "npm run test:debug:firefox", + "test:debug:chrome-headless": "npm run test:debug:nobrowser -- --browsers=ChromeHeadless", + "test:debug:chrome": "npm run test:debug:nobrowser -- --browsers=Chrome", + "test:debug:firefox": "npm run test:debug:nobrowser -- --browsers=Firefox", + "test:debug:nobrowser": "cd tests && karma start --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", + "watch": "tsc --build --watch" }, "dependencies": { diff --git a/packages/commands/tests/src/index.spec.ts b/packages/commands/tests/src/index.spec.ts index 4f7b43f49..822877e5e 100644 --- a/packages/commands/tests/src/index.spec.ts +++ b/packages/commands/tests/src/index.spec.ts @@ -275,13 +275,21 @@ describe('@lumino/commands', () => { describe('#icon()', () => { + const iconRenderer = { + render: (host: HTMLElement, options?: any) => { + const renderNode = document.createElement('div'); + renderNode.className = 'p-render'; + host.appendChild(renderNode); + } + }; + it('should get the icon for a specific command', () => { let cmd = { execute: (args: JSONObject) => { return args; }, - icon: 'foo' + icon: iconRenderer }; registry.addCommand('test', cmd); - expect(registry.icon('test')).to.equal('foo'); + expect(registry.icon('test')).to.equal(iconRenderer); }); it('should give the appropriate icon given arguments', () => { @@ -302,6 +310,25 @@ describe('@lumino/commands', () => { expect(registry.icon('test')).to.equal(''); }); + /* */ + it('should be able to return a string value', () => { + let cmd = { + execute: (args: JSONObject) => { return args; }, + icon: 'foo' + }; + registry.addCommand('test', cmd); + expect(registry.icon('test')).to.equal('foo'); + }); + + it('should alias .iconClass() if cmd.icon is unset', () => { + let cmd = { + execute: (args: JSONObject) => { return args; }, + iconClass: 'foo' + }; + registry.addCommand('test', cmd); + expect(registry.icon('test')).to.equal('foo'); + }); + /* */ }); describe('#caption()', () => { diff --git a/packages/widgets/tests/src/title.spec.ts b/packages/widgets/tests/src/title.spec.ts index f781299b0..18e924e33 100644 --- a/packages/widgets/tests/src/title.spec.ts +++ b/packages/widgets/tests/src/title.spec.ts @@ -210,6 +210,11 @@ describe('@lumino/widgets', () => { // when switched to string, should alias .iconClass expect(title.icon).to.equal(title.iconClass); }); + + it('should alias .iconClass if unset', () => { + let title = new Title({ owner, iconClass: 'foo' }); + expect(title.icon).to.equal('foo'); + }); /* */ });