Skip to content

odjs/classes

Repository files navigation

@odjs/classes

CircleCI npm codecov jsDelivr dependencies dev dependencies packagephobia bundlephobia types Known Vulnerabilities license

Classname management for @odjs/dom

While this project has been created to be used internally in @odjs/dom, it can be used as standalone both in Node.js and in the browser.

Install

npm i @odjs/classes

For the browser you can use one of our cdn scripts, or you can use a tool like Webpack, Browserify or Parcel.

see usage section...

API

syntax

classes(...names: ClassName[]): string;

Accepts any number of ClassName arguments and returns normalized classname string.

example

const classname = classes(
  "btn",
  ["is-small", "is-red", "is-enable"],
  {
    "is-rounded has-border": (current) => current["is-enable"],
    "is-enable": false,
  },
  { "has-border": false },
);

console.log(classname);
> "btn is-small is-red is-rounded"

note that "has-border" is not present in the resulting string, see object normalization feature for more information.

Types

ClassName

type ClassName = string | ClassObject | ClassArray;

ClassObject

type IsClassPresent = (current: { [key: string]: boolean }) => any;

interface ClassObject {
  [key: string]: IsClassPresent | any;
}

see Conditional Class for more info.

ClassArray

interface ClassArray {
  [index: number]: ClassName;
}

CDN

jsDelivr

<script src="https://cdn.jsdelivr.net/npm/@odjs/classes@latest/dist/map.umd.js"></script>

or for production...

<script src="https://cdn.jsdelivr.net/npm/@odjs/classes@latest/dist/map.umd.min.js"></script>

more options...

unpkg

<script src="https://unpkg.com/@odjs/classes@latest/dist/map.umd.js"></script>

for production...

<script src="https://unpkg.com/@odjs/classes@latest/dist/map.umd.min.js"></script>

more options...

Usage

Node.js

const classes = require("@odjs/classes");
element.className = classes("btn", { red: true });

Browser

After including the script tag in your html file, classes will be available globally.

element.className = classes("btn", { red: true });

Iteration Order

Object key iteration order is implementation dependent, which means there are no guarantees the iteration will happen in the order you declared them.

example

// BAD IDEA

classes({
  "btn is-red": true,
  "is-red": false,
});

"is-red" may be present or it may not, use the following code for a predictable result.

classes(
  "btn is-red",
  { "is-red": false }
);

Features

Object Normalization

Objects with "multi-class" keys (keys that contain spaces) and "multi-class" strings will be normalized, which allows to extend a "single-class" after it's been set from a "multi-class". It also trims any extra space in the object keys and ignore empty keys.

example

const classes1 = "btn is-small is-rounded is-enabled";

const classes2 = {
  "is-small": false,
  "is-medium": true,
};

const classes3 = {
  "is-rounded": false,
};

const classname = classes(
  classes1,
  classes2,
  classes3,
),

console.log(classname);
> "btn is-enabled is-medium"

Using this feature within a single is a bad idea. see Iteration Order for more info. However, it will work most of the times.

example

// this is a bad idea
const classObj = {
  "button is-rounded is-enabled": true,
  "is-rounded": false,
};

const classname = classes(classObj);

console.log(classname);
> "button is-enabled"

...most of the times!
however "is-rounded" may be present or it may not.

Use the following code for a predictable result.

const base = "button is-rounded is-enabled";

const cond = {
  "is-rounded": false,
};

const classname = classes(base, cond);

console.log(classname);
> "button is-enabled"

...always!

Conditional Class

When using a ClassObject the object key will be used as classname and the value will determine whether that classname should be present in the final result. If the value if a function, it will be called with an object as only argument containing the current normalized state of the result.

⚠️ DO NOT rely on classnames within the same object to be included in the normalized object. see Iteration Order for more info.

example

const classname = classes(
  "button is-enabled",
  "is-rounded",
  {
    "is-rounded": (current, classnames) => {
      // current = {
      //   button: true,
      //   "is-enabled": true
      //   "is-rounded": true
      // }
      // classnames = ['is-rounded']

      // note: is-rounded = true

      // returning undefined will case
      // the classname to be set to false
    }
  },
  {
    "is-red": (current, classnames) => {
      // current = {
      //   button: true,
      //   "is-enabled": true
      //   "is-rounded": false
      // }
      // classnames = ['is-red']

      // note: is-rounded = false
      // because it was set to false in the previous step

      return current["is-enabled"]
    },
  }
);

console.log(classname);
> "button is-enabled is-red"

License

MIT © Manuel Fernández