From 59acd94bc62d2301fc15bc7f1726906770757491 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 11 Aug 2024 11:24:20 +0100 Subject: [PATCH] feat: correct z-index level for floating always rendered panel --- .../overlay/overlayRenderContainer.spec.ts | 75 ++++++++++++++-- .../src/dockview/dockviewPanel.ts | 2 +- .../src/overlay/overlayRenderContainer.ts | 90 ++++++++++--------- 3 files changed, 117 insertions(+), 50 deletions(-) diff --git a/packages/dockview-core/src/__tests__/overlay/overlayRenderContainer.spec.ts b/packages/dockview-core/src/__tests__/overlay/overlayRenderContainer.spec.ts index 1fa37cab8..0e7ddfbce 100644 --- a/packages/dockview-core/src/__tests__/overlay/overlayRenderContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/overlay/overlayRenderContainer.spec.ts @@ -8,11 +8,11 @@ import { import { fromPartial } from '@total-typescript/shoehorn'; import { Writable, exhaustMicrotaskQueue } from '../__test_utils__/utils'; import { DockviewComponent } from '../../dockview/dockviewComponent'; +import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; describe('overlayRenderContainer', () => { let referenceContainer: IRenderable; let parentContainer: HTMLElement; - let cut: OverlayRenderContainer; beforeEach(() => { parentContainer = document.createElement('div'); @@ -21,14 +21,14 @@ describe('overlayRenderContainer', () => { element: document.createElement('div'), dropTarget: fromPartial({}), }; - - cut = new OverlayRenderContainer( - parentContainer, - fromPartial({}) - ); }); test('that attach(...) and detach(...) mutate the DOM as expected', () => { + const cut = new OverlayRenderContainer( + parentContainer, + fromPartial({}) + ); + const panelContentEl = document.createElement('div'); const onDidVisibilityChange = new Emitter(); @@ -42,6 +42,7 @@ describe('overlayRenderContainer', () => { onDidDimensionsChange: onDidDimensionsChange.event, onDidLocationChange: onDidLocationChange.event, isVisible: true, + location: { type: 'grid' }, }, view: { content: { @@ -67,6 +68,11 @@ describe('overlayRenderContainer', () => { }); test('add a view that is not currently in the DOM', async () => { + const cut = new OverlayRenderContainer( + parentContainer, + fromPartial({}) + ); + const panelContentEl = document.createElement('div'); const onDidVisibilityChange = new Emitter(); @@ -80,6 +86,7 @@ describe('overlayRenderContainer', () => { onDidDimensionsChange: onDidDimensionsChange.event, onDidLocationChange: onDidLocationChange.event, isVisible: true, + location: { type: 'grid' }, }, view: { content: { @@ -197,4 +204,60 @@ describe('overlayRenderContainer', () => { referenceContainer.element.getBoundingClientRect ).toHaveBeenCalledTimes(3); }); + + test('related z-index from `aria-level` set on floating panels', async () => { + const group = fromPartial({}); + + const element = document.createElement('div'); + element.setAttribute('aria-level', '2'); + const spy = jest.spyOn(element, 'getAttribute'); + + const accessor = fromPartial({ + floatingGroups: [ + { + group, + overlay: { + element, + }, + }, + ], + }); + + const cut = new OverlayRenderContainer(parentContainer, accessor); + + const panelContentEl = document.createElement('div'); + + const onDidVisibilityChange = new Emitter(); + const onDidDimensionsChange = new Emitter(); + const onDidLocationChange = new Emitter(); + + const panel = fromPartial({ + api: { + id: 'test_panel_id', + onDidVisibilityChange: onDidVisibilityChange.event, + onDidDimensionsChange: onDidDimensionsChange.event, + onDidLocationChange: onDidLocationChange.event, + isVisible: true, + group, + location: { type: 'floating' }, + }, + view: { + content: { + element: panelContentEl, + }, + }, + group: { + api: { + location: { type: 'floating' }, + }, + }, + }); + + cut.attach({ panel, referenceContainer }); + + await exhaustMicrotaskQueue(); + + expect(spy).toHaveBeenCalledWith('aria-level'); + expect(panelContentEl.parentElement!.style.zIndex).toBe('1004'); + }); }); diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index 63f27d62f..984c3fcdb 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -87,7 +87,7 @@ export class DockviewPanel // you are actually just resizing the panels parent which is the group this.group.api.setSize(event); }), - this.api.onDidRendererChange((event) => { + this.api.onDidRendererChange(() => { this.group.model.rerender(this); }) ); diff --git a/packages/dockview-core/src/overlay/overlayRenderContainer.ts b/packages/dockview-core/src/overlay/overlayRenderContainer.ts index a0d024a47..1d68042e9 100644 --- a/packages/dockview-core/src/overlay/overlayRenderContainer.ts +++ b/packages/dockview-core/src/overlay/overlayRenderContainer.ts @@ -120,6 +120,48 @@ export class OverlayRenderContainer extends CompositeDisposable { const observerDisposable = new MutableDisposable(); + const correctLayerPosition = () => { + if (panel.api.location.type === 'floating') { + queueMicrotask(() => { + const floatingGroup = this.accessor.floatingGroups.find( + (group) => group.group === panel.api.group + ); + + if (!floatingGroup) { + return; + } + + const element = floatingGroup.overlay.element; + + const update = () => { + const level = Number( + element.getAttribute('aria-level') + ); + focusContainer.style.zIndex = `${ + DEFAULT_OVERLAY_Z_INDEX + level * 2 + 1 + }`; + }; + + const observer = new MutationObserver(() => { + update(); + }); + + observerDisposable.value = Disposable.from(() => + observer.disconnect() + ); + + observer.observe(element, { + attributeFilter: ['aria-level'], + attributes: true, + }); + + update(); + }); + } else { + focusContainer.style.zIndex = ''; // reset the z-index, perhaps CSS will take over here + } + }; + const disposable = new CompositeDisposable( observerDisposable, /** @@ -147,7 +189,7 @@ export class OverlayRenderContainer extends CompositeDisposable { }, }), - panel.api.onDidVisibilityChange((event) => { + panel.api.onDidVisibilityChange(() => { /** * Control the visibility of the content, however even when not visible (display: none) * the content is still maintained within the DOM hence DOM specific attributes @@ -162,48 +204,8 @@ export class OverlayRenderContainer extends CompositeDisposable { resize(); }), - panel.api.onDidLocationChange((event) => { - const isFloating = event.location.type === 'floating'; - - if (isFloating) { - queueMicrotask(() => { - const floatingGroup = this.accessor.floatingGroups.find( - (group) => group.group === panel.api.group - ); - - if (!floatingGroup) { - return; - } - - const element = floatingGroup.overlay.element; - - const update = () => { - const level = Number( - element.getAttribute('aria-level') - ); - focusContainer.style.zIndex = `${ - DEFAULT_OVERLAY_Z_INDEX + level * 2 + 1 - }`; - }; - - const observer = new MutationObserver(() => { - update(); - }); - - observerDisposable.value = Disposable.from(() => - observer.disconnect() - ); - - observer.observe(element, { - attributeFilter: ['aria-level'], - attributes: true, - }); - - update(); - }); - } else { - focusContainer.style.zIndex = ''; // reset the z-index, perhaps CSS will take over here - } + panel.api.onDidLocationChange(() => { + correctLayerPosition(); }) ); @@ -215,6 +217,8 @@ export class OverlayRenderContainer extends CompositeDisposable { focusContainer.parentElement?.removeChild(focusContainer); }); + correctLayerPosition(); + queueMicrotask(() => { if (this.isDisposed) { return;