-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b3d9e7a
commit 728422f
Showing
9 changed files
with
638 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
/lib |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.