MDC Web follows naming and documentation best practices to keep our code consistent, and our APIs user-friendly. We follow isolation best practices to keep our code loosely coupled. And we follow performance best practices to keep our components fast.
- Match spec whenever possible. If the nomenclature used in spec conflicts with a natively implemented element or pattern, reach out for guidance
- Use the BEM naming convention for CSS classes
- Keep documentation short, don't use ten words when one will do
- Let Material Design guidelines cover when/why to use a component
- Never reference element directly in the Foundation
TODO: Add more notes about how to isolate subsystems from component specifics
- Only animate properties that will run on the GPU
- Use
requestAnimationFrame
- Avoid constant synchronous DOM reads/writes
- Reduce the number of composite layers
- MDC Web has other lifecycle methods (
initialize()
andinitSyncWithDom()
) that are not contained within theconstructor
. - Typescript compiler cannot infer that the other methods are run in conjunction, and will throw an error on properties not defined.
- Feel free to use the
!
when you run into the error<PROPERTY_NAME> has no initializer and is not definitely assigned in the constructor.
. ie.
private progress_!: number; // Assigned in init
init() {
this.progress_ = 0;
}
- Prefer
interface
overtype
for defining types when possible.
- Defer to using
unknown
over bothany
and{}
types. - If you must choose between
any
and{}
defer to{}
.
@material/base
defines convenience types (EventType
andSpecificEventListener
) for working with events and event listeners.- Prefer to type as
EventType
overstring
when you expect that the string will be a standard event name (e.g.click
,keydown
). - Prefer to type as
SpecificEventListener
overEventListener
when you know what type of event is being listened for (e.g.SpecificEventHandler<'click'>
).
Node
is more generic thanElement
, whileElement
is more generic thanHTMLElement
.Node
is mainly used for the document or comments/text.Element
should be used when the type in question could beHTMLElement
,SVGElement
, or others.HTMLElement
only pertains to DOM Elements such as<a>
,<li>
,<div>
just to name a few.- Use the most generic type that you think is possible during runtime.
Only the index.ts
or component.ts
files are allowed to reference from other component packages' index.ts
.
This is because wrapping libraries only use foundation
and adapter
, so we should decouple the component
.
// BAD
import {MDCFoundation} from '@material/base';
// GOOD
import {MDCFoundation} from '@material/base/foundation';
Each adapter must be defined within an adapter.ts
file in the component's package directory.
All methods should contain a summary of what they should do. This summary should be
copied over to the adapter API documentation in our README. This will facilitate future endeavors
to potentially automate the generation of our adapter API docs.
Note that this replaces the inline comments present in the methods within defaultAdapter
.
// adapter.ts
export interface MDCComponentAdapter {
/**
* Adds a class to the root element.
*/
addClass(className: string): void;
/**
* Removes a class from the root element.
*/
removeClass(className: string): void;
}
Foundations must extend MDCFoundation
parameterized by their respective adapter.
The defaultAdapter
must return an object with the correct adapter shape.
// foundation.ts
import {MDCFoundation} from '@material/base/foundation';
import MDCComponentAdapter from './adapter';
export class MDCComponentFoundation extends MDCFoundation<MDCComponentAdapter> {
static get defaultAdapter(): MDCComponentAdapter {
return {
addClass: (className: string) => undefined,
removeClass: (className: string) => undefined,
};
}
}
Components must extend MDCComponent
parameterized by their respective foundation.
// index.ts
import {MDCComponent} from '@material/base/component';
import MDCComponentFoundation from './foundation';
export class MDCAwesomeComponent extends MDCComponent<MDCComponentFoundation> {
getDefaultFoundation(): MDCComponentFoundation {
return new MDCComponentFoundation({
addClass: (className: string) => this.root_.classList.add(className),
removeClass: (className: string) => this.root_.classList.remove(className),
});
}
}