Skip to content

Commit

Permalink
Add version 0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroen-dekker-ytec committed May 26, 2021
1 parent b3d9e7a commit 728422f
Show file tree
Hide file tree
Showing 9 changed files with 638 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
/lib
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "bem-toolkit",
"version": "0.6.0",
"description": "",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"prepare": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ytec-nl/bem-toolkit.git"
},
"keywords": [],
"author": "Jeroen Dekker",
"license": "ISC",
"bugs": {
"url": "https://github.com/ytec-nl/bem-toolkit/issues"
},
"homepage": "https://github.com/ytec-nl/bem-toolkit#readme",
"devDependencies": {
"typescript": "^4.0.3"
}
}
107 changes: 107 additions & 0 deletions src/bem-toolkit.bem-nodelist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* An array of BEM elements with some advanced functions
*/
export default class BEMNodeList extends Array<Element> {
public static create<T>(): BEMNodeList {
return Object.create(BEMNodeList.prototype) as BEMNodeList;
}

/**
* sets innerHTML for all elements in the BEMNodeList
*/
public set innerHTML(HTML: string) {
this.forEach((element) => {
element.innerHTML = HTML; // eslint-disable-line no-param-reassign
});
}

/**
* Returns a BEMNodelist containing all elements that have a certain BEM modifier
* @param modifier BEM modifier string
*/
public withModifier(modifier: string): BEMNodeList {
const nodes = new BEMNodeList();
this.forEach((element: Element) => {
if (element.getBEMModifiers().includes(modifier)) nodes.push(element);
});

return nodes;
}

/**
* Adds an event listener to all elements in the BEMNodeList
* @param type A case-sensitive string representing the event type to listen for.
* @param listener Callback that is called when the event occurs
* @param useCapture A Boolean indicating whether events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.
*/
public addEventListener(
type: string,
listener: (event?: Event) => void,
useCapture = false
): void {
this.forEach((element) => {
element.addEventListener(type, listener, useCapture);
});
}

/**
* Removes an event listener from all elements in the BEMNodeList
* @param type A case-sensitive string representing the event type to listen for.
* @param listener The callback that is to be removed
* @param useCapture A Boolean indicating whether events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.
*/
public removeEventListener(
type: string,
listener: (event?: Event) => void,
useCapture = false
): void {
this.forEach((element) => {
element.removeEventListener(type, listener, useCapture);
});
}

/**
* Toggles a BEM Modifier on or off for all elements of the BEMNodeList
*/
public toggleBEMModifier(modifier: string, force?: boolean): void {
this.forEach((element) => {
element.toggleBEMModifier(modifier, force);
});
}

/**
* Adds a BEM Modifier for all elements of the BEMNodeList
*/
public addBEMModifier(modifier: string): void {
this.forEach((element) => {
element.addBEMModifier(modifier);
});
}

/**
* Removes a BEM Modifier for all elements of the BEMNodeList
*/
public removeBEMModifier(modifier: string): void {
this.forEach((element) => {
element.removeBEMModifier(modifier);
});
}

/**
* Sets BEM modifier for one element of the BEMNodelist and remove it for the others
*
* @param modifier BEM modifier that needs to be set
* @param index Index of the element that needs the modifier to be added
* @param inverse Set modifier to all elements except for the one with index
*/
public setBEMState(modifier: string, indexInput: number | number[]): void {
const indexes: number[] = Array.isArray(indexInput)
? (indexInput as number[])
: ([indexInput] as number[]);

this.forEach((element, elementIndex) => {
const setModifier = indexes.indexOf(elementIndex) !== -1;
element.toggleBEMModifier(modifier, setModifier);
});
}
}
146 changes: 146 additions & 0 deletions src/bem-toolkit.bem-nodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import BEMNodeList from "./bem-toolkit.bem-nodelist";
import "./bem-toolkit";

