Comparing version 3.0.2 to 3.1.1
@@ -24,2 +24,4 @@ "use strict"; | ||
const E = Symbol('exclude'); | ||
const toDiv = props => (0, _react.createElement)('div', props); | ||
@@ -43,2 +45,4 @@ | ||
const matcher = {}; | ||
const groups = {}; | ||
const excludes = {}; | ||
let hasKey = false; | ||
@@ -52,15 +56,30 @@ | ||
case 'string': | ||
hasKey = true; | ||
if (options.groups && options.groups.test(k)) { | ||
const [, base, index] = k.split(options.groups); | ||
const group = groups[base] || (groups[base] = { | ||
[E]: new Set() | ||
}); | ||
excludes[k] = { | ||
value: v, | ||
set: group[E].add(v) | ||
}; | ||
group[index] = v; | ||
matcher[base] || (matcher[base] = key => group[key]); | ||
} | ||
flags[k] = v; | ||
hasKey = true; | ||
break; | ||
case 'function': | ||
hasKey = true; | ||
matcher[k] = v; | ||
hasKey = true; | ||
break; | ||
case 'object': | ||
matcher[k] = value => v[value]; | ||
hasKey = true; | ||
hasKey = true; | ||
matcher[k] = key => v[key]; | ||
break; | ||
@@ -74,7 +93,8 @@ | ||
return { | ||
matcher, | ||
flags, | ||
hasKey, | ||
consume: options.consume, | ||
baseClassName | ||
matcher, | ||
excludes, | ||
baseClassName, | ||
consume: options.consume | ||
}; | ||
@@ -88,3 +108,4 @@ }; | ||
consume, | ||
baseClassName | ||
baseClassName, | ||
excludes | ||
}, render) => { | ||
@@ -110,3 +131,14 @@ if (!hasKey) { | ||
const match = matcher[key]; | ||
const exclude = excludes[key]; | ||
if (exclude) { | ||
disabled || (disabled = new Set()); | ||
for (const excludedClassName of exclude.set) { | ||
disabled.add(excludedClassName); | ||
} | ||
disabled.delete(exclude.value); | ||
} | ||
if (match) { | ||
@@ -122,3 +154,2 @@ className = applyClassName(className, match(props[key])); | ||
if (className === props.className) return render(props); | ||
const newProps = {}; | ||
@@ -139,12 +170,14 @@ | ||
if (disabled) { | ||
const classSet = new Set(className.split(' ')); | ||
if (className) { | ||
if (disabled) { | ||
const classSet = new Set(className.split(' ')); | ||
for (const disabledClass of disabled) { | ||
classSet.delete(disabledClass); | ||
for (const disabledClass of disabled) { | ||
classSet.delete(disabledClass); | ||
} | ||
newProps.className = [...classSet].join(' '); | ||
} else { | ||
newProps.className = className; | ||
} | ||
newProps.className = [...classSet].join(' '); | ||
} else { | ||
newProps.className = className; | ||
} | ||
@@ -151,0 +184,0 @@ |
45
index.js
import React, { memo, createElement } from 'react' | ||
const E = Symbol('exclude') | ||
const toDiv = props => createElement('div', props) | ||
@@ -18,3 +19,6 @@ const applyClassName = (a, b) => (a && b ? `${a} ${b}` : b || a) | ||
const matcher = {} | ||
const groups = {} | ||
const excludes = {} | ||
let hasKey = false | ||
for (const [k, v] of Object.entries(options)) { | ||
@@ -25,12 +29,20 @@ if (k === 'className' || k === 'classNames' || k === 'consume') continue | ||
case 'string': | ||
hasKey = true | ||
if (options.groups && options.groups.test(k)) { | ||
const [, base, index] = k.split(options.groups) | ||
const group = groups[base] || (groups[base] = { [E]: new Set() }) | ||
excludes[k] = { value: v, set: group[E].add(v) } | ||
group[index] = v | ||
matcher[base] || (matcher[base] = key => group[key]) | ||
} | ||
flags[k] = v | ||
hasKey = true | ||
break | ||
case 'function': | ||
hasKey = true | ||
matcher[k] = v | ||
hasKey = true | ||
break | ||
case 'object': | ||
matcher[k] = value => v[value] | ||
hasKey = true | ||
matcher[k] = key => v[key] | ||
break | ||
@@ -41,6 +53,17 @@ default: | ||
} | ||
return { matcher, flags, hasKey, consume: options.consume, baseClassName } | ||
return { | ||
flags, | ||
hasKey, | ||
matcher, | ||
excludes, | ||
baseClassName, | ||
consume: options.consume, | ||
} | ||
} | ||
const dallas = ({ matcher, flags, hasKey, consume, baseClassName }, render) => { | ||
const dallas = ( | ||
{ matcher, flags, hasKey, consume, baseClassName, excludes }, | ||
render, | ||
) => { | ||
if (!hasKey) { | ||
@@ -68,2 +91,10 @@ // simple case, no flags or matcher specified | ||
const match = matcher[key] | ||
const exclude = excludes[key] | ||
if (exclude) { | ||
disabled || (disabled = new Set()) | ||
for (const excludedClassName of exclude.set) { | ||
disabled.add(excludedClassName) | ||
} | ||
disabled.delete(exclude.value) | ||
} | ||
if (match) { | ||
@@ -139,6 +170,6 @@ className = applyClassName(className, match(props[key])) | ||
{ ...opts, baseClassName: classes.slice(0, i).join(' ') }, | ||
props => createElement(nodeType, props) | ||
props => createElement(nodeType, props), | ||
) | ||
}, | ||
{ get: (_, key) => stepper([...classes, options[key] || key]) } | ||
{ get: (_, key) => stepper([...classes, options[key] || key]) }, | ||
) | ||
@@ -145,0 +176,0 @@ return stepper(baseClassName ? [baseClassName] : []) |
{ | ||
"name": "dallas", | ||
"version": "3.0.2", | ||
"version": "3.1.1", | ||
"description": "A wrapper for applying classes", | ||
@@ -25,2 +25,3 @@ "main": "index.build.js", | ||
"esm": "^3.0.84", | ||
"prettier": "^1.16.4", | ||
"react": "^16.7.0-alpha.0", | ||
@@ -27,0 +28,0 @@ "schwarzy": "0.0.3" |
@@ -110,2 +110,44 @@ # `dallas` | ||
#### option `consume (Boolean)` | ||
Passing the consume option to `true` will remove props that are used for flags. | ||
#### option `groups (RegExp)` | ||
A `RegExp` used to match exclusive props together | ||
```js | ||
export const Matcher = classe({ | ||
orange1: 'light-orange', | ||
orange2: 'orange', | ||
orange3: 'dark-orange', | ||
groups: /^([a-z]+)([0-9]+)$/i, | ||
}) | ||
// this JSX | ||
<Matcher orange="1" /> | ||
// render this dom | ||
<div class="light-orange" /> | ||
// this JSX | ||
<Matcher orange3 /> | ||
// render this dom | ||
<div class="dark-orange" /> | ||
// this JSX | ||
<Matcher orange={2} /> | ||
// render this dom | ||
<div class="orange" /> | ||
``` | ||
Groups are mutualy exclusives so once flags are matched together, only the last | ||
one will be applied: | ||
```js | ||
// this JSX | ||
<Matcher orange1 orange2 orange3 /> | ||
// render this dom | ||
<div class="dark-orange" /> | ||
``` | ||
### Refs | ||
@@ -112,0 +154,0 @@ |
@@ -21,2 +21,3 @@ "use strict"; | ||
disabled: 'is-disabled', | ||
active: 'is-active', | ||
color: { | ||
@@ -32,10 +33,5 @@ primary: 'color-blue', | ||
const applyDallas = (options, props) => (0, _indexBuild.classeNoMemo)(options, pass)(Object.freeze(props)); | ||
const Flags = (0, _indexBuild.wrapper)({ | ||
hidden: 'opacity-0', | ||
...allOptions | ||
}); | ||
const flagged = Flags.disabled.hidden(_ => _); | ||
/* APPLY CLASSNAME */ | ||
console.log('it should apply className'); | ||
@@ -130,7 +126,7 @@ (0, _assert.deepStrictEqual)(applyDallas({ | ||
}); | ||
console.log('it should not modify the object if no flags are applied'); | ||
console.log('it should return the same props if no flags are applied'); | ||
(0, _assert.deepStrictEqual)(applyDallas({ | ||
x: 'x', | ||
d: 'd' | ||
}, expected) === expected, true); | ||
}, expected), expected); | ||
/* TEST CONSUME OPTS*/ | ||
@@ -150,2 +146,10 @@ | ||
}); | ||
console.log('we should remove `false` flags on consume even with no classes'); | ||
(0, _assert.deepStrictEqual)(applyDallas({ | ||
x: 'x', | ||
d: 'd', | ||
consume: true | ||
}, { | ||
x: false | ||
}), {}); | ||
/* APPLY MATCHER */ | ||
@@ -169,3 +173,3 @@ | ||
console.log('it should not change the props when not matching'); | ||
(0, _assert.deepStrictEqual)(applyDallas(matcher, expected) === expected, true); | ||
(0, _assert.deepStrictEqual)(applyDallas(matcher, expected), expected); | ||
/* COMBINE ALL */ | ||
@@ -180,5 +184,10 @@ | ||
console.log('if should return the same props if no changes were nescessary'); | ||
(0, _assert.deepStrictEqual)(applyDallas(allOptions, expected) === expected, true); | ||
(0, _assert.deepStrictEqual)(applyDallas(allOptions, expected), expected); | ||
/* HANDLE WRAPPER FLAGS */ | ||
const Flags = (0, _indexBuild.wrapper)({ | ||
hidden: 'opacity-0', | ||
...allOptions | ||
}); | ||
const flagged = Flags.disabled.hidden(_ => _); | ||
console.log('A flagged component has default classes'); | ||
@@ -194,1 +203,23 @@ (0, _assert.deepStrictEqual)(flagged({}).className, 'hello world is-disabled opacity-0'); | ||
}).className, 'hello world opacity-0'); | ||
/* GROUPING */ | ||
const grouped = { | ||
mb1: 'margin_bottom_1', | ||
mb2: 'margin_bottom_2', | ||
mb3: 'margin_bottom_3', | ||
groups: /^([a-z]+)([0-9]+)$/i, | ||
consume: true | ||
}; | ||
console.log('grouped values should be mutualy exclusives'); | ||
(0, _assert.deepStrictEqual)(applyDallas(grouped, { | ||
mb1: true, | ||
mb2: true | ||
}), { | ||
className: grouped.mb2 | ||
}); | ||
console.log('grouped values can be set with index'); | ||
(0, _assert.deepStrictEqual)(applyDallas(grouped, { | ||
mb: 3 | ||
}), { | ||
className: grouped.mb3 | ||
}); |
66
test.js
@@ -11,7 +11,8 @@ import { deepStrictEqual as test } from 'assert' | ||
disabled: 'is-disabled', | ||
active: 'is-active', | ||
color: { | ||
primary: 'color-blue', | ||
warning: 'color-orange', | ||
error: 'color-red' | ||
} | ||
error: 'color-red', | ||
}, | ||
} | ||
@@ -22,13 +23,3 @@ | ||
classeNoMemo(options, pass)(Object.freeze(props)) | ||
const Flags = wrapper({ hidden: 'opacity-0', ...allOptions }) | ||
const flagged = Flags.disabled.hidden(_ => _) | ||
console.log('we should remove `false` flags on consume') | ||
test(applyDallas({ x: 'x', d: 'd', consume: true }, { x: false, d: true }), { | ||
className: 'd' | ||
}) | ||
console.log('we should remove `false` flags on consume even with no classes') | ||
test(applyDallas({ x: 'x', d: 'd', consume: true }, { x: false }), {}) | ||
/* APPLY CLASSNAME */ | ||
@@ -43,3 +34,3 @@ console.log('it should apply className') | ||
test(applyDallas('hello', { className: 'world' }), { | ||
className: 'world hello' | ||
className: 'world hello', | ||
}) | ||
@@ -56,3 +47,3 @@ | ||
test(applyDallas(classNames, { className: 'my' }), { | ||
className: 'my hello world' | ||
className: 'my hello world', | ||
}) | ||
@@ -71,15 +62,15 @@ | ||
d: true, | ||
className: 'x d' | ||
className: 'x d', | ||
}) | ||
console.log( | ||
'it should apply a single flag and concatenate with existing className' | ||
'it should apply a single flag and concatenate with existing className', | ||
) | ||
test(applyDallas({ x: 'x' }, { x: true, className: 'a' }), { | ||
x: true, | ||
className: 'a x' | ||
className: 'a x', | ||
}) | ||
console.log( | ||
'it should apply multiple flags and concatenate with existing className' | ||
'it should apply multiple flags and concatenate with existing className', | ||
) | ||
@@ -89,3 +80,3 @@ test(applyDallas({ x: 'x', d: 'd' }, { x: true, d: true, className: 'a' }), { | ||
d: true, | ||
className: 'a x d' | ||
className: 'a x d', | ||
}) | ||
@@ -96,3 +87,3 @@ | ||
d: true, | ||
className: 'a d' | ||
className: 'a d', | ||
}) | ||
@@ -108,7 +99,10 @@ | ||
{ x: 'x', d: 'd', consume: true }, | ||
{ x: true, d: true, className: 'a' } | ||
{ x: true, d: true, className: 'a' }, | ||
), | ||
{ className: 'a x d' } | ||
{ className: 'a x d' }, | ||
) | ||
console.log('we should remove `false` flags on consume even with no classes') | ||
test(applyDallas({ x: 'x', d: 'd', consume: true }, { x: false }), {}) | ||
/* APPLY MATCHER */ | ||
@@ -118,3 +112,3 @@ console.log('it should apply matcher') | ||
status: 'online', | ||
className: 'on' | ||
className: 'on', | ||
}) | ||
@@ -125,3 +119,3 @@ | ||
status: 'offline', | ||
className: 'a off' | ||
className: 'a off', | ||
}) | ||
@@ -138,5 +132,5 @@ | ||
disabled: true, | ||
color: 'primary' | ||
color: 'primary', | ||
}).className, | ||
'my-class hello world is-disabled color-blue' | ||
'my-class hello world is-disabled color-blue', | ||
) | ||
@@ -148,2 +142,5 @@ | ||
/* HANDLE WRAPPER FLAGS */ | ||
const Flags = wrapper({ hidden: 'opacity-0', ...allOptions }) | ||
const flagged = Flags.disabled.hidden(_ => _) | ||
console.log('A flagged component has default classes') | ||
@@ -155,3 +152,3 @@ test(flagged({}).className, 'hello world is-disabled opacity-0') | ||
flagged({ color: 'primary' }).className, | ||
'hello world is-disabled opacity-0 color-blue' | ||
'hello world is-disabled opacity-0 color-blue', | ||
) | ||
@@ -161,1 +158,16 @@ | ||
test(flagged({ disabled: false }).className, 'hello world opacity-0') | ||
/* GROUPING */ | ||
const grouped = { | ||
mb1: 'margin_bottom_1', | ||
mb2: 'margin_bottom_2', | ||
mb3: 'margin_bottom_3', | ||
groups: /^([a-z]+)([0-9]+)$/i, | ||
consume: true, | ||
} | ||
console.log('grouped values should be mutualy exclusives') | ||
test(applyDallas(grouped, { mb1: true, mb2: true }), { className: grouped.mb2 }) | ||
console.log('grouped values can be set with index') | ||
test(applyDallas(grouped, { mb: 3 }), { className: grouped.mb3 }) |
24977
672
160
4