Skip to content

Commit

Permalink
Merge pull request #22 from bem/feature/lists-as-boolean-modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
qfox committed Jul 24, 2020
2 parents 2f46ccb + bef3460 commit 0b137e2
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 16 deletions.
73 changes: 58 additions & 15 deletions src/BH.php
Expand Up @@ -48,6 +48,18 @@ class BH
protected $_optEscapeContent = false;
protected $_optNobaseMods = false;

/**
* Naming bits (delimiters) for blocks and elements, classic BEM by default (block__element_modifier).
* For Harry Roberts' BEM-style http://csswizardry.com/work/ (block__element--modifier)
* use `['mod' => '--']`.
* @var array
*/
protected $_optNaming = [
'elem' => '__',
'mod' => '_',
'val' => '_'
];

/**
* Флаг, включающий автоматическую систему поиска зацикливаний. Следует использовать в development-режиме,
* чтобы определять причины зацикливания.
Expand Down Expand Up @@ -130,6 +142,10 @@ public function setOptions($options)
$this->_optEscapeContent = $options['escapeContent'];
}

if (isset($options['naming'])) {
$this->_optNaming = $options['naming'] + $this->_optNaming;
}

return $this;
}

Expand Down Expand Up @@ -297,7 +313,7 @@ public function buildMatcher()
for ($i = sizeof($allMatchers) - 1; $i >= 0; $i--) {
$matcherInfo = $allMatchers[$i];
$decl = ['fn' => $matcherInfo['fn'], '__id' => $matcherInfo['__id'], 'index' => $i]
+ static::parseBemCssClasses($matcherInfo['expr']);
+ static::parseBemCssClasses($matcherInfo['expr'], $this->_optNaming);
$declarations[] = $decl;
}

Expand Down Expand Up @@ -643,11 +659,11 @@ public function _html($json)

if ($json->bem !== false) {
// hardcoded naming
$base = ($json->block ? $json->block : '') . ($json->elem ? '__' . $json->elem : '');
$base = ($json->block ? $json->block : '') . ($json->elem ? $this->_optNaming['elem'] . $json->elem : '');

$jsParams = false;
if ($json->block) {
$cls = static::toBemCssClasses($json, $base, null, $this->_optNobaseMods);
$cls = static::toBemCssClasses($json, $base, null, $this->_optNobaseMods, $this->_optNaming);
if ($json->js !== null && $json->js !== false) {
$jsParams = [];
$jsParams[$base] = $json->js === true ? [] : static::filterNulls($json->js);
Expand All @@ -672,7 +688,13 @@ public function _html($json)
$mixElem = $mix->elem ?: ($mix->block ? null : ($json->block ? $json->elem : null));
$mixBase = $mixBlock . ($mixElem ? '__' . $mixElem : '');

$cls .= static::toBemCssClasses($mix, $mixBase, $base, $this->_optNobaseMods);
$cls .= static::toBemCssClasses(
$mix,
$mixBase,
$base,
$this->_optNobaseMods,
$this->_optNaming
);
if ($mix->js !== null && $mix->js !== false) {
$jsParams = $jsParams ?: [];
$jsParams[$mixBase] = $mix->js === true ? [] : static::filterNulls($mix->js);
Expand Down Expand Up @@ -738,7 +760,7 @@ public static function attrEscape($s)
return htmlspecialchars($s, ENT_QUOTES);
}

public static function toBemCssClasses($json, $base, $parentBase = null, $nobase = false)
public static function toBemCssClasses($json, $base, $parentBase = null, $nobase = false, array $naming = null)
{
$res = '';

Expand All @@ -751,34 +773,55 @@ public static function toBemCssClasses($json, $base, $parentBase = null, $nobase

// if (mods = json.elem && json.elemMods || json.mods)
$mods = $json->elem && isset($json->elemMods) ? $json->elemMods : $json->mods;
$isList = isList($mods);
foreach ($mods as $k => $mod) {
if (!$mod && $mod !== 0) {
continue;
}
$res .= ' ' . ($nobase ? '' : $base) . '_' . $k . ($mod === true ? '' : '_' . $mod);
$res .= ' ' . ($nobase ? '' : $base) . $naming['mod'];
$res .= $isList
? $mod
: $k . ($mod === true ? '' : $naming['val'] . $mod);
}

return $res;
}

// @todo fixup hardcoded leveling
public static function parseBemCssClasses($expr)
public static function parseBemCssClasses($expr, array $naming = null)
{
list($blockBits, $elemBits) = explode('__', $expr . "__\1");
list($blockBits, $elemBits) = explode($naming['elem'], $expr . $naming['elem'] . "\1");

list($block, $blockMod, $blockModVal) = explode('_', $blockBits . "_\1_\1");
$blockMod = $blockMod === "\1" ? null : $blockMod;
$blockModVal = $blockMod ? ($blockModVal !== "\1" ? $blockModVal : true) : null;
list($block, $blockMod, $blockModVal) = self::parseBemCssBits($blockBits, $naming);

if ($elemBits !== "\1") {
list($elem, $elemMod, $elemModVal) = explode('_', $elemBits . "_\1_\1_\1");
$elemMod = $elemMod === "\1" ? null : $elemMod;
$elemModVal = $elemMod ? ($elemModVal !== "\1" ? $elemModVal : true) : null;
list($elem, $elemMod, $elemModVal) = self::parseBemCssBits($elemBits, $naming);
}

return compact('block', 'blockMod', 'blockModVal', 'elem', 'elemMod', 'elemModVal');
}

protected static function parseBemCssBits($expr, $naming)
{
$elem = $naming['elem'];
$mod = $naming['mod'];
$val = $naming['val'];

if ($mod === $val) {
list($ent, $entMod, $entModVal) = explode($mod, "${expr}${mod}\1${mod}\1");
} else {
list($ent, $entModBits) = explode($mod, "${expr}${mod}\1");
list($entMod, $entModVal) = explode($val, "${entModBits}${val}\1");
}

$entMod = $entMod === "\1" || empty($entMod) ? null : $entMod;
$entModVal = $entMod && !empty($entModVal) ?
($entModVal !== "\1" ? $entModVal : true)
: null;

return [$ent, $entMod, $entModVal];
}

/**
* Group up selectors by some key
* @param array $data
Expand All @@ -805,4 +848,4 @@ protected static function filterNulls($arr)
return $e !== null;
});
}
};
}
78 changes: 77 additions & 1 deletion tests/basic/bh-bemCssClasses.test.php
Expand Up @@ -6,14 +6,90 @@ class bh_bemCssClasses extends PHPUnit_Framework_TestCase
{
public function test_itShouldParseCssClasses()
{
$naming = ['elem' => '__', 'mod' => '_', 'val' => '_'];

$this->assertEquals(
[ 'block' => 'button',
'blockMod' => 'disabled',
'blockModVal' => true,
'elem' => 'control',
'elemMod' => null,
'elemModVal' => null],
BH::parseBemCssClasses('button_disabled__control')
BH::parseBemCssClasses('button_disabled__control', $naming)
);

$this->assertEquals(
[ 'block' => 'button',
'blockMod' => 'mod',
'blockModVal' => 'val',
'elem' => 'elem',
'elemMod' => 'modelem',
'elemModVal' => 'valelem'],
BH::parseBemCssClasses('button_mod_val__elem_modelem_valelem', $naming)
);

$this->assertEquals(
[ 'block' => 'button',
'blockMod' => 'disabled',
'blockModVal' => true,
'elem' => 'control',
'elemMod' => null,
'elemModVal' => null],
BH::parseBemCssClasses('button--disabled__control', ['mod' => '--'] + $naming)
);

$this->assertEquals(
[ 'block' => 'button',
'blockMod' => 'mod',
'blockModVal' => 'val',
'elem' => 'elem',
'elemMod' => 'modelem',
'elemModVal' => 'valelem'],
BH::parseBemCssClasses('button--mod_val__elem--modelem_valelem', ['mod' => '--'] + $naming)
);

$this->assertEquals(
[ 'block' => 'button',
'blockMod' => 'mod',
'blockModVal' => 'val',
'elem' => 'elem',
'elemMod' => 'modelem',
'elemModVal' => 'valelem'],
BH::parseBemCssClasses('button--mod--val__elem--modelem--valelem', ['mod' => '--', 'val' => '--'] + $naming)
);

$this->assertEquals(
[ 'block' => 'button',
'blockMod' => null,
'blockModVal' => null,],
BH::parseBemCssClasses('button', ['mod' => '--'] + $naming)
);
$this->assertEquals(
[ 'block' => 'button',
'blockMod' => null,
'blockModVal' => null,
'elem' => 'control',
'elemMod' => 'type',
'elemModVal' => 'span'],
BH::parseBemCssClasses('button__control_type_span', ['mod' => '_'] + $naming)
);
$this->assertEquals(
[ 'block' => 'button',
'blockMod' => null,
'blockModVal' => null,
'elem' => 'control',
'elemMod' => 'type',
'elemModVal' => true],
BH::parseBemCssClasses('button__control_type', $naming)
);
$this->assertEquals(
[ 'block' => 'button',
'blockMod' => null,
'blockModVal' => null,
'elem' => 'control',
'elemMod' => null,
'elemModVal' => null],
BH::parseBemCssClasses('button__control', $naming)
);
}
}

0 comments on commit 0b137e2

Please sign in to comment.