Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dist/1643.819e6c76d1c5a6b9.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/1900.11283310e68f4ba9.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion dist/1900.a12604cdc6136544.js

This file was deleted.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/2564.b331a445a56b11aa.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/5036.1abee30f5159f212.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/7058.1a06d2ebd2eaf5a5.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
<body class="mat-typography">
<df-root></df-root>
<script type="text/javascript" src="https://assets.calendly.com/assets/external/widget.js"></script>
<script src="runtime.49b64f99a9c8b8b2.js" type="module"></script><script src="polyfills.acd6a468b9e08280.js" type="module"></script><script src="main.8669394e11a06ae4.js" type="module"></script></body>
<script src="runtime.1966a023475956b2.js" type="module"></script><script src="polyfills.acd6a468b9e08280.js" type="module"></script><script src="main.a8aea8bd0d10987a.js" type="module"></script></body>
</html>
1 change: 1 addition & 0 deletions dist/runtime.1966a023475956b2.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 111 additions & 0 deletions e2e/style-leaks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { test, expect, Page } from '@playwright/test';
import { loginAsAdmin, waitForAppReady } from './fixtures/admin-login';

/**
* Regression guard for component styles that leak onto the rest of the app.
*
* Angular 16 keeps a component's <style> in <head> for the whole session once
* the component has rendered (REMOVE_STYLES_ON_COMPONENT_DESTROY is false until
* v17), so any ::ng-deep rule without a :host prefix is effectively global from
* then on. The Data Explorer used to clip the shell's mat-sidenav-content this
* way, which removed the page scrollbar everywhere until a hard refresh.
*/

const SHELL_SCROLLER = 'mat-sidenav-content.sidenav-content';

async function shellOverflowY(page: Page) {
return page
.locator(SHELL_SCROLLER)
.evaluate(el => getComputedStyle(el).overflowY);
}

async function openExplorerTable(page: Page) {
await page.goto('/dreamfactory/dist/#/api-connections/data-explorer', {
waitUntil: 'domcontentloaded',
});
const db = page
.locator('.db-item', {
has: page.locator('.db-type', { hasText: /sqlite/i }),
})
.first();
await expect(db).toBeVisible({ timeout: 20_000 });
await db.click();
const table = page.locator('.table-item').first();
await expect(table).toBeVisible({ timeout: 20_000 });
await table.click();
await expect(page.locator('select.filter-op').first()).toBeVisible({
timeout: 20_000,
});
}

test.describe('component style leaks', () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(page);
await waitForAppReady(page);
});

test('shell keeps its scrollbar after visiting the Data Explorer', async ({
page,
}) => {
await openExplorerTable(page);

// The explorer still clips its own sidenav content...
const inner = page.locator(
'.explorer-sidenav-container > .mat-drawer-content'
);
await expect(inner).toBeVisible();
expect(await inner.evaluate(el => getComputedStyle(el).overflowY)).toBe(
'hidden'
);
// ...but never the shell's scroller, even while it is on screen.
expect(await shellOverflowY(page)).toBe('auto');

// Leave for a long page: the shell must still scroll.
await page.evaluate(() => {
location.hash = '#/api-connections/api-types/database/5';
});
await page.waitForSelector('df-service-details', { timeout: 20_000 });
expect(await shellOverflowY(page)).toBe('auto');

// No stylesheet may carry an unscoped rule that clips the shell.
const unscoped = await page.evaluate(
() =>
Array.from(document.head.querySelectorAll('style')).filter(s =>
/(^|[},])\s*\.mat-(sidenav|drawer)-content[^{,]*\{[^}]*overflow:\s*hidden/.test(
s.textContent || ''
)
).length
);
expect(unscoped).toBe(0);
});