/**
* An associative array where the keys are BEM element names and the values are BEMNodeLists
*
* Block elements without a BEM element name have the key '$bareBlock'
*/
export default class BEMNodes {
[key: string]: BEMNodeList;

public constructor(
target?: Element | NodeListOf<Element> | string,
mustInclude?: string[],
findRoot?: boolean
) {
const elements = BEMNodes.getElementsFromTarget(target);

if (!elements) return;

elements.forEach((elementNeedle) => {
const startElement = findRoot
? elementNeedle.getBEMBlockRoot()
: elementNeedle;

const blockName = startElement.getBEMBlockName();

BEMNodes.getBlockElements(this, startElement as Element, blockName);
});

if (mustInclude) {
if (!BEMNodes.arrayContainsArray(Object.keys(this), mustInclude)) {
throw new Error("Not all required elements were found in the DOM");
}
}
}

public static create<T>(): BEMNodes {
return Object.create(BEMNodes.prototype) as BEMNodes;
}

/**
* Adds an element to the BEMNodes
*
* @param key BEM element name that the Element will be added to
* @param element Element that will be added to the BEMNodes
*/
public static add(
bemnodesIn: BEMNodes,
key: string,
element: Element
): BEMNodes {
const bemnodes = bemnodesIn;
if (typeof bemnodes[key] === "undefined") {
bemnodes[key] = new BEMNodeList(element);
} else {
try {
bemnodes[key].push(element);
} catch (err) {
if (typeof bemnodes[key] === "function") {
throw new Error(
`Could not add element with name '${key}' to this BEMNodes, because it is also a BEMNodes method`
);
} else {
throw new Error(err);
}
}
}
return bemnodes;
}

/**
* Outputs array of Elements from different inputs
*
* @param target Target entrypoint of the BEMNodes
*/
private static getElementsFromTarget(
target: string | Element | NodeListOf<Element>
): Element[] {
if (typeof target === "string") {
return document.querySelectorAll(
target as string
) as unknown as Element[];
}
if (target instanceof Element) {
return [target as Element];
}

return target as unknown as Element[];
}

/**
* Returns a BEMNodes with all BEM elements in a block from a start Element
*
* @param accumulator Accumulates BEM block elements in a BEMNodes
* @param element Current element
* @param requiredBlockName Elements are ignored if they don't have this block name
*/
private static getBlockElements(
accumulatorIn: BEMNodes,
element: Element,
requiredBlockName: string
): BEMNodes {
let accumulator = accumulatorIn;
const elementName = element.getBEMElementName(requiredBlockName);
const blockName = element.getBEMBlockName(requiredBlockName);

let elementNameKey;
if (elementName)
elementNameKey = elementName.replace(/-([a-z])/g, (g) => {
return g[1].toUpperCase();
});

if (elementName && blockName === requiredBlockName) {
BEMNodes.add(accumulator, elementNameKey, element);
} else if (blockName === requiredBlockName) {
BEMNodes.add(accumulator, "$bareBlock", element);
}

if (typeof element.children !== "undefined") {
[...element.children].forEach((childNode: Element) => {
accumulator = this.getBlockElements(
accumulator,
childNode,
requiredBlockName
);
});
}
return accumulator;
}

/**
* Checks whether every element of an array is inside another array
*/
private static arrayContainsArray(
superset: string[],
subset: string[]
): boolean {
if (subset.length === 0) {
return false;
}
return subset.every((value) => {
return superset.indexOf(value) >= 0;
});
}
}
71 changes: 71 additions & 0 deletions src/bem-toolkit.classlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Workaround for IE11 not supporting classLists properly
*/
export default class ClassList extends Array<string> {
private element: Element;

public constructor(element: Element) {
const classString = element.getAttribute("class");
if (classString === null || classString === "") {
super();
this.element = element;
return;
}
const classes = classString.split(" ");

super(...classes);
this.element = element;
}

/**
* Add a class to the element
*
* @param className Name of the class that is added
*/
public add(className: string): void {
const classString = this.element.getAttribute("class") || "";
const classes = classString.split(" ");

if (classes.includes(className)) return;

this.element.setAttribute("class", `${classString} ${className}`);
}

/**
* Remove a class from the element
*
* @param className Name of the class that is removed
*/
public remove(className: string): void {
const classString = this.element.getAttribute("class") || "";
const classes = classString.split(" ");

if (!classes.includes(className)) return;

const index = classes.indexOf(className);
classes.splice(index, 1);

this.element.setAttribute("class", `${classes.join(" ")}`);
}

/**
*
* Toggle whether or not a class exists on the element
* @param className Name of the class that is added or removed
* @param forceInput Force whether the element should have the class or not
*/
public toggle(className: string, forceInput?: boolean): void {
const classString = this.element.getAttribute("class") || "";
const classes = classString.split(" ");

let force: boolean;
if (typeof forceInput === "undefined") {
force = !classes.includes(className);
} else {
force = forceInput;
}

if (force === true) this.add(className);
else this.remove(className);
}
}
Loading

0 comments on commit 728422f

Please sign in to comment.