Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

feat(modal): core modal #4479

Merged
merged 1 commit into from Apr 30, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/ngcc.js
Expand Up @@ -37,7 +37,7 @@ module.exports = findPackagesSync(path.join(process.cwd(), 'src'));
// './src/clr-core/package.json',
// './src/clr-core/badge/package.json',
// './src/clr-core/button/package.json',
// './src/clr-core/common/package.json',
// './src/clr-core/internal/package.json',
// './src/clr-core/icon-shapes/package.json',
// './src/clr-core/icon/package.json',
// './src/clr-core/tag/package.json',
Expand Down
12 changes: 7 additions & 5 deletions src/clr-angular/layout/main-container/_layout.clarity.scss
@@ -1,4 +1,4 @@
// Copyright (c) 2016-2019 VMware, Inc. All Rights Reserved.
// Copyright (c) 2016-2020 VMware, Inc. All Rights Reserved.
// This software is released under MIT license.
// The full license information can be found in LICENSE in the root directory of this project.

Expand Down Expand Up @@ -79,11 +79,13 @@
}
}

body.no-scrolling .main-container .content-container .content-area {
body.no-scrolling,
body[cds-layout='no-scrolling'] {
overflow: hidden;
}

body.no-scrolling {
overflow: hidden;
// The selector below targets Clarity-UI and is provided for compatibility
.main-container .content-container .content-area {
jeeyun marked this conversation as resolved.
Show resolved Hide resolved
overflow: hidden;
}
}
}
1 change: 1 addition & 0 deletions src/clr-angular/utils/i18n/common-strings.interface.ts
Expand Up @@ -129,6 +129,7 @@ export interface ClrCommonStrings {
* Modal end of content
*/
modalContentEnd: string;

/**
* Datagrid Show columns menu description
*/
Expand Down
4 changes: 2 additions & 2 deletions src/clr-core/alert/alert.base.ts
Expand Up @@ -49,7 +49,7 @@ const iconMap = {
* Base class for alerts. Contains properties and functions common to all alerts.
*/
export class CdsBaseAlert extends LitElement {
@event() private closedChange: EventEmitter<boolean>;
@event() private closeChange: EventEmitter<boolean>;

/** If false, the alert will not render the close button. */
@property({ type: Boolean })
Expand Down Expand Up @@ -127,6 +127,6 @@ export class CdsBaseAlert extends LitElement {
}

closeAlert() {
this.closedChange.emit(true);
this.closeChange.emit(true);
}
}
4 changes: 2 additions & 2 deletions src/clr-core/alert/alert.element.spec.ts
Expand Up @@ -162,10 +162,10 @@ describe('alert element', () => {
expect(button.getAttribute('aria-label')).toEqual(service.keys.alertCloseButtonAriaLabel);
});

it('should emit a closedChanged event when close button is clicked', async done => {
it('should emit a closeChange event when close button is clicked', async done => {
let value: any;
await componentIsStable(component);
component.addEventListener<any>('closedChange', (e: CustomEvent) => {
component.addEventListener<any>('closeChange', (e: CustomEvent) => {
value = e.detail;
expect(value).toBe(true);
done();
Expand Down
2 changes: 1 addition & 1 deletion src/clr-core/alert/alert.stories.ts
Expand Up @@ -65,7 +65,7 @@ export const API = () => {
.iconTitle=${iconTitle}
.size=${size}
.status=${alertStatus}
@closedChange=${action('closeChanged')}>
@closeChange=${action('closeChange')}>
<cds-alert-content>
${slot}
</cds-alert-content>
Expand Down
4 changes: 2 additions & 2 deletions src/clr-core/alert/app-alert.element.spec.ts
Expand Up @@ -147,10 +147,10 @@ describe('alert element', () => {
expect(button.getAttribute('aria-label')).toEqual(service.keys.alertCloseButtonAriaLabel);
});

it('should emit a closedChanged event when close button is clicked', async done => {
it('should emit a closeChanged event when close button is clicked', async done => {
let value: any;
await componentIsStable(component);
component.addEventListener<any>('closedChange', (e: CustomEvent) => {
component.addEventListener<any>('closeChange', (e: CustomEvent) => {
value = e.detail;
expect(value).toBe(true);
done();
Expand Down
2 changes: 1 addition & 1 deletion src/clr-core/alert/app-alert.stories.ts
Expand Up @@ -59,7 +59,7 @@ export const API = () => {
.iconShape=${iconShape}
.iconTitle=${iconTitle}
.status=${alertStatus}
@closedChange=${action('closeChanged')}>
@closeChange=${action('closeChanged')}>
<cds-alert-content>
${slot}
</cds-alert-content>
Expand Down
3 changes: 3 additions & 0 deletions src/clr-core/import-map.importmap
Expand Up @@ -9,11 +9,13 @@
"lit-html/directives/unsafe-html": "/base/node_modules/lit-html/directives/unsafe-html.js",
"ramda/es/anyPass": "/base/node_modules/ramda/es/anyPass.js",
"ramda/es/isNil": "/base/node_modules/ramda/es/isNil.js",
"ramda/es/includes": "/base/node_modules/ramda/es/includes.js",
"ramda/es/curryN": "/base/node_modules/ramda/es/curryN.js",
"ramda/es/path": "/base/node_modules/ramda/es/path.js",
"ramda/es/has": "/base/node_modules/ramda/es/has.js",
"ramda/es/is": "/base/node_modules/ramda/es/is.js",
"ramda/es/isEmpty": "/base/node_modules/ramda/es/isEmpty.js",
"ramda/es/without": "/base/node_modules/ramda/es/without.js",
"css-vars-ponyfill": "/base/node_modules/css-vars-ponyfill/dist/css-vars-ponyfill.esm.js",

"@clr/core/alert": "/base/dist/clr-core/alert/index.js",
Expand All @@ -22,6 +24,7 @@
"@clr/core/icon": "/base/dist/clr-core/icon/index.js",
"@clr/core/icon-shapes": "/base/dist/clr-core/icon-shapes/index.js",
"@clr/core/internal": "/base/dist/clr-core/internal/index.js",
"@clr/core/modal": "/base/dist/clr-core/modal/index.js",
"@clr/core/tag": "/base/dist/clr-core/tag/index.js",
"@clr/core/test-dropdown": "/base/dist/clr-core/test-dropdown/index.js",
"@clr/core/test/utils": "/base/dist/clr-core/test/utils.js"
Expand Down
82 changes: 82 additions & 0 deletions src/clr-core/internal/base/focus-trap.base.spec.ts
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2016-2020 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
import { createTestElement, removeTestElement, waitForComponent, componentIsStable } from '@clr/core/test/utils';
import { registerElementSafely } from '../utils/register.js';
import { CdsBaseFocusTrap } from './focus-trap.base.js';

// register the element just for testing purposes
registerElementSafely('cds-base-focus-trap', CdsBaseFocusTrap);

declare global {
interface HTMLElementTagNameMap {
'cds-base-focus-trap': CdsBaseFocusTrap;
}
}

describe('modal element', () => {
describe('basic', () => {
let testElement: HTMLElement;
let component: CdsBaseFocusTrap;
const placeholderText = 'Button Placeholder';

beforeEach(async () => {
testElement = createTestElement();
testElement.innerHTML = `
<cds-base-focus-trap>
<cds-button>
<span>${placeholderText}</span>
</cds-button>
</cds-base-focus-trap>
`;

await waitForComponent('cds-base-focus-trap');
component = testElement.querySelector<CdsBaseFocusTrap>('cds-base-focus-trap');
});

afterEach(() => {
removeTestElement(testElement);
});

it('should create the component', async () => {
await componentIsStable(component);
expect(component.innerText).toBe(placeholderText.toUpperCase());
});

it('should enable focus trap', async () => {
await componentIsStable(component);
expect((component as any).focusTrap).toBeDefined();
});
});

describe('demo mode', () => {
let testElement: HTMLElement;
let component: CdsBaseFocusTrap;
const placeholderText = 'Button Placeholder';

beforeEach(async () => {
testElement = createTestElement();
testElement.innerHTML = `
<cds-base-focus-trap __demo-mode>
<cds-button>
<span>${placeholderText}</span>
</cds-button>
</cds-base-focus-trap>
`;

await waitForComponent('cds-base-focus-trap');
component = testElement.querySelector<CdsBaseFocusTrap>('cds-base-focus-trap');
});

afterEach(() => {
removeTestElement(testElement);
});

it('should not create focus trap if demo mode', async () => {
await componentIsStable(component);
expect((component as any).focusTrap).toBeUndefined();
});
});
});
35 changes: 35 additions & 0 deletions src/clr-core/internal/base/focus-trap.base.ts
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2016-2020 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { html, LitElement } from 'lit-element';
import { FocusTrap } from '../utils/focus-trap.js';
import { property } from '../decorators/property.js';
export class CdsBaseFocusTrap extends LitElement {
protected focusTrap: FocusTrap;

@property({ type: Boolean })
private __demoMode = false;

connectedCallback() {
super.connectedCallback();

if (!this.__demoMode) {
jeeyun marked this conversation as resolved.
Show resolved Hide resolved
this.focusTrap = new FocusTrap(this);
this.focusTrap.enableFocusTrap();
}
}
disconnectedCallback() {
super.disconnectedCallback();

if (!this.__demoMode) {
this.focusTrap.removeFocusTrap();
}
}

protected render() {
return html`<slot></slot>`;
}
}
mathisscott marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions src/clr-core/internal/index.ts
Expand Up @@ -16,10 +16,13 @@ export * from './enums/key-codes.js';
export * from './services/common-strings.service.js';
export * from './services/common-strings.interface.js';
export * from './services/common-strings.default.js';
export * from './services/focus-trap-tracker.service.js';
export * from './utils/conditional.js';
export * from './utils/exists.js';
export * from './utils/focus-trap.js';
export * from './utils/framework.js';
export * from './utils/identity.js';
export * from './utils/key-codes.js';
export * from './utils/string.js';
export * from './mixins/css-helpers.js';
export * from './mixins/unique-id.js';
Expand Down
1 change: 1 addition & 0 deletions src/clr-core/internal/services/common-strings.default.ts
Expand Up @@ -35,6 +35,7 @@ export const commonStringsDefault: ClrCommonStrings = {
totalPages: 'Total Pages',
minValue: 'Min value',
maxValue: 'Max value',
modalCloseButtonAriaLabel: 'Close modal',
modalContentStart: 'Beginning of Modal Content',
modalContentEnd: 'End of Modal Content',
showColumnsMenuDescription: 'Show or hide columns menu',
Expand Down
5 changes: 4 additions & 1 deletion src/clr-core/internal/services/common-strings.interface.ts
Expand Up @@ -116,11 +116,14 @@ export interface ClrCommonStrings {
* Datagrid numeric filter: max
*/
maxValue: string;
/**
* Modal: Close modal button
*/
modalCloseButtonAriaLabel: string;
/**
* Datagrid filter toggle button
*/
datagridFilterAriaLabel?: string;

/**
* Modal start of content
*/
Expand Down
25 changes: 25 additions & 0 deletions src/clr-core/internal/services/focus-trap-tracker.service.ts
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2016-2020 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

/**
* FocusTrapTracker is a static class that keeps track of the active element with focus trap,
* in case there are multiple in a given page.
*/
export class FocusTrapTracker {
private static focusTrapElements: Element[] = [];

static setCurrent(el: Element) {
this.focusTrapElements.unshift(el);
}

static activatePreviousCurrent() {
this.focusTrapElements.shift();
}

static getCurrent() {
return this.focusTrapElements[0];
}
}
jeeyun marked this conversation as resolved.
Show resolved Hide resolved