From 8ad1157787ad847d835c70cc684beb4c9de4b26b Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Sat, 9 Nov 2019 23:35:56 -0500 Subject: [PATCH 01/13] build: enable codelyzer and most default Angular CLI TSLint rules - do some merging of tslint:recommended and our rules --- e2e/src/app.e2e-spec.ts | 2 +- package.json | 2 +- .../component-sidenav/component-sidenav.html | 6 +- src/app/shared/doc-viewer/doc-viewer.spec.ts | 13 +- src/app/shared/doc-viewer/doc-viewer.ts | 10 +- .../documentation-items.ts | 14 +- .../example-viewer/example-viewer.spec.ts | 10 +- .../navigation-focus/navigation-focus.ts | 5 +- .../shared/stack-blitz/stack-blitz-button.ts | 9 +- .../stack-blitz/stack-blitz-writer.spec.ts | 4 +- .../shared/stack-blitz/stack-blitz-writer.ts | 10 +- .../style-manager/style-manager.spec.ts | 8 +- src/app/shared/svg-viewer/svg-viewer.ts | 2 +- src/app/shared/theme-picker/theme-picker.ts | 3 +- tslint.json | 127 +++++++++++++----- 15 files changed, 146 insertions(+), 79 deletions(-) diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts index 7b30190b8..00643a7e5 100644 --- a/e2e/src/app.e2e-spec.ts +++ b/e2e/src/app.e2e-spec.ts @@ -8,7 +8,7 @@ describe('Material Docs App', () => { page = new MaterialDocsAppPage(); }); - it('should display welcome message', async() => { + it('should display welcome message', async () => { await page.navigateTo(); expect(await page.getTitleText()).toEqual('Angular Material'); }); diff --git a/package.json b/package.json index cf0150783..bf2cba577 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "start": "yarn build-themes && ng serve", "start:jit": "yarn build-themes && ng serve --aot=false", "start:prod": "yarn build-themes && ng serve --prod", - "lint": "tslint -p tsconfig.json", + "lint": "ng lint", "test": "yarn build-themes && ng test", "pree2e": "webdriver-manager update", "e2e": "yarn build-themes && ng e2e", diff --git a/src/app/pages/component-sidenav/component-sidenav.html b/src/app/pages/component-sidenav/component-sidenav.html index 49577abb0..03dd7e8bf 100644 --- a/src/app/pages/component-sidenav/component-sidenav.html +++ b/src/app/pages/component-sidenav/component-sidenav.html @@ -2,9 +2,9 @@ @@ -14,7 +14,7 @@
diff --git a/src/app/shared/doc-viewer/doc-viewer.spec.ts b/src/app/shared/doc-viewer/doc-viewer.spec.ts index ca491a78b..693051def 100644 --- a/src/app/shared/doc-viewer/doc-viewer.spec.ts +++ b/src/app/shared/doc-viewer/doc-viewer.spec.ts @@ -22,38 +22,37 @@ describe('DocViewer', () => { })); it('should load doc into innerHTML', () => { - let fixture = TestBed.createComponent(DocViewerTestComponent); + const fixture = TestBed.createComponent(DocViewerTestComponent); fixture.detectChanges(); const url = fixture.componentInstance.documentUrl; http.expectOne(url).flush(FAKE_DOCS[url]); - let docViewer = fixture.debugElement.query(By.directive(DocViewer)); + const docViewer = fixture.debugElement.query(By.directive(DocViewer)); expect(docViewer).not.toBeNull(); expect(docViewer.nativeElement.innerHTML).toBe('
my docs page
'); }); it('should save textContent of the doc', () => { - let fixture = TestBed.createComponent(DocViewerTestComponent); + const fixture = TestBed.createComponent(DocViewerTestComponent); fixture.detectChanges(); const url = fixture.componentInstance.documentUrl; http.expectOne(url).flush(FAKE_DOCS[url]); - let docViewer = fixture.debugElement.query(By.directive(DocViewer)); + const docViewer = fixture.debugElement.query(By.directive(DocViewer)); expect(docViewer.componentInstance.textContent).toBe('my docs page'); }); - it('should correct hash based links', () => { - let fixture = TestBed.createComponent(DocViewerTestComponent); + const fixture = TestBed.createComponent(DocViewerTestComponent); fixture.componentInstance.documentUrl = `http://material.angular.io/doc-with-links.html`; fixture.detectChanges(); const url = fixture.componentInstance.documentUrl; http.expectOne(url).flush(FAKE_DOCS[url]); - let docViewer = fixture.debugElement.query(By.directive(DocViewer)); + const docViewer = fixture.debugElement.query(By.directive(DocViewer)); // Our test runner runs at the page /context.html, so it will be the prepended value. expect(docViewer.nativeElement.innerHTML).toContain(`/context.html#test"`); }); diff --git a/src/app/shared/doc-viewer/doc-viewer.ts b/src/app/shared/doc-viewer/doc-viewer.ts index 4163a0f0d..335aa2e1a 100644 --- a/src/app/shared/doc-viewer/doc-viewer.ts +++ b/src/app/shared/doc-viewer/doc-viewer.ts @@ -102,15 +102,15 @@ export class DocViewer implements OnDestroy { /** Instantiate a ExampleViewer for each example. */ private _loadComponents(componentName: string, componentClass: any) { - let exampleElements = + const exampleElements = this._elementRef.nativeElement.querySelectorAll(`[${componentName}]`); Array.prototype.slice.call(exampleElements).forEach((element: Element) => { - let example = element.getAttribute(componentName); - let portalHost = new DomPortalHost( + const example = element.getAttribute(componentName); + const portalHost = new DomPortalHost( element, this._componentFactoryResolver, this._appRef, this._injector); - let examplePortal = new ComponentPortal(componentClass, this._viewContainerRef); - let exampleViewer = portalHost.attach(examplePortal); + const examplePortal = new ComponentPortal(componentClass, this._viewContainerRef); + const exampleViewer = portalHost.attach(examplePortal); if (example !== null) { (exampleViewer.instance as ExampleViewer).example = example; } diff --git a/src/app/shared/documentation-items/documentation-items.ts b/src/app/shared/documentation-items/documentation-items.ts index c9322f95b..c5b7d5ac9 100644 --- a/src/app/shared/documentation-items/documentation-items.ts +++ b/src/app/shared/documentation-items/documentation-items.ts @@ -583,14 +583,14 @@ const DOCS: {[key: string]: DocCategory[]} = { ] }; -for (let category of DOCS[COMPONENTS]) { - for (let doc of category.items) { +for (const category of DOCS[COMPONENTS]) { + for (const doc of category.items) { doc.packageName = 'material'; } } -for (let category of DOCS[CDK]) { - for (let doc of category.items) { +for (const category of DOCS[CDK]) { + for (const doc of category.items) { doc.packageName = 'cdk'; } } @@ -619,11 +619,11 @@ export class DocumentationItems { } getItemById(id: string, section: string): DocItem | undefined { - const sectionLookup = section == 'cdk' ? 'cdk' : 'material'; - return ALL_DOCS.find(doc => doc.id === id && doc.packageName == sectionLookup); + const sectionLookup = section === 'cdk' ? 'cdk' : 'material'; + return ALL_DOCS.find(doc => doc.id === id && doc.packageName === sectionLookup); } getCategoryById(id: string): DocCategory | undefined { - return ALL_CATEGORIES.find(c => c.id == id); + return ALL_CATEGORIES.find(c => c.id === id); } } diff --git a/src/app/shared/example-viewer/example-viewer.spec.ts b/src/app/shared/example-viewer/example-viewer.spec.ts index 750bf175d..a7c2d5352 100644 --- a/src/app/shared/example-viewer/example-viewer.spec.ts +++ b/src/app/shared/example-viewer/example-viewer.spec.ts @@ -126,7 +126,7 @@ describe('ExampleViewer', () => { }); it('should call copier service when clicked', (() => { - const copierService: CopierService = TestBed.get(CopierService); + const copierService: CopierService = TestBed.inject(CopierService); const spy = spyOn(copierService, 'copyText'); expect(spy.calls.count()).toBe(0, 'before click'); button.click(); @@ -135,8 +135,8 @@ describe('ExampleViewer', () => { })); it('should display a message when copy succeeds', (() => { - const snackBar: MatSnackBar = TestBed.get(MatSnackBar); - const copierService: CopierService = TestBed.get(CopierService); + const snackBar: MatSnackBar = TestBed.inject(MatSnackBar); + const copierService: CopierService = TestBed.inject(CopierService); spyOn(snackBar, 'open'); spyOn(copierService, 'copyText').and.returnValue(true); button.click(); @@ -144,8 +144,8 @@ describe('ExampleViewer', () => { })); it('should display an error when copy fails', (() => { - const snackBar: MatSnackBar = TestBed.get(MatSnackBar); - const copierService: CopierService = TestBed.get(CopierService); + const snackBar: MatSnackBar = TestBed.inject(MatSnackBar); + const copierService: CopierService = TestBed.inject(CopierService); spyOn(snackBar, 'open'); spyOn(copierService, 'copyText').and.returnValue(false); button.click(); diff --git a/src/app/shared/navigation-focus/navigation-focus.ts b/src/app/shared/navigation-focus/navigation-focus.ts index 9d0ce6012..eb0b8aa1f 100644 --- a/src/app/shared/navigation-focus/navigation-focus.ts +++ b/src/app/shared/navigation-focus/navigation-focus.ts @@ -1,13 +1,14 @@ -import {NgModule, OnInit, Directive, ElementRef} from '@angular/core'; +import {NgModule, OnInit, Directive, ElementRef, HostBinding} from '@angular/core'; /** The timeout id of the previous focus change. */ let lastTimeoutId = -1; @Directive({ selector: '[focusOnNavigation]', - host: {'tabindex': '-1'}, }) export class NavigationFocus implements OnInit { + @HostBinding('tabindex') role = '-1'; + constructor(private el: ElementRef) {} ngOnInit() { diff --git a/src/app/shared/stack-blitz/stack-blitz-button.ts b/src/app/shared/stack-blitz/stack-blitz-button.ts index e96670334..5480769e5 100644 --- a/src/app/shared/stack-blitz/stack-blitz-button.ts +++ b/src/app/shared/stack-blitz/stack-blitz-button.ts @@ -1,4 +1,4 @@ -import {Component, Input, NgModule} from '@angular/core'; +import {Component, HostListener, Input, NgModule} from '@angular/core'; import {ExampleData} from '@angular/components-examples'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; @@ -9,9 +9,6 @@ import {StackBlitzWriter} from './stack-blitz-writer'; selector: 'stack-blitz-button', templateUrl: './stack-blitz-button.html', providers: [StackBlitzWriter], - host: { - '(mouseover)': 'isDisabled = !stackBlitzForm' - } }) export class StackBlitzButton { /** @@ -23,6 +20,10 @@ export class StackBlitzButton { isDisabled = false; stackBlitzForm: HTMLFormElement; + @HostListener('mouseover') onMouseOver() { + this.isDisabled = !this.stackBlitzForm; + } + @Input() set example(example: string) { const exampleData = new ExampleData(example); diff --git a/src/app/shared/stack-blitz/stack-blitz-writer.spec.ts b/src/app/shared/stack-blitz/stack-blitz-writer.spec.ts index 30fa8762c..0e496762f 100644 --- a/src/app/shared/stack-blitz/stack-blitz-writer.spec.ts +++ b/src/app/shared/stack-blitz/stack-blitz-writer.spec.ts @@ -24,7 +24,7 @@ describe('StackBlitzWriter', () => { })); beforeEach(() => { - stackBlitzWriter = TestBed.get(StackBlitzWriter); + stackBlitzWriter = TestBed.inject(StackBlitzWriter); data = new ExampleData(''); data.exampleFiles = ['test.ts', 'test.html', 'src/detail.ts']; }); @@ -51,7 +51,7 @@ describe('StackBlitzWriter', () => { }); it('should add files to form input', () => { - let form = stackBlitzWriter._createFormElement('index.ts'); + const form = stackBlitzWriter._createFormElement('index.ts'); stackBlitzWriter._addFileToForm(form, data, 'NoContent', 'test.ts', 'path/to/file'); stackBlitzWriter._addFileToForm(form, data, 'Test', 'test.html', 'path/to/file'); diff --git a/src/app/shared/stack-blitz/stack-blitz-writer.ts b/src/app/shared/stack-blitz/stack-blitz-writer.ts index 76cd06e75..df723e32d 100644 --- a/src/app/shared/stack-blitz/stack-blitz-writer.ts +++ b/src/app/shared/stack-blitz/stack-blitz-writer.ts @@ -97,10 +97,10 @@ export class StackBlitzWriter { this._appendFormInput(form, 'dependencies', JSON.stringify(dependencies)); return new Promise(resolve => { - let templateContents = TEMPLATE_FILES + const templateContents = TEMPLATE_FILES .map(file => this._readFile(form, data, file, TEMPLATE_PATH)); - let exampleContents = data.exampleFiles + const exampleContents = data.exampleFiles .map(file => this._readFile(form, data, file, DOCS_CONTENT_PATH)); // TODO(josephperrott): Prevent including assets to be manually checked. @@ -166,7 +166,7 @@ export class StackBlitzWriter { filename: string, path: string, prependApp = true) { - if (path == TEMPLATE_PATH) { + if (path === TEMPLATE_PATH) { content = this._replaceExamplePlaceholderNames(data, filename, content); } else if (prependApp) { filename = 'src/app/' + filename; @@ -183,13 +183,13 @@ export class StackBlitzWriter { _replaceExamplePlaceholderNames(data: ExampleData, fileName: string, fileContent: string): string { - if (fileName == 'src/index.html') { + if (fileName === 'src/index.html') { // Replace the component selector in `index,html`. // For example, will be replaced as // fileContent = fileContent.replace(/material-docs-example/g, data.selectorName); fileContent = fileContent.replace(/{{version}}/g, VERSION.full); - } else if (fileName == 'src/main.ts') { + } else if (fileName === 'src/main.ts') { // Replace the component name in `main.ts`. // Replace `import {MaterialDocsExample} from 'material-docs-example'` // will be replaced as `import {ButtonDemo} from './button-demo'` diff --git a/src/app/shared/style-manager/style-manager.spec.ts b/src/app/shared/style-manager/style-manager.spec.ts index 9d82ad628..d5b958a28 100644 --- a/src/app/shared/style-manager/style-manager.spec.ts +++ b/src/app/shared/style-manager/style-manager.spec.ts @@ -16,8 +16,8 @@ describe('StyleManager', () => { })); afterEach(() => { - let links = document.head.querySelectorAll('link'); - for (let link of Array.prototype.slice.call(links)) { + const links = document.head.querySelectorAll('link'); + for (const link of Array.prototype.slice.call(links)) { if (link.className.includes('style-manager-')) { document.head.removeChild(link); } @@ -26,14 +26,14 @@ describe('StyleManager', () => { it('should add stylesheet to head', () => { styleManager.setStyle('test', 'test.css'); - let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; + const styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; expect(styleEl).not.toBeNull(); expect(styleEl.href.endsWith('test.css')).toBe(true); }); it('should change existing stylesheet', () => { styleManager.setStyle('test', 'test.css'); - let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; + const styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement; expect(styleEl).not.toBeNull(); expect(styleEl.href.endsWith('test.css')).toBe(true); diff --git a/src/app/shared/svg-viewer/svg-viewer.ts b/src/app/shared/svg-viewer/svg-viewer.ts index e4e8f4adf..7a0c8c84d 100644 --- a/src/app/shared/svg-viewer/svg-viewer.ts +++ b/src/app/shared/svg-viewer/svg-viewer.ts @@ -19,7 +19,7 @@ export class SvgViewer implements OnInit { this.elementRef.nativeElement.innerHTML = template; if (this.scaleToContainer) { - let svg = this.elementRef.nativeElement.querySelector('svg'); + const svg = this.elementRef.nativeElement.querySelector('svg'); svg.setAttribute('width', '100%'); svg.setAttribute('height', '100%'); svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); diff --git a/src/app/shared/theme-picker/theme-picker.ts b/src/app/shared/theme-picker/theme-picker.ts index f1ea8a3ce..570d0b8f5 100644 --- a/src/app/shared/theme-picker/theme-picker.ts +++ b/src/app/shared/theme-picker/theme-picker.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, + HostBinding, NgModule, OnDestroy, OnInit, @@ -24,9 +25,9 @@ import {map} from 'rxjs/operators'; styleUrls: ['theme-picker.scss'], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - host: {'aria-hidden': 'true'}, }) export class ThemePicker implements OnInit, OnDestroy { + @HostBinding('aria-hidden') role = 'true'; private _queryParamSubscription = Subscription.EMPTY; currentTheme: DocsSiteTheme; diff --git a/tslint.json b/tslint.json index 33fc1d300..4c8d71978 100644 --- a/tslint.json +++ b/tslint.json @@ -1,32 +1,102 @@ { + "extends": "tslint:recommended", + "rulesDirectory": [ + "codelyzer" + ], "rules": { - "max-line-length": [true, 100], - // Disable this flag because of SHA tslint#48b0c597f9257712c7d1f04b55ed0aa60e333f6a - // TSLint now shows warnings if types for properties are inferred. This rule needs to be - // disabled because all properties need to have explicit types set to work for Dgeni. - "no-inferrable-types": false, - "class-name": true, + "array-type": false, + "arrow-parens": false, + // Bans jasmine helper functions that will prevent the CI from properly running tests. + "ban": [ + true, + ["fit"], + ["fdescribe"], + ["xit"], + ["xdescribe"], + ["Object.assign", "Use the spread operator instead."], + ["Object", "Avoid using the `Object` type. Did you mean `object`?"], + ["Function", + "Avoid using the `Function` type. Prefer a specific function type, like `() => void`." + ], + ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], + ["Number", "Avoid using the `Number` type. Did you mean `number`?"], + ["String", "Avoid using the `String` type. Did you mean `string`?"], + ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"] + ], "comment-format": [ true, "check-space" ], + "contextual-lifecycle": true, + "deprecation": { + "severity": "warning" + }, "indent": [ true, "spaces" ], - "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "interface-name": false, + "jsdoc-format": [true, "check-multiline-start"], + // Avoids inconsistent linebreak styles in source files. Forces developers to use LF linebreaks. + "linebreak-style": [true, "LF"], + "max-classes-per-file": false, + "max-line-length": [true, 100], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-arg": true, + "no-conflicting-lifecycle": true, + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-debugger": true, + "no-duplicate-imports": true, "no-duplicate-variable": true, + "no-empty": false, "no-eval": true, - "no-arg": true, + "no-host-metadata-property": true, + // Disable this flag because of SHA tslint#48b0c597f9257712c7d1f04b55ed0aa60e333f6a + // TSLint now shows warnings if types for properties are inferred. This rule needs to be + // disabled because all properties need to have explicit types set to work for Dgeni. + "no-inferrable-types": false, + "no-input-rename": true, + "no-inputs-metadata-property": true, "no-internal-module": true, - "no-trailing-whitespace": true, - "no-bitwise": true, - "no-shadowed-variable": true, - "no-unused-expression": true, - "no-var-keyword": true, - "member-access": [true, "no-public"], - "no-debugger": true, + // Namespaces are not allowed, because of Closure compiler. + "no-namespace": true, + "no-non-null-assertion": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-string-literal": false, "no-unused-variable": true, + "no-use-before-declare": false, + "no-var-requires": false, + "object-literal-key-quotes": false, + "object-literal-shorthand": false, + "object-literal-sort-keys": false, "one-line": [ true, "check-catch", @@ -34,12 +104,14 @@ "check-open-brace", "check-whitespace" ], + "ordered-imports": false, "quotemark": [ true, "single", "avoid-escape" ], "semicolon": true, + "trailing-comma": false, "typedef-whitespace": [ true, { @@ -50,7 +122,6 @@ "variable-declaration": "nospace" } ], - "curly": true, "variable-name": [ true, "ban-keywords", @@ -66,20 +137,14 @@ "check-type", "check-preblock" ], - // Bans jasmine helper functions that will prevent the CI from properly running tests. - "ban": [ - true, - ["fit"], - ["fdescribe"], - ["xit"], - ["xdescribe"], - {"name": "Object.assign", "message": "Use the spread operator instead."} - ], - // Avoids inconsistent linebreak styles in source files. Forces developers to use LF linebreaks. - "linebreak-style": [true, "LF"], - // Namespaces are no allowed, because of Closure compiler. - "no-namespace": true, - "jsdoc-format": [true, "check-multiline-start"], - "no-duplicate-imports": true + // Codelyzer rules + "component-class-suffix": false, + "component-selector": false, + "directive-class-suffix": false, + "directive-selector": false, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true } } From 8d5fbf15f86f390997c94b3ba9b5b5531e649dec Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Sun, 10 Nov 2019 01:28:55 -0500 Subject: [PATCH 02/13] fix(a11y): fix theme picker keyboard access enable Codelyzer a11y rules Relates to #671 --- .../component-viewer/component-viewer.ts | 2 +- src/app/shared/doc-viewer/doc-viewer.ts | 4 +- src/app/shared/theme-picker/theme-picker.html | 22 +++---- src/app/shared/theme-picker/theme-picker.scss | 58 ------------------- .../shared/theme-picker/theme-picker.spec.ts | 2 +- src/app/shared/theme-picker/theme-picker.ts | 16 ++--- .../theme-storage/theme-storage.ts | 1 + tslint.json | 11 ++++ 8 files changed, 33 insertions(+), 83 deletions(-) diff --git a/src/app/pages/component-viewer/component-viewer.ts b/src/app/pages/component-viewer/component-viewer.ts index ae1562a9d..17870aa39 100644 --- a/src/app/pages/component-viewer/component-viewer.ts +++ b/src/app/pages/component-viewer/component-viewer.ts @@ -36,7 +36,7 @@ export class ComponentViewer implements OnDestroy { public _componentPageTitle: ComponentPageTitle, public docItems: DocumentationItems, ) { - let routeAndParentParams = [_route.params]; + const routeAndParentParams = [_route.params]; if (_route.parent) { routeAndParentParams.push(_route.parent.params); } diff --git a/src/app/shared/doc-viewer/doc-viewer.ts b/src/app/shared/doc-viewer/doc-viewer.ts index 335aa2e1a..40e297850 100644 --- a/src/app/shared/doc-viewer/doc-viewer.ts +++ b/src/app/shared/doc-viewer/doc-viewer.ts @@ -1,4 +1,4 @@ -import {ComponentPortal, DomPortalHost} from '@angular/cdk/portal'; +import {ComponentPortal, DomPortalHost, DomPortalOutlet} from '@angular/cdk/portal'; import {HttpClient, HttpErrorResponse} from '@angular/common/http'; import {DomSanitizer} from '@angular/platform-browser'; import { @@ -25,7 +25,7 @@ import {HeaderLink} from './header-link'; template: 'Loading document...', }) export class DocViewer implements OnDestroy { - private _portalHosts: DomPortalHost[] = []; + private _portalHosts: DomPortalOutlet[] = []; private _documentFetchSubscription: Subscription; @Input() name: string; diff --git a/src/app/shared/theme-picker/theme-picker.html b/src/app/shared/theme-picker/theme-picker.html index f88cbffbe..313d08df4 100644 --- a/src/app/shared/theme-picker/theme-picker.html +++ b/src/app/shared/theme-picker/theme-picker.html @@ -1,18 +1,14 @@ - - - - -
-
- check_circle -
-
-
-
-
+ + diff --git a/src/app/shared/theme-picker/theme-picker.scss b/src/app/shared/theme-picker/theme-picker.scss index cd010ae3c..e69de29bb 100644 --- a/src/app/shared/theme-picker/theme-picker.scss +++ b/src/app/shared/theme-picker/theme-picker.scss @@ -1,58 +0,0 @@ -$theme-picker-menu-padding: 8px; -$theme-picker-grid-cell-size: 48px; -$theme-picker-grid-cells-per-row: 2; -$theme-picker-swatch-size: 36px; -$theme-picker-accent-stripe-size: 6px; - - -.docs-theme-picker-menu { - .mat-menu-content { - padding: $theme-picker-menu-padding; - } - - [mat-menu-item] { - flex: 0 0 auto; - padding: 0; - overflow: hidden; - } - - .docs-theme-picker-swatch { - position: relative; - width: $theme-picker-swatch-size; - height: $theme-picker-swatch-size; - margin: ($theme-picker-grid-cell-size - $theme-picker-swatch-size) / 2; - border-radius: 50%; - overflow: hidden; - - .docs-theme-chosen-icon { - color: white; - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - } - - &::after { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - box-sizing: border-box; - border: 1px solid rgba(0,0,0,.2); - border-radius: 50%; - } - } - - .docs-theme-picker-primary { - width: 100%; - height: 100%; - } - - .docs-theme-picker-accent { - position: absolute; - bottom: $theme-picker-accent-stripe-size; - width: 100%; - height: $theme-picker-accent-stripe-size; - } -} diff --git a/src/app/shared/theme-picker/theme-picker.spec.ts b/src/app/shared/theme-picker/theme-picker.spec.ts index dca6faffa..47202a617 100644 --- a/src/app/shared/theme-picker/theme-picker.spec.ts +++ b/src/app/shared/theme-picker/theme-picker.spec.ts @@ -15,7 +15,7 @@ describe('ThemePicker', () => { const component = fixture.componentInstance; const name = 'pink-bluegrey'; spyOn(component.styleManager, 'setStyle'); - component.installTheme(name); + component.onThemeSelection(name); expect(component.styleManager.setStyle).toHaveBeenCalled(); expect(component.styleManager.setStyle).toHaveBeenCalledWith('theme', `assets/${name}.css`); }); diff --git a/src/app/shared/theme-picker/theme-picker.ts b/src/app/shared/theme-picker/theme-picker.ts index 570d0b8f5..8d7a1a8f4 100644 --- a/src/app/shared/theme-picker/theme-picker.ts +++ b/src/app/shared/theme-picker/theme-picker.ts @@ -1,11 +1,9 @@ import { ChangeDetectionStrategy, Component, - HostBinding, NgModule, OnDestroy, OnInit, - ViewEncapsulation, } from '@angular/core'; import {StyleManager} from '../style-manager'; import {DocsSiteTheme, ThemeStorage} from './theme-storage/theme-storage'; @@ -23,11 +21,9 @@ import {map} from 'rxjs/operators'; selector: 'theme-picker', templateUrl: 'theme-picker.html', styleUrls: ['theme-picker.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush }) export class ThemePicker implements OnInit, OnDestroy { - @HostBinding('aria-hidden') role = 'true'; private _queryParamSubscription = Subscription.EMPTY; currentTheme: DocsSiteTheme; @@ -35,12 +31,14 @@ export class ThemePicker implements OnInit, OnDestroy { { primary: '#673AB7', accent: '#FFC107', + displayName: 'Deep Purple / Amber', name: 'deeppurple-amber', isDark: false, }, { primary: '#3F51B5', accent: '#E91E63', + displayName: 'Indigo / Pink', name: 'indigo-pink', isDark: false, isDefault: true, @@ -48,12 +46,14 @@ export class ThemePicker implements OnInit, OnDestroy { { primary: '#E91E63', accent: '#607D8B', + displayName: 'Pink / Blue-grey', name: 'pink-bluegrey', isDark: true, }, { primary: '#9C27B0', accent: '#4CAF50', + displayName: 'Purple / Green', name: 'purple-green', isDark: true, }, @@ -65,7 +65,7 @@ export class ThemePicker implements OnInit, OnDestroy { private _activatedRoute: ActivatedRoute) { const themeName = this._themeStorage.getStoredThemeName(); if (themeName) { - this.installTheme(themeName); + this.onThemeSelection(themeName); } } @@ -74,7 +74,7 @@ export class ThemePicker implements OnInit, OnDestroy { .pipe(map((params: ParamMap) => params.get('theme'))) .subscribe((themeName: string | null) => { if (themeName) { - this.installTheme(themeName); + this.onThemeSelection(themeName); } }); } @@ -83,7 +83,7 @@ export class ThemePicker implements OnInit, OnDestroy { this._queryParamSubscription.unsubscribe(); } - installTheme(themeName: string) { + onThemeSelection(themeName: string) { const theme = this.themes.find(currentTheme => currentTheme.name === themeName); if (!theme) { diff --git a/src/app/shared/theme-picker/theme-storage/theme-storage.ts b/src/app/shared/theme-picker/theme-storage/theme-storage.ts index 8d5d5699d..24a039050 100644 --- a/src/app/shared/theme-picker/theme-storage/theme-storage.ts +++ b/src/app/shared/theme-picker/theme-storage/theme-storage.ts @@ -2,6 +2,7 @@ import {Injectable, EventEmitter} from '@angular/core'; export interface DocsSiteTheme { name: string; + displayName?: string; accent: string; primary: string; isDark?: boolean; diff --git a/tslint.json b/tslint.json index 4c8d71978..6a6e83f29 100644 --- a/tslint.json +++ b/tslint.json @@ -140,9 +140,20 @@ // Codelyzer rules "component-class-suffix": false, "component-selector": false, + "contextual-decorator": true, "directive-class-suffix": false, "directive-selector": false, + "template-accessibility-alt-text": true, + "template-accessibility-elements-content": true, + "template-accessibility-label-for": true, + "template-accessibility-tabindex-no-positive": true, + "template-accessibility-table-scope": true, + "template-accessibility-valid-aria": true, "template-banana-in-box": true, + "template-click-events-have-key-events": true, + "template-mouse-events-have-key-events": true, + "template-no-autofocus": true, + "template-no-distracting-elements": true, "template-no-negated-async": true, "use-lifecycle-interface": true, "use-pipe-transform-interface": true From 609451edd410ddbca8da1f2161061ab87d1b9c15 Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Fri, 27 Dec 2019 18:23:10 -0500 Subject: [PATCH 03/13] fix(component-overview): header does not show up in headings list Fixes #695 --- src/app/pages/component-viewer/component-overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pages/component-viewer/component-overview.html b/src/app/pages/component-viewer/component-overview.html index b991105db..ec47790f4 100644 --- a/src/app/pages/component-viewer/component-overview.html +++ b/src/app/pages/component-viewer/component-overview.html @@ -1,5 +1,5 @@ - + Overview for {{docItem?.id}} Date: Fri, 27 Dec 2019 18:55:01 -0500 Subject: [PATCH 04/13] fix(stack-blitz-button): no accessible label and description is not on correct element Fixes #693 --- src/app/shared/stack-blitz/stack-blitz-button.html | 13 ++++++------- src/app/shared/stack-blitz/stack-blitz-button.ts | 5 +++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/shared/stack-blitz/stack-blitz-button.html b/src/app/shared/stack-blitz/stack-blitz-button.html index abd0edc93..92dda84d6 100644 --- a/src/app/shared/stack-blitz/stack-blitz-button.html +++ b/src/app/shared/stack-blitz/stack-blitz-button.html @@ -1,7 +1,6 @@ -
- -
+ diff --git a/src/app/shared/stack-blitz/stack-blitz-button.ts b/src/app/shared/stack-blitz/stack-blitz-button.ts index 5480769e5..1ff82a39b 100644 --- a/src/app/shared/stack-blitz/stack-blitz-button.ts +++ b/src/app/shared/stack-blitz/stack-blitz-button.ts @@ -19,6 +19,7 @@ export class StackBlitzButton { */ isDisabled = false; stackBlitzForm: HTMLFormElement; + exampleData: ExampleData; @HostListener('mouseover') onMouseOver() { this.isDisabled = !this.stackBlitzForm; @@ -26,10 +27,10 @@ export class StackBlitzButton { @Input() set example(example: string) { - const exampleData = new ExampleData(example); + this.exampleData = new ExampleData(example); if (example) { - this.stackBlitzWriter.constructStackBlitzForm(exampleData) + this.stackBlitzWriter.constructStackBlitzForm(this.exampleData) .then((stackBlitzForm: HTMLFormElement) => { this.stackBlitzForm = stackBlitzForm; this.isDisabled = false; From 51c95fff4a9b90c8cce46d3cb096ded7aa088dce Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Fri, 27 Dec 2019 18:55:42 -0500 Subject: [PATCH 05/13] fix(example-viewer): no accessible label on View source button Fixes #693 --- src/app/shared/example-viewer/example-viewer.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/example-viewer/example-viewer.html b/src/app/shared/example-viewer/example-viewer.html index 65b052021..be0472f68 100644 --- a/src/app/shared/example-viewer/example-viewer.html +++ b/src/app/shared/example-viewer/example-viewer.html @@ -3,7 +3,7 @@
{{exampleData?.title}}
- + + + {{component.name}} + +
diff --git a/src/app/pages/component-sidenav/component-sidenav.ts b/src/app/pages/component-sidenav/component-sidenav.ts index 4d6ec07c8..ffc0990aa 100644 --- a/src/app/pages/component-sidenav/component-sidenav.ts +++ b/src/app/pages/component-sidenav/component-sidenav.ts @@ -37,6 +37,7 @@ import {HttpClientModule} from '@angular/common/http'; import {StackBlitzButtonModule} from '../../shared/stack-blitz'; import {SvgViewerModule} from '../../shared/svg-viewer/svg-viewer'; import {ExampleModule} from '@angular/components-examples'; +import {MatListModule} from '@angular/material/list'; const SMALL_WIDTH_BREAKPOINT = 720; @@ -81,6 +82,7 @@ export class ComponentSidenav implements OnInit { export class ComponentNav implements OnInit, OnDestroy { @Input() params: Observable; expansions: {[key: string]: boolean} = {}; + currentItemId: string; private _onDestroy = new Subject(); constructor(public docItems: DocumentationItems, @@ -91,7 +93,7 @@ export class ComponentNav implements OnInit, OnDestroy { startWith(null), switchMap(() => this.params), takeUntil(this._onDestroy) - ).subscribe(p => this.setExpansions(p)); + ).subscribe(params => this.setExpansions(params)); } ngOnDestroy() { @@ -103,14 +105,12 @@ export class ComponentNav implements OnInit, OnDestroy { setExpansions(params: Params) { const categories = this.docItems.getCategories(params.section); for (const category of (categories || [])) { - if (this.expansions[category.id]) { - continue; - } let match = false; for (const item of category.items) { if (this._router.url.indexOf(item.id) > -1) { match = true; + this.currentItemId = item.id; break; } } @@ -167,6 +167,9 @@ const routes: Routes = [ { @NgModule({ imports: [ + MatSidenavModule, + MatListModule, + RouterModule, CommonModule, ComponentCategoryListModule, ComponentHeaderModule, From f5630c5d54359ac7495d58107ae54fd41050e412 Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Fri, 27 Dec 2019 21:35:04 -0500 Subject: [PATCH 07/13] fix: use header and main tags for more semantic HTML - improves screen reader support - footer tags were already properly used Relates to #671. --- src/app/material-docs-app.html | 1 - .../component-page-header.html | 4 ++-- .../component-sidenav/component-sidenav.html | 4 ++-- src/app/pages/guide-list/guide-list.html | 20 ++++++++++--------- src/app/pages/homepage/homepage.html | 4 ++-- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/app/material-docs-app.html b/src/app/material-docs-app.html index e068a91b2..b7b9af2d0 100644 --- a/src/app/material-docs-app.html +++ b/src/app/material-docs-app.html @@ -1,3 +1,2 @@ - diff --git a/src/app/pages/component-page-header/component-page-header.html b/src/app/pages/component-page-header/component-page-header.html index 52266ab16..2e9f18857 100644 --- a/src/app/pages/component-page-header/component-page-header.html +++ b/src/app/pages/component-page-header/component-page-header.html @@ -1,7 +1,7 @@ -
+

{{getTitle()}}

-
+ diff --git a/src/app/pages/component-sidenav/component-sidenav.html b/src/app/pages/component-sidenav/component-sidenav.html index 03dd7e8bf..547bb3d3c 100644 --- a/src/app/pages/component-sidenav/component-sidenav.html +++ b/src/app/pages/component-sidenav/component-sidenav.html @@ -11,14 +11,14 @@
-
+
-
+
diff --git a/src/app/pages/guide-list/guide-list.html b/src/app/pages/guide-list/guide-list.html index 1967360c7..e441d3b9e 100644 --- a/src/app/pages/guide-list/guide-list.html +++ b/src/app/pages/guide-list/guide-list.html @@ -1,13 +1,15 @@ -
+

Guides

-
+ - - - {{guide.name}} - - +
+ + + {{guide.name}} + + +
diff --git a/src/app/pages/homepage/homepage.html b/src/app/pages/homepage/homepage.html index a1eb7a84f..ef09f06b0 100644 --- a/src/app/pages/homepage/homepage.html +++ b/src/app/pages/homepage/homepage.html @@ -11,7 +11,7 @@

Material Design components for Angular

-
+
Optimized for Angular Get started
-
+
From 291a460d8e9f828a1e67b7253ae9ca29a03c7427 Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Fri, 27 Dec 2019 21:36:00 -0500 Subject: [PATCH 08/13] fix(component-nav): apply aria-label to navs - so that they can be differentiated by screen readers Relates to #671. --- src/app/pages/component-sidenav/component-nav.html | 7 ++++--- src/app/pages/component-viewer/component-viewer.html | 2 +- src/app/shared/navbar/navbar.html | 4 ++-- src/app/shared/table-of-contents/table-of-contents.html | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/pages/component-sidenav/component-nav.html b/src/app/pages/component-sidenav/component-nav.html index 8a3fb231c..08bc40abd 100644 --- a/src/app/pages/component-sidenav/component-nav.html +++ b/src/app/pages/component-sidenav/component-nav.html @@ -1,6 +1,6 @@
- +
diff --git a/src/app/pages/component-viewer/component-viewer.html b/src/app/pages/component-viewer/component-viewer.html index f7610feaf..05f3f6163 100644 --- a/src/app/pages/component-viewer/component-viewer.html +++ b/src/app/pages/component-viewer/component-viewer.html @@ -1,5 +1,5 @@
-