From ad86fd932ca5fd03177742342a93f6aede040882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Sat, 24 Dec 2022 10:07:06 +0000 Subject: [PATCH] Backport PR #497: Add content-visibility hide mode --- packages/widgets/src/docklayout.ts | 19 +++-- packages/widgets/src/widget.ts | 96 ++++++++++++++--------- packages/widgets/tests/src/widget.spec.ts | 32 +++++++- review/api/widgets.api.md | 1 + 4 files changed, 100 insertions(+), 48 deletions(-) diff --git a/packages/widgets/src/docklayout.ts b/packages/widgets/src/docklayout.ts index 6d96c9950..c35e51d78 100644 --- a/packages/widgets/src/docklayout.ts +++ b/packages/widgets/src/docklayout.ts @@ -852,18 +852,21 @@ export class DockLayout extends Layout { // Using transform create an additional layer in the pixel pipeline // to limit the number of layer, it is set only if there is more than one widget. - if ( - this._hiddenMode === Widget.HiddenMode.Scale && - refNode.tabBar.titles.length > 0 - ) { - if (refNode.tabBar.titles.length == 1) { + if (this._hiddenMode === Widget.HiddenMode.Scale) { + if (refNode.tabBar.titles.length === 0) { + // Singular tab should use display mode to limit number of layers. + widget.hiddenMode = Widget.HiddenMode.Display; + } else if (refNode.tabBar.titles.length == 1) { + // If we are adding a second tab, switch the existing tab back to scale. const existingWidget = refNode.tabBar.titles[0].owner; existingWidget.hiddenMode = Widget.HiddenMode.Scale; + } else { + // For the third and subsequent tabs no special action is needed. + widget.hiddenMode = Widget.HiddenMode.Scale; } - - widget.hiddenMode = Widget.HiddenMode.Scale; } else { - widget.hiddenMode = Widget.HiddenMode.Display; + // For all other modes just propagate the current mode. + widget.hiddenMode = this._hiddenMode; } // Insert the widget's tab relative to the target index. diff --git a/packages/widgets/src/widget.ts b/packages/widgets/src/widget.ts index f07d73e24..f2ae230c4 100644 --- a/packages/widgets/src/widget.ts +++ b/packages/widgets/src/widget.ts @@ -183,30 +183,23 @@ export class Widget implements IMessageHandler, IObservableDisposable { if (this._hiddenMode === value) { return; } - this._hiddenMode = value; - switch (value) { - case Widget.HiddenMode.Display: - this.node.style.willChange = 'auto'; - break; - case Widget.HiddenMode.Scale: - this.node.style.willChange = 'transform'; - break; + + if (this.isHidden) { + // Reset styles set by previous mode. + this._toggleHidden(false); } + if (value == Widget.HiddenMode.Scale) { + this.node.style.willChange = 'transform'; + } else { + this.node.style.willChange = 'auto'; + } + + this._hiddenMode = value; + if (this.isHidden) { - if (value === Widget.HiddenMode.Display) { - this.addClass('lm-mod-hidden'); - /* */ - this.addClass('p-mod-hidden'); - /* */ - this.node.style.transform = ''; - } else { - this.node.style.transform = 'scale(0)'; - this.removeClass('lm-mod-hidden'); - /* */ - this.removeClass('p-mod-hidden'); - /* */ - } + // Set styles for new mode. + this._toggleHidden(true); } } @@ -434,14 +427,7 @@ export class Widget implements IMessageHandler, IObservableDisposable { } this.clearFlag(Widget.Flag.IsHidden); this.node.removeAttribute('aria-hidden'); - if (this.hiddenMode === Widget.HiddenMode.Display) { - this.removeClass('lm-mod-hidden'); - /* */ - this.removeClass('p-mod-hidden'); - /* */ - } else { - this.node.style.transform = ''; - } + this._toggleHidden(false); if (this.isAttached && (!this.parent || this.parent.isVisible)) { MessageLoop.sendMessage(this, Widget.Msg.AfterShow); @@ -469,14 +455,7 @@ export class Widget implements IMessageHandler, IObservableDisposable { } this.setFlag(Widget.Flag.IsHidden); this.node.setAttribute('aria-hidden', 'true'); - if (this.hiddenMode === Widget.HiddenMode.Display) { - this.addClass('lm-mod-hidden'); - /* */ - this.addClass('p-mod-hidden'); - /* */ - } else { - this.node.style.transform = 'scale(0)'; - } + this._toggleHidden(true); if (this.isAttached && (!this.parent || this.parent.isVisible)) { MessageLoop.sendMessage(this, Widget.Msg.AfterHide); @@ -759,6 +738,42 @@ export class Widget implements IMessageHandler, IObservableDisposable { */ protected onChildRemoved(msg: Widget.ChildMessage): void {} + private _toggleHidden(hidden: boolean) { + if (hidden) { + switch (this._hiddenMode) { + case Widget.HiddenMode.Display: + this.addClass('lm-mod-hidden'); + /* */ + this.addClass('p-mod-hidden'); + /* */ + break; + case Widget.HiddenMode.Scale: + this.node.style.transform = 'scale(0)'; + break; + case Widget.HiddenMode.ContentVisibility: + this.node.style.contentVisibility = 'hidden'; + this.node.style.zIndex = '-1'; + break; + } + } else { + switch (this._hiddenMode) { + case Widget.HiddenMode.Display: + this.removeClass('lm-mod-hidden'); + /* */ + this.removeClass('p-mod-hidden'); + /* */ + break; + case Widget.HiddenMode.Scale: + this.node.style.transform = ''; + break; + case Widget.HiddenMode.ContentVisibility: + this.node.style.contentVisibility = ''; + this.node.style.zIndex = ''; + break; + } + } + } + private _flags = 0; private _layout: Layout | null = null; private _parent: Widget | null = null; @@ -819,7 +834,12 @@ export namespace Widget { /** * Hide the widget by setting the `transform` to `'scale(0)'`. */ - Scale + Scale, + + /** + *Hide the widget by setting the `content-visibility` to `'hidden'`. + */ + ContentVisibility } /** diff --git a/packages/widgets/tests/src/widget.spec.ts b/packages/widgets/tests/src/widget.spec.ts index 82e6c750f..9bca55346 100644 --- a/packages/widgets/tests/src/widget.spec.ts +++ b/packages/widgets/tests/src/widget.spec.ts @@ -740,10 +740,38 @@ describe('@lumino/widgets', () => { widget.dispose(); }); - it('should add class when switching from scale to display', () => { + for (const fromMode of [ + Widget.HiddenMode.Scale, + Widget.HiddenMode.ContentVisibility + ]) { + it(`should add class when switching from ${fromMode} to display`, () => { + let widget = new Widget(); + Widget.attach(widget, document.body); + widget.hiddenMode = fromMode; + widget.hide(); + widget.hiddenMode = Widget.HiddenMode.Display; + expect(widget.hasClass('lm-mod-hidden')).to.equal(true); + expect(widget.node.style.transform).to.equal(''); + expect(widget.node.style.willChange).to.equal('auto'); + widget.dispose(); + }); + } + + it('should use content-visibility in relevant mode', () => { let widget = new Widget(); Widget.attach(widget, document.body); - widget.hiddenMode = Widget.HiddenMode.Scale; + widget.hiddenMode = Widget.HiddenMode.ContentVisibility; + widget.hide(); + expect(widget.hasClass('lm-mod-hidden')).to.equal(false); + expect(widget.node.style.contentVisibility).to.equal('hidden'); + expect(widget.node.style.willChange).to.equal('auto'); + widget.dispose(); + }); + + it('should add class when switching from content-visibility to display', () => { + let widget = new Widget(); + Widget.attach(widget, document.body); + widget.hiddenMode = Widget.HiddenMode.ContentVisibility; widget.hide(); widget.hiddenMode = Widget.HiddenMode.Display; expect(widget.hasClass('lm-mod-hidden')).to.equal(true); diff --git a/review/api/widgets.api.md b/review/api/widgets.api.md index 07b9ca32f..b3a24ba48 100644 --- a/review/api/widgets.api.md +++ b/review/api/widgets.api.md @@ -1267,6 +1267,7 @@ export namespace Widget { IsVisible = 8 } export enum HiddenMode { + ContentVisibility = 2, Display = 0, Scale = 1 }