test('roles access select panels size to content without a global overlay rule', async ({
page,
}) => {
await page.goto(
'/dreamfactory/dist/#/api-connections/role-based-access/create',
{
waitUntil: 'domcontentloaded',
}
);
await page.waitForSelector('df-roles-access', { timeout: 20_000 });
await page.getByRole('button', { name: /create a new entry/i }).click();
const select = page.locator('df-roles-access mat-select').first();
await expect(select).toBeVisible({ timeout: 10_000 });
await select.click();
const pane = page
.locator('.cdk-overlay-pane:has(.mat-mdc-select-panel)')
.first();
await expect(pane).toBeVisible({ timeout: 5_000 });
// panelWidth: null leaves the pane unsized so it fits its options.
expect(await pane.evaluate(el => (el as HTMLElement).style.width)).toBe('');
const leaked = await page.evaluate(
() =>
Array.from(document.head.querySelectorAll('style')).filter(s =>
/\.cdk-overlay-pane\s*\{[^}]*max-content/.test(s.textContent || '')
).length
);
expect(leaked).toBe(0);
await page.keyboard.press('Escape');
});
});
12 changes: 8 additions & 4 deletions src/app/adf-data-explorer/df-data-explorer.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
}

// Also target via ::ng-deep to override Angular Material's own host styles
::ng-deep mat-sidenav-container.explorer-sidenav-container {
:host ::ng-deep mat-sidenav-container.explorer-sidenav-container {
height: 100% !important;
min-height: 0 !important;
max-height: 100% !important;
Expand All @@ -47,9 +47,13 @@
background: var(--df-surface);
}

// Force mat-sidenav-content to have bounded height and act as positioned parent
::ng-deep .mat-sidenav-content,
::ng-deep .mat-drawer-content {
// Force the explorer's own mat-sidenav-content to a bounded height and make
// it the positioned parent. Scoped to :host on purpose: Angular 16 keeps a
// component's <style> in the document for the rest of the session once the
// component has rendered, and an unscoped ::ng-deep here used to match the
// app shell's mat-sidenav-content too, leaving every page visited after the
// Data Explorer with overflow: hidden and no scrollbar until a reload.
:host ::ng-deep .explorer-sidenav-container > .mat-drawer-content {
height: 100% !important;
overflow: hidden !important;
position: relative !important;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,9 @@
}
}

// Prompt row under a zero-value stat.
::ng-deep [prompt] {
// Prompt row under a zero-value stat (projected content, hence ::ng-deep;
// :host keeps the rule from applying to every [prompt] in the app).
:host ::ng-deep [prompt] {
font-size: 1.3rem;
color: var(--df-text-2);
font-weight: 400;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,3 @@ tr.element-row:not(.example-expanded-row):active {
.detail-input {
margin-right: 20px;
}
::ng-deep {
.cdk-overlay-pane {
width: max-content !important;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { TranslocoPipe } from '@ngneat/transloco';
import { BASE_SERVICE_TOKEN } from 'src/app/shared/constants/tokens';
import { DfBaseCrudService } from 'src/app/shared/services/df-base-crud.service';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatSelectModule, MAT_SELECT_CONFIG } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input';
import { MatExpansionModule } from '@angular/material/expansion';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
Expand All @@ -43,6 +43,12 @@ import {
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'df-roles-access',
// panelWidth null lets each select panel size to its content instead of
// the trigger width (the endpoint and verb lists are long). This replaces
// an unscoped ::ng-deep .cdk-overlay-pane rule that leaked onto every
// overlay in the app for the rest of the session once this screen had
// rendered.
providers: [{ provide: MAT_SELECT_CONFIG, useValue: { panelWidth: null } }],
templateUrl: './df-roles-access.component.html',
styleUrls: ['./df-roles-access.component.scss'],
standalone: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,10 @@ mat-icon {
}
}

::ng-deep .mat-expansion-panel-header > .mat-expansion-indicator::after {
:host ::ng-deep .mat-expansion-panel-header > .mat-expansion-indicator::after {
color: unset !important;
}
::ng-deep .mat-mdc-select-arrow {
:host ::ng-deep .mat-mdc-select-arrow {
color: unset !important;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,13 @@
}
}

::ng-deep [mat-sort-header].cdk-keyboard-focused .mat-sort-header-container,
[mat-sort-header].cdk-program-focused .mat-sort-header-container {
:host
::ng-deep
[mat-sort-header].cdk-keyboard-focused
.mat-sort-header-container,
:host
::ng-deep
[mat-sort-header].cdk-program-focused
.mat-sort-header-container {
border-bottom: unset !important;
}
Loading