Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
@material/base
Advanced tools
The set of base classes for Material Components for the web
@material/base is a foundational package for building Material Design components. It provides base classes and utilities that help in creating custom components that adhere to Material Design guidelines.
Foundation Class
The Foundation class is a base class for creating the logic of a Material Design component. It provides a structure for defining the component's behavior and lifecycle methods.
const { MDCFoundation } = require('@material/base');
class MyComponentFoundation extends MDCFoundation {
constructor(adapter) {
super(Object.assign(MyComponentFoundation.defaultAdapter, adapter));
}
static get defaultAdapter() {
return {
addClass: () => {},
removeClass: () => {},
// other adapter methods
};
}
// Foundation methods
init() {
// Initialization logic
}
destroy() {
// Cleanup logic
}
}
module.exports = MyComponentFoundation;
Adapter Pattern
The Adapter pattern is used to abstract the DOM manipulation and other platform-specific operations. This allows the foundation logic to be platform-agnostic and reusable.
const { MDCFoundation } = require('@material/base');
class MyComponentAdapter {
addClass(className) {
// Add class to the component
}
removeClass(className) {
// Remove class from the component
}
// Other adapter methods
}
class MyComponentFoundation extends MDCFoundation {
constructor(adapter) {
super(Object.assign(MyComponentFoundation.defaultAdapter, adapter));
}
static get defaultAdapter() {
return {
addClass: () => {},
removeClass: () => {},
// other adapter methods
};
}
}
module.exports = { MyComponentAdapter, MyComponentFoundation };
Component Initialization
The Component class is used to initialize and manage the lifecycle of a Material Design component. It ties together the foundation and adapter to create a fully functional component.
const { MDCComponent } = require('@material/base');
const MyComponentFoundation = require('./my-component-foundation');
class MyComponent extends MDCComponent {
static attachTo(root) {
return new MyComponent(root, new MyComponentFoundation());
}
getDefaultFoundation() {
return new MyComponentFoundation({
addClass: (className) => this.root_.classList.add(className),
removeClass: (className) => this.root_.classList.remove(className),
// other adapter methods
});
}
}
module.exports = MyComponent;
React Material-UI is a popular library for implementing Material Design components in React applications. It provides a wide range of pre-built components that follow Material Design guidelines, making it easier to build consistent UIs. Unlike @material/base, which is more of a foundational library, React Material-UI offers ready-to-use components.
Vuetify is a Material Design component framework for Vue.js. It offers a comprehensive suite of pre-built components that adhere to Material Design specifications. Similar to React Material-UI, Vuetify provides ready-to-use components, whereas @material/base is more focused on providing the building blocks for custom components.
Angular Material is a UI component library for Angular developers that implements Material Design. It provides a set of reusable, well-tested, and accessible UI components based on Material Design. Angular Material is more feature-complete compared to @material/base, which serves as a foundation for building custom components.
MDC Base contains core foundation and component classes that serve as the base classes for all of MDC Web's foundation classes and components (respectively).
Most of the time, you shouldn't need to depend on mdc-base
directly. It is useful however if you'd like to write custom components that follow MDC Web's pattern and elegantly integrate with the MDC Web ecosystem.
First install the module:
npm install @material/base
Then include it in your code in one of the following ways:
import {MDCComponent, MDCFoundation} from '@material/base';
const MDCComponent = require('mdc-base').MDCComponent;
const MDCFoundation = require('mdc-base').MDCFoundation;
require(['path/to/mdc-base'], function(mdcBase) {
const MDCComponent = mdcBase.MDCComponent;
const MDCFoundation = mdcBase.MDCFoundation;
});
const MDCComponent = mdc.base.MDCComponent;
const MDCFoundation = mdc.base.MDCFoundation;
mdc-base exposes two classes: MDCComponent
(the default export) which all components extend from, and MDCFoundation
, which all foundation classes extend from. To learn more about foundation classes vs. components, check out our overview on architecture and best practices.
MDCFoundation provides the basic mechanisms for implementing foundation classes. Subclasses are expected to:
init()
and destroy()
lifecycle methodsimport {MDCFoundation} from '@material/base/foundation';
export default class MyFoundation extends MDCFoundation {
static get cssClasses() {
return {
ROOT: 'my-component',
MESSAGE: 'my-component__message',
BUTTON: 'my-component__button',
TOGGLED: 'my-component--toggled'
};
}
static get defaultAdapter() {
return {
toggleClass: (/* className: string */) => {},
registerBtnClickHandler: (/* handler: Function */) => {},
deregisterBtnClickHandler: (/* handler: Function */) => {}
};
}
constructor(adapter) {
super({...MyFoundation.defaultAdapter, ...adapter});
const {TOGGLED} = MyFoundation.cssClasses;
this.clickHandler = () => this.adapter.toggleClass(TOGGLED);
}
init() {
this.adapter.registerBtnClickHandler(this.clickHandler);
}
destroy() {
this.adapter.deregisterBtnClickHandler(this.clickHandler);
}
}
The static getters specify constants that can be used within the foundation class, its component, and by 3rd-party code. It's important to remember to always put constants into these getters. This will ensure your component can interop in as many environments as possible, including those where CSS classes need to be overwritten by the host library (e.g., Closure Stylesheets), or strings need to be modified (for i18n, for example).
Note that you do not have to explicitly provide getters for constants if your component has none.
The getters which should be provided are specified below:
getter | description |
---|---|
cssClasses | returns an object where each key identifies a css class that some code will rely on. |
strings | returns an object where each key identifies a string constant, e.g. ARIA_ROLE |
numbers | returns an object where each key identifies a numeric constant, e.g. TRANSITION_DELAY_MS |
defaultAdapter | returns an object specifying the shape of the adapter. Can be used as sensible defaults for an adapter as well as a way to specify your adapter's "schema" |
Each foundation class has two lifecycle methods: init()
and destroy()
, which are described below:
method | time of invocation | use case |
---|---|---|
init() | called by a host class when a component is ready to be initialized | add event listeners, query for info via adapters, etc. |
destroy() | called by a host class when a component is no longer in use | remove event listeners, reset any transient state, etc. |
MDCComponent provides the basic mechanisms for implementing component classes.
import MyComponentFoundation from './foundation';
export class MyComponent extends MDCComponent {
static attachTo(root) {
return new MyComponent(root);
}
getDefaultFoundation() {
const btn = this.root.querySelector<HTMLElement>(`.${MyComponentFoundation.cssClasses.BUTTON}`)!;
return new MyComponentFoundation({
toggleClass: className => {
if (this.root.classList.contains(className)) {
this.root.classList.remove(className);
return;
}
this.root.classList.add(className);
},
registerBtnClickHandler: handler => btn.addEventListener('click', handler),
deregisterBtnClickHandler: handler => btn.removeEventListener('click', handler)
});
}
}
MDCComponent
provides the following "private" properties to subclasses:
property | description |
---|---|
root | The root element passed into the constructor as the first argument. |
foundation | The foundation class for this component. This is either passed in as an optional second argument to the constructor, or assigned the result of calling getDefaultFoundation() |
MDCComponent
provides the following methods to subclasses:
method | description |
---|---|
initialize(...args) | Called after the root element is attached to the component, but before the foundation is instantiated. Any positional arguments passed to the component constructor after the root element, along with the optional foundation 2nd argument, will be provided to this method. This is a good place to do any setup work normally done within a constructor function. |
getDefaultFoundation() | Returns an instance of a foundation class properly configured for the component. Called when no foundation instance is given within the constructor. Subclasses must implement this method. |
initialSyncWithDOM() | Called within the constructor. Subclasses may override this method if they wish to perform initial synchronization of state with the host DOM element. For example, a slider may want to check if its host element contains a pre-set value, and adjust its internal state accordingly. Note that the same caveats apply to this method as to foundation class lifecycle methods. Defaults to a no-op. |
destroy() | Subclasses may override this method if they wish to perform any additional cleanup work when a component is destroyed. For example, a component may want to deregister a window resize listener. |
listen(type: string, handler: EventListener) | Adds an event listener to the component's root node for the given type . Note that this is simply a proxy to this.root.addEventListener . |
unlisten(type: string, handler: EventListener) | Removes an event listener from the component's root node. Note that this is simply a proxy to this.root.removeEventListener . |
emit(type: string, data: Object, shouldBubble: boolean = false) | Dispatches a custom event of type type with detail data from the component's root node. It also takes an optional shouldBubble argument to specify if the event should bubble. This is the preferred way of dispatching events within our vanilla components. |
In addition to methods inherited, subclasses should implement the following two static methods within their code:
method | description |
---|---|
attachTo(root) => <ComponentClass> | Subclasses must implement this as a convenience method to instantiate and return an instance of the class using the root element provided. This will be used within mdc-auto-init , and in the future its presence may be enforced via a custom lint rule. |
MDCComponent
calls its foundation's init()
function within its constructor, and its foundation's destroy()
function within its own destroy() function. Therefore it's important to remember to always call super() when overriding destroy(). Not doing so can lead to leaked resources.
If you need to pass in additional parameters into a component's constructor, you can make use of the
initialize
method, as shown above. An example of this is passing in a child component as a
dependency.
class MyComponent extends MDCComponent {
initialize(childComponent = null) {
this.child = childComponent ?
childComponent : new ChildComponent(this.root.querySelector<HTMLElement>('.child'));
}
getDefaultFoundation() {
return new MyComponentFoundation({
doSomethingWithChildComponent: () => this.child.doSomething(),
// ...
});
}
}
You could call this code like so:
const childComponent = new ChildComponent(document.querySelector<HTMLElement>('.some-child'));
const myComponent = new MyComponent(
document.querySelector<HTMLElement>('.my-component'), /* foundation */ undefined, childComponent
);
// use myComponent
NOTE: You could also pass in an initialized foundation if you wish. The example above simply showcases how you could pass in initialization arguments without instantiating a foundation.
If you find your adapters getting too complex, you should consider refactoring the complex parts out into their own implementations.
import MyComponentFoundation from './foundation';
import {toggleClass} from './util';
class MyComponent {
// ...
getDefaultFoundation() {
return new MyComponentFoundation({
toggleClass: className => util.toggleClass(this.root, className),
// ...
});
}
}
Where ./util
could look like:
export function toggleClass(element, className) {
if (root.classList.contains(className)) {
root.classList.remove(className);
return;
}
root.classList.add(className);
}
This not only reduces the complexity of your component class, but allows for the functionality of complex adapters to be adequately tested:
test('toggleClass() removes a class when present on an element', t => {
const root = document.createElement('div');
root.classList.add('foo');
util.toggleClass(root, 'foo');
t.false(root.classList.contains('foo'));
t.end();
});
FAQs
The set of base classes for Material Components for the web
The npm package @material/base receives a total of 826,033 weekly downloads. As such, @material/base popularity was classified as popular.
We found that @material/base demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 15 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.