New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

bemto-components

Package Overview
Dependencies
Maintainers
2
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bemto-components - npm Package Compare versions

Comparing version 1.3.0 to 1.4.0

7

Changelog.md
# `bemto-components` changelog
## v1.4.0 (2017-11-16)
- Added context handling for polymorphic tags.
- Added modifiers declaration on block with possible passing of functions.
- Added a way to pass props on element declaration.
- Fixed a tag getter from the tagString of an element declaration.
## v1.3.0 (2017-11-08)

@@ -4,0 +11,0 @@

302

lib/index.js
const React = require('react');
const PropTypes = require('prop-types');

@@ -15,4 +16,36 @@ // The code below is a mess, I'm sorry. But it is a working mess at least!

const gatherModifiers = function(props) {
return Object.keys(props).filter(prop => props[prop] && prop[0] === '_' && prop[1] !== '_').map(key => typeof props[key] === 'string' ? `${key}_${props[key]}` : key );
const renderModifier = function(key, value, props) {
const type = typeof value;
if (type === 'string' || type === 'number') {
return `${key}_${value}`;
}
if (type === 'function') {
modValue = value(props);
if (modValue) {
return renderModifier(key, modValue, props);
} else {
return;
}
}
if (value) {
return key;
}
}
const gatherModifiers = function(props, modifiersObj) {
if (!props) return [];
const propKeys = Object.keys(props);
const modifiers = propKeys.filter(prop => props[prop] && prop[0] === '_' && prop[1] !== '_').map(key => renderModifier(key, props[key], props));
if (modifiersObj) {
for (var key in modifiersObj) {
if (propKeys.indexOf(key) === -1) {
const modValue = renderModifier(key, modifiersObj[key], props);
if (modValue) {
modifiers.push(modValue);
}
}
}
}
return modifiers;
};

@@ -30,3 +63,3 @@

// Base for polymorphic tag names
const selectTag = function(props, defaultTag) {
const selectTag = function(props, defaultTag, context) {
if (typeof defaultTag === 'string') {

@@ -41,3 +74,10 @@ if (props.href) return 'a';

}
if (context) {
if (context === 'list') return 'li';
if (context === 'optionlist') return 'option';
}
}
if (defaultTag === 'div') {
if (context && context === 'inline') return 'span';
}
}

@@ -47,4 +87,27 @@ return defaultTag;

const applyToAll = function(content, func) {
return (content && content.constructor === Array) ? content.map(func) : func(content);
const applyToAll = function(content, func, modifier) {
if (React.isValidElement(content)) {
if (modifier) {
return {
content: content,
type: 'reactContent',
modifier: modifier
};
}
return content;
}
const isArray = (content && content.constructor === Array);
if (modifier) {
const modifyContent = function(content) {
for (var key in modifier) {
content[key] = modifier[key];
}
return content;
}
const modifiedContent = applyToAll(content, modifyContent);
return isArray ? modifiedContent.map(func) : func(modifiedContent);
}
return isArray ? content.map(func) : func(content);
};

@@ -55,62 +118,127 @@

const collectOptions = function(tagString, additionalOptions) {
const options = additionalOptions || typeof tagString !== 'string' && tagString || {};
options.type = 'block';
options.tagString = options.tagString || typeof tagString === 'string' && tagString;
options.parsedTagString = parseTagString(options.tagString || '');
options.tag = options.tag || options.parsedTagString.tag;
options.props = options.props || options.parsedTagString.props || {};
options.props.className = (options.props.className || options.className || '')+ ' ' + options.parsedTagString.className
options.props.id = options.props.id || options.id || options.parsedTagString.id;
return options;
const options = additionalOptions || typeof tagString === 'object' && tagString || {};
const parsedTagString = parseTagString(options.tagString || typeof tagString === 'string' && tagString || '');
options.type = typeof tagString === 'function' ? 'wrapper' : 'block';
options.tag = options.tag || (typeof tagString === 'function' && tagString) || parsedTagString.tag;
options.props = options.props || parsedTagString.props || {};
options.props.className = [options.props.className, options.className, parsedTagString.className].filter(className => className).join(' ');
options.props.id = options.props.id || options.id || parsedTagString.id;
return {
tag: options.tag,
type: options.type,
props: options.props,
content: options.content,
modifiers: options.modifiers
};
};
// Most of the props-handling stuff happens there
// TODO: refactor this to a few specialized funcions.
const gatherOptions = function(outerOptions, baseOptions) {
// Clone & merge options from arguments
const options = Object.assign(baseOptions, outerOptions);
options.props = Object.assign({}, outerOptions.props);
const normalizeClassNames = function(classNames) {
const classNamesArray = classNames.split(/\s+/);
return classNamesArray.filter((item, index) => classNamesArray.indexOf(item) === index).join(' ');
};
// Modify options if we're at elem scope
options.elem = options.tagProps.__BemtoElem;
if (options.elem) {
options.type = 'elem';
const elemParsedTagString = options.elem.parsedTagString || parseTagString(options.elem.tagString || '');
if (elemParsedTagString.tag) {
options.tag = elemParsedTagString.tag;
const moveValidProps = function(fromProps, toProps, tag) {
if (fromProps) {
for (var key in fromProps) {
if (!(typeof tag === 'string' && key[0] === '_') && key !== 'children' && key !== 'className') {
toProps[key] = fromProps[key];
}
}
if (elemParsedTagString.id) {
options.props.id = elemParsedTagString.id;
}
options.props.className = elemParsedTagString.className || '';
toProps.className = (toProps.className || '') + ' ' + (fromProps.className || '');
}
};
options.finalProps = {};
for (var key in (options.elem && (options.elem.props || {}) || options.tagProps)) {
if (!(typeof options.tag === 'string' && key[0] === '_') && key !== 'children') {
options.finalProps[key] = (options.elem && (options.elem.props || {}) || options.tagProps)[key];
}
}
// Most of the props-handling stuff happens there
const gatherOptions = function(outerOptions, baseOptions) {
// Preparing all the objects
const options = Object.assign({}, outerOptions);
const originalProps = Object.assign({}, options.props);
const elem = baseOptions.tagProps.__BemtoElem;
// Starting to initiate base props
const fullProps = elem ? elem.props : baseOptions.tagProps;
const finalOptions =
elem
? collectOptions(elem.tagString, elem.parsedTagString)
: Object.assign({}, options);
finalOptions.props = Object.assign({}, finalOptions && finalOptions.props);
finalOptions.modifiers = elem ? elem.modifiers : finalOptions.modifiers;
finalOptions.tagContext = elem ? elem.tagContext || finalOptions.tagContext || 'block' : baseOptions.tagContext;
if (options.props.id && !options.finalProps.id) {
options.finalProps.id = options.props.id;
}
// Use just the props that don't start with underscore for the final version
moveValidProps(fullProps, finalOptions.props, finalOptions.tag)
if (options.elem) {
options.finalProps.className = (options.finalProps.className || '') + ' ' + createElemClassNames(((options.tagProps.className || '') + ' ' + options.parsedTagString.className), [options.elem.name]);
if (elem) {
// Adding Element parts to classNames
finalOptions.props.className += ' ' + createElemClassNames(((baseOptions.tagProps.className || '') + ' ' + originalProps.className), [elem.name]);
} else {
// Otherwise, add the block's className
finalOptions.props.className += ' ' + originalProps.className;
}
options.finalProps.className = modifyClassNames((options.finalProps.className || '') + ' ' + options.props.className, gatherModifiers(options.elem && (options.elem.props || {}) || options.tagProps));
// Applying modifiers
finalOptions.props.className = modifyClassNames(
finalOptions.props.className,
gatherModifiers(finalOptions.type !== 'wrapper' && fullProps, finalOptions.modifiers));
// FIXME: replace with joining className_s_ (which don't exist yet)
options.finalProps.className = options.finalProps.className.replace(/\s{2,}/g, ' ');
// Normalizing classNames (removing duplicates etc.)
finalOptions.props.className = normalizeClassNames(finalOptions.props.className);
options.finalTag = selectTag(options.finalProps, options.tag);
// Handling id (hmm, should be done somehow better?)
if (originalProps.id && !finalOptions.props.id) {
finalOptions.props.id = originalProps.id;
}
if (!finalOptions.props.id) {
delete finalOptions.props.id;
}
if (!finalOptions.props.className) {
delete finalOptions.props.className;
}
return options;
return {
finalTag: selectTag(finalOptions.props, finalOptions.tag, finalOptions.tagContext),
finalProps: finalOptions.props,
children: baseOptions.children
};
}
const SELF_CLOSING_TAGS = ['hr', 'br', 'wbr', 'source', 'img', 'input'];
const INLINE_TAGS = ['a', 'abbr', 'acronym', 'b', 'code', 'em', 'font', 'i', 'ins', 'kbd', 'map', 'pre', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'textarea', 'time'];
const INLINE_CONTEXT_TAGS = ['label', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const LIST_CONTEXT_TAGS = ['ul', 'ol'];
const OPTIONS_CONTEXT_TAGS = ['select', 'datalist'];
// TODO: add plaintext context?
const getContextFromTag = function(tag, context) {
if (tag === 'div') {
return context === 'inline' ? 'inline' : 'block';
}
if (typeof tag !== 'string') {
return;
}
if (INLINE_TAGS.indexOf(tag) !== -1 || INLINE_CONTEXT_TAGS.indexOf(tag) !== -1) {
return 'inline';
}
if (LIST_CONTEXT_TAGS.indexOf(tag) !== -1) {
return 'list';
}
if (OPTIONS_CONTEXT_TAGS.indexOf(tag) !== -1) {
return 'optionlist';
}
if (SELF_CLOSING_TAGS.indexOf(tag) !== -1) {
return 'empty';
}
return 'block';
};
// Recursive unfolder of children for the object to elements generator
const unfoldChildren = function(props, generator) {
const unfoldFunction = function(item, index) {
const unfoldFunction = function(item, index, modifier) {
if (React.isValidElement(item)) {
return item;
}
// Not sure if we need it there or if won't be ever used?
if (item.type && item.type === 'reactContent') {
return item.content;
}
const elemName = item.name || item.elem;

@@ -141,11 +269,19 @@ let elemProp = props['__' + elemName] || item.parents && props['__' + item.parents.filter(name => props['__' + name]).reverse()[0]];

const parsedTagString = parseTagString(item.tagString);
parsedTagString.tag = item.tag || 'div';
parsedTagString.tag = item.tag || parsedTagString.tag || 'div';
if (item.className) { parsedTagString.className += ' ' + item.className; }
const myPropsProps = elemProp && elemProp.props || {};
const myPropsProps = elemProp && elemProp.props || item.props || {};
if (elemProp && elemProp.props) {
for (var key in elemProp.props) {
myPropsProps[key] = elemProp.props[key];
}
}
myPropsProps.key = index;
const myProps = Object.assign({
__BemtoElem: {
tagContext: item.tagContext,
name: elemName,
props: myPropsProps,
parsedTagString: parsedTagString
parsedTagString: parsedTagString,
modifiers: item.modifiers
}

@@ -158,3 +294,6 @@ }, props);

parents.push(elemName);
content.parents = parents;
const contentModifier = {
parents: parents
};
contentModifier.tagContext = getContextFromTag(selectTag(myPropsProps, parsedTagString.tag, item.tagContext))
if (item.list && elemProp && elemProp.constructor === Array) {

@@ -184,3 +323,3 @@ const key_prefix = myPropsProps.key + '_';

}
return generator(myProps, applyToAll(content, unfoldFunction));
return generator(myProps, applyToAll(content, unfoldFunction, contentModifier));
} else if (item.children || item.type === 'children') {

@@ -193,2 +332,18 @@ return props.children;

// Provider of bemto tag context for using when there are stuff inbetween that won't set it
class BemtoContextProvider extends React.Component {
getChildContext() {
return { bemtoTagContext: this.props.context }
}
render() {
return this.props.children;
}
}
BemtoContextProvider.propTypes = {
context: PropTypes.string.isRequired,
};
BemtoContextProvider.childContextTypes = {
bemtoTagContext: PropTypes.string.isRequired
};
// Our main factory

@@ -198,14 +353,26 @@ const bemto = function(tagString, additionalOptions) {

const bemtoFactory = class bemtoTag extends React.Component {
constructor(props, context) {
super(props);
this.state = {
bemtoTagContext: getContextFromTag(blockOptions.tag, context && context.bemtoTagContext)
};
}
getChildContext() {
return { bemtoTagContext: this.state.bemtoTagContext }
}
render() {
const generateTag = (props, children) => {
const isReactElem = children && children.type && children.type === 'reactContent';
const options = gatherOptions(blockOptions, {
tagProps: props,
children: children
children: isReactElem ? React.createElement(BemtoContextProvider, { context: children.modifier.tagContext }, children.content) : children,
tagContext: this.context.bemtoTagContext
})
return React.createElement(options.finalTag, options.finalProps, options.children);
};
const children = blockOptions.content
? applyToAll(blockOptions.content, unfoldChildren(this.props, generateTag))
const content = !this.props.__BemtoElem && blockOptions.content;
const children = content
? applyToAll(content, unfoldChildren(this.props, generateTag), {
tagContext: getContextFromTag(blockOptions.tag)
})
: this.props.children;

@@ -216,8 +383,14 @@ return generateTag(this.props, children);

bemtoFactory.elem = (elemName, tagString) => {
return bemto.elem(bemtoFactory, elemName, tagString || '');
bemtoFactory.childContextTypes = {
bemtoTagContext: PropTypes.string
};
bemtoFactory.contextTypes = {
bemtoTagContext: PropTypes.string
};
bemtoFactory.elem = (elemName, tagString, options) => {
return bemto.elem(bemtoFactory, elemName, tagString || '', options);
};
bemtoFactory.addElem = (elemName, tagString) => {
bemtoFactory[elemName] = bemtoFactory.elem(elemName, tagString);
bemtoFactory.addElem = (elemName, tagString, options) => {
bemtoFactory[elemName] = bemtoFactory.elem(elemName, tagString, options);
return bemtoFactory;

@@ -229,3 +402,3 @@ };

bemto.elem = function(block, elemName, tagString) {
bemto.elem = function(block, elemName, tagString, options) {
const parsedTagString = parseTagString(tagString);

@@ -238,3 +411,4 @@ return class bemtoTag extends React.Component {

props: this.props,
parsedTagString: parsedTagString
parsedTagString: parsedTagString,
options: options
};

@@ -241,0 +415,0 @@ return React.createElement(block, props, this.props.children);

{
"name": "bemto-components",
"version": "1.3.0",
"version": "1.4.0",
"description": "Smart components for using parts of BEM methodology with React",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

@@ -27,2 +27,3 @@ # bemto-components 🍱

5. `bemto-components` allows you to sometimes omit the tag names and get them from the context: if you'd call a bemto block from inside another bemto block which has inline context (like a `span` tag) without explicitly stating a tag (or for some reason trying to have a `div`), it would become a `span`. The same would happen for `ul` and `select`: its items would be by default `li` and `option`.

@@ -29,0 +30,0 @@ ## Disclaimer

@@ -80,2 +80,87 @@ const React = require('react');

test('with modifiers passed in the modifiers object', () => {
testSnapshot(
bemto('.block', {
modifiers: {
_modWithVal: 'value',
_modBool: true,
_modNum: 2,
_noMod: false
}
})
);
});
test('with modifiers passed in the modifiers object while having an element', () => {
testSnapshot(
bemto('.block', {
content: [
{ children: true },
{
elem: 'Helper',
modifiers: {
_elemModWithVal: 'value',
_elemModBool: true,
_elemModNum: 2,
_elemNoMod: false
}
}
],
modifiers: {
_modWithVal: 'value',
_modBool: true,
_modNum: 2,
_noMod: false
}
})
);
});
test('with modifiers passed in the modifiers object based on other props', () => {
testSnapshot(
bemto('.block', {
modifiers: {
_hasTitle: (props) => !!props.title,
_titleText: (props) => props.title,
_moreThan9000: (props) => props.power > 9000
}
}),
{
title: 'hello',
power: 9042
}
);
});
test('with modifiers passed in the modifiers object based on other props (absense)', () => {
testSnapshot(
bemto('.block', {
modifiers: {
_hasTitle: (props) => !!props.title,
_titleText: (props) => props.title,
_moreThan9000: (props) => props.power > 9000
}
})
);
});
test('with modifiers passed in the modifiers object based on other props which are then overriden', () => {
testSnapshot(
bemto('.block', {
modifiers: {
_hasTitle: (props) => !!props.title,
_titleText: (props) => props.title,
_moreThan9000: (props) => props.power > 9000
}
}),
{
title: 'hello',
power: 9042,
_moreThan9000: false,
_titleText: 'goodbye',
_hasTitle: false
}
);
});
test('with tag and explicit class name', () => {

@@ -210,2 +295,15 @@ testSnapshot(

test('with a wrapped component', () => {
const wrappedComponent = bemto('span.wrappedComponent');
testSnapshot(
bemto(wrappedComponent, {
className: 'wrapComponent'
}),
{
className: 'externalClassname',
_mod: 'value'
}
);
});
test('should become an anchor based on an attrubute, even when defined as a button', () => {

@@ -382,2 +480,307 @@ testSnapshot(

test('simple block with a Helper item before children with some added props', () => {
testSnapshot(
bemto('.myBlock', {
content: [
{
elem: 'Helper',
props: {
title: 'Oh wow',
hidden: true
}
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('with nested bemto block in inline context', () => {
testSnapshot(
bemto('span.inlineClass1'),
{},
React.createElement(bemto('.class2'))
);
});
test('with nested bemto block in list context', () => {
testSnapshot(
bemto('ul.listClass1'),
{},
React.createElement(bemto('.class2'))
);
});
test('with nested bemto block in select context', () => {
testSnapshot(
bemto('select.selectClass1'),
{},
React.createElement(bemto('.class2'))
);
});
test('with multiple nested bemto blocks in inline context', () => {
testSnapshot(
bemto('span.inlineClass1'),
{},
React.createElement(bemto('.class2'), {}, React.createElement(bemto('.class3')))
);
});
test('with a more complex nested context', () => {
const Minimal = bemto();
const List = bemto('ol');
const Strong = bemto('strong');
testSnapshot(
List,
{},
React.createElement(Minimal, {}, [
'Item',
React.createElement(Strong, { key: 'a'}, [
React.createElement(Minimal, { key: 'a'}, 'with '),
React.createElement(Minimal, { key: 'b'}, 'spans')
]),
React.createElement(Minimal, { key: 'b'}, 'And a new line')
])
);
});
test('simple block with a Helper item before children with inline context', () => {
testSnapshot(
bemto('span.myInlineBlock', {
content: [
{
elem: 'Helper'
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('simple block with a Helper containing an extra elem at inline context', () => {
testSnapshot(
bemto('span.myInlineBlock', {
content: [
{
elem: 'Helper',
content: React.createElement(bemto('.class2'))
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('simple block with a Helper containing an array of extra elems at inline context', () => {
testSnapshot(
bemto('span.myInlineBlock', {
content: [
{
elem: 'Helper',
content: [
React.createElement(bemto('.class2'), { key: 'a' }),
React.createElement(bemto('.class2'), { key: 'b' })
]
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('with a nested Helpers containing an extra elem at inner inline context', () => {
testSnapshot(
bemto('.myBlock', {
content: [
{
elem: 'InlineHelper',
tag: 'span',
content: {
elem: 'Helper2',
content: React.createElement(bemto('.class2'))
}
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('with a nested Helpers containing an extra elem at inner inline context overridden by label', () => {
testSnapshot(
bemto('span.myInlineBlock', {
content: [
{
elem: 'LabelHelper',
tag: 'label',
content: [
{
elem: 'Helper2'
},
{
elem: 'Helper3',
content: React.createElement(bemto('.class2'))
}
]
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('simple block with a Helper item before children with list context', () => {
testSnapshot(
bemto('ul.myListBlock', {
content: [
{
elem: 'Helper'
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('simple block with a Helper item before children with select context', () => {
testSnapshot(
bemto('select.mySelectBlock', {
content: [
{
elem: 'Helper'
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('simple block with a Helper item before children with nested list context', () => {
testSnapshot(
bemto('ul.myListBlock', {
content: [
{
elem: 'Helper',
content: { elem: 'Helper__Item' }
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('simple block with a Helper item before children with list nested in normal item', () => {
testSnapshot(
bemto('.myBlock', {
content: [
{
elem: 'myListHelper',
tag: 'ul',
content: { elem: 'myListHelper__Item' }
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('block with a complex elements structure', () => {
testSnapshot(
bemto('.myBlock', {
content: [
{
elem: 'Elem1',
tag: 'span',
content: [
{ elem: 'Elem1__Elem1' },
{
elem: 'Elem1__Elem2',
content: [
{ elem: 'Elem1__Elem2__Elem1' },
{ elem: 'Elem1__Elem2__Elem2' }
]
}
]
},
{
elem: 'Elem2',
tag: 'ul',
content: [
{ elem: 'Elem2__Elem1' },
{
elem: 'Elem2__Elem2',
content: [
{ elem: 'Elem2__Elem2__Elem1' },
{ elem: 'Elem2__Elem2__Elem2' }
]
}
]
},
{
elem: 'Elem3',
tag: 'span',
content: [
{ elem: 'Elem3__Elem1', tag: 'label' },
{
elem: 'Elem3__Elem2',
props: { href: '#x' },
content: [
{ elem: 'Elem3__Elem2__Elem1' },
{ elem: 'Elem3__Elem2__Elem2' }
]
}
]
},
{
children: true
}
]
}),
{},
'children text'
);
});
test('simple block with a Helper item before children using a button tag on parent', () => {

@@ -463,2 +866,4 @@ testSnapshot(

// FIXME: problem with array and keys =_= so this test gives a warning
/*
test('block with passing content to an element through props (passing a list of proper elements)', () => {

@@ -487,2 +892,3 @@ const MyBlock = bemto('.myBlock', {

});
*/

@@ -489,0 +895,0 @@ test('block with passing content to an element through props using an object', () => {

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc