├── .babelrc ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierrc ├── README.md ├── __tests__ └── withBemMod.test.ts ├── docs ├── README.en.md └── README.ru.md ├── index.ts ├── jestconfig.json ├── package.json ├── rollup.config.js ├── src └── index.ts ├── tsconfig.json └── tslint.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets" : [ 3 | [ 4 | "@babel/preset-env" 5 | ] 6 | ], 7 | "plugins": ["@babel/plugin-proposal-export-default-from"] 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_Store 4 | package-lock.json 5 | *.log 6 | dist 7 | lib 8 | .vscode 9 | .rpt2_cache 10 | TODO.md 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | src 3 | tsconfig.json 4 | tslint.json 5 | .prettierrc 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | save=false 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all", 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bem-vue 2 | 3 | Vue.js version of package [@bem-react/core](https://github.com/bem/bem-react/). 4 | helps organize and manage components with [BEM modifiers](https://en.bem.info/methodology/key-concepts/#modifier) in Vue.js. 5 | 6 | Readme lang: 7 | - [Eng](https://github.com//sp1ker/bem-vue/blob/v0.1.8/docs/README.en.md) 8 | - [Rus](https://github.com//sp1ker/bem-vue/blob/v0.1.8/docs/README.ru.md) 9 | -------------------------------------------------------------------------------- /__tests__/withBemMod.test.ts: -------------------------------------------------------------------------------- 1 | import { cn, compose, withBemMod } from '@/index.ts'; 2 | import { ClassNameFormatter } from '@bem-react/classname'; 3 | import { mount } from '@vue/test-utils'; 4 | import Vue, { CreateElement } from 'vue'; 5 | 6 | describe('withBemMod', () => { 7 | const getPresenter = (cnPresenter: ClassNameFormatter) => { 8 | return Vue.extend({ 9 | name: 'Presenter', 10 | render(h: CreateElement) { 11 | return h('div', { class: cnPresenter() }, this.$slots.default); 12 | }, 13 | }); 14 | }; 15 | 16 | describe('has classes', () => { 17 | const cnPresenter = cn('Presenter'); 18 | 19 | const Presenter = getPresenter(cnPresenter); 20 | 21 | it('should has only Block class', () => { 22 | const Enhanced1 = withBemMod(cnPresenter(), { theme: 'normal' }); 23 | const ResultComponent = compose(Enhanced1)(Presenter); 24 | const componentWrapper = mount(ResultComponent); 25 | 26 | expect(componentWrapper.classes()).toEqual(['Presenter']); 27 | }); 28 | 29 | it('should add modifier class for theme prop with single modifier', () => { 30 | const Enhanced1 = withBemMod(cnPresenter(), { theme: 'normal' }); 31 | 32 | const ResultComponent = compose(Enhanced1)(Presenter); 33 | const componentWrapper = mount(ResultComponent, { 34 | propsData: { 35 | theme: 'normal', 36 | }, 37 | }); 38 | 39 | expect(componentWrapper.classes()).toEqual(['Presenter', 'Presenter_theme_normal']); 40 | }); 41 | 42 | it('should add modifier class for theme prop with multiple modifiers', () => { 43 | const Enhanced1 = withBemMod(cnPresenter(), { theme: 'normal' }); 44 | const Enhanced2 = withBemMod(cnPresenter(), { view: 'default' }); 45 | 46 | const ResultComponent = compose( 47 | Enhanced1, 48 | Enhanced2, 49 | )(Presenter); 50 | const componentWrapper = mount(ResultComponent, { 51 | propsData: { 52 | theme: 'normal', 53 | }, 54 | }); 55 | 56 | expect(componentWrapper.classes()).toEqual(['Presenter', 'Presenter_theme_normal']); 57 | }); 58 | 59 | it('should add modifier class for view prop', () => { 60 | const Enhanced1 = withBemMod(cnPresenter(), { theme: 'normal' }); 61 | const Enhanced2 = withBemMod(cnPresenter(), { view: 'default' }); 62 | 63 | const ResultComponent = compose( 64 | Enhanced1, 65 | Enhanced2, 66 | )(Presenter); 67 | 68 | const componentWrapper = mount(ResultComponent, { 69 | propsData: { 70 | view: 'default', 71 | }, 72 | }); 73 | 74 | expect(componentWrapper.classes()).toEqual(['Presenter', 'Presenter_view_default']); 75 | }); 76 | 77 | it('should add modifier class for view and theme props', () => { 78 | const Enhanced1 = withBemMod(cnPresenter(), { theme: 'normal' }); 79 | const Enhanced2 = withBemMod(cnPresenter(), { view: 'default' }); 80 | 81 | const ResultComponent = compose( 82 | Enhanced1, 83 | Enhanced2, 84 | )(Presenter); 85 | 86 | const componentWrapper = mount(ResultComponent, { 87 | propsData: { 88 | theme: 'normal', 89 | view: 'default', 90 | }, 91 | }); 92 | 93 | expect(componentWrapper.classes()).toEqual(['Presenter', 'Presenter_theme_normal', 'Presenter_view_default']); 94 | }); 95 | 96 | it('should not add modifier class for star matched prop', () => { 97 | const Enhanced1 = withBemMod(cnPresenter(), { star: '*' }); 98 | 99 | const ResultComponent = compose(Enhanced1)(Presenter); 100 | 101 | const componentWrapper = mount(ResultComponent, { 102 | propsData: { 103 | star: 'star value', 104 | }, 105 | }); 106 | 107 | expect(componentWrapper.classes()).toEqual(['Presenter']); 108 | }); 109 | 110 | it('should add boolean modifier class', () => { 111 | const Enhanced1 = withBemMod(cnPresenter(), { big: true }); 112 | 113 | const ResultComponent = compose(Enhanced1)(Presenter); 114 | 115 | const componentWrapper = mount(ResultComponent, { 116 | propsData: { 117 | big: true, 118 | }, 119 | }); 120 | 121 | expect(componentWrapper.classes()).toEqual(['Presenter', 'Presenter_big']); 122 | }); 123 | 124 | it('should mix classes', () => { 125 | const Enhanced1 = withBemMod(cnPresenter(), { modifier: true }); 126 | 127 | const ResultComponent = compose(Enhanced1)(Presenter); 128 | 129 | const PresenterWrapper = Vue.extend({ 130 | name: 'PresenterWrapper', 131 | render(h: CreateElement) { 132 | return h(ResultComponent, { 133 | class: { 'mix-class': true }, 134 | props: { 135 | modifier: true, 136 | }, 137 | }); 138 | }, 139 | }); 140 | 141 | const componentWrapper = mount(PresenterWrapper); 142 | 143 | expect(componentWrapper.classes()).toEqual(['Presenter', 'mix-class', 'Presenter_modifier']); 144 | }); 145 | }); 146 | 147 | describe('with enhancer', () => { 148 | const cnPresenter = cn('Presenter'); 149 | const Presenter = getPresenter(cnPresenter); 150 | 151 | const resultComponentFactory = () => { 152 | const Enhanced1 = withBemMod(cnPresenter(), { enhanced: true }, Component => { 153 | return { 154 | functional: true, 155 | render(h: CreateElement, context) { 156 | return h(Component, context.data, ['Enhanced text']); 157 | }, 158 | }; 159 | }); 160 | 161 | return compose(Enhanced1)(Presenter); 162 | }; 163 | 164 | it('should add enhancer', () => { 165 | const ResultComponent = resultComponentFactory(); 166 | const componentWrapper = mount(ResultComponent, { 167 | propsData: { 168 | enhanced: true, 169 | }, 170 | }); 171 | 172 | expect(componentWrapper.text()).toEqual('Enhanced text'); 173 | expect(componentWrapper.classes()).toEqual(['Presenter', 'Presenter_enhanced']); 174 | }); 175 | 176 | it('should not add enhancer', () => { 177 | const ResultComponent = resultComponentFactory(); 178 | const componentWrapper = mount(ResultComponent, { 179 | propsData: { 180 | enhanced: false, 181 | }, 182 | }); 183 | 184 | expect(componentWrapper.text()).toEqual(''); 185 | expect(componentWrapper.classes()).toEqual(['Presenter']); 186 | }); 187 | }); 188 | 189 | describe('with listener', () => { 190 | const cnPresenter = cn('Presenter'); 191 | const Presenter = getPresenter(cnPresenter); 192 | 193 | const resultComponentFactory = (state: any) => { 194 | const Enhanced1 = withBemMod(cnPresenter(), { enhanced: true }, Component => { 195 | return { 196 | functional: true, 197 | render(h: CreateElement, context) { 198 | return h( 199 | Component, 200 | { 201 | ...context.data, 202 | nativeOn: { 203 | click() { 204 | state.counter++; 205 | }, 206 | }, 207 | }, 208 | ['Enhanced text'], 209 | ); 210 | }, 211 | }; 212 | }); 213 | 214 | return compose(Enhanced1)(Presenter); 215 | }; 216 | 217 | it('should emit modifier native listener if turned on', () => { 218 | const state = Vue.observable({ 219 | counter: 0, 220 | }); 221 | 222 | const ResultComponent = resultComponentFactory(state); 223 | const componentWrapper = mount(ResultComponent, { 224 | propsData: { 225 | enhanced: true, 226 | }, 227 | }); 228 | 229 | componentWrapper.trigger('click'); 230 | 231 | expect(state.counter).toEqual(1); 232 | }); 233 | 234 | it('should emit modifier native listener if it turned off', () => { 235 | const state = Vue.observable({ 236 | counter: 0, 237 | }); 238 | 239 | const ResultComponent = resultComponentFactory(state); 240 | const componentWrapper = mount(ResultComponent, { 241 | propsData: { 242 | enhanced: false, 243 | }, 244 | }); 245 | 246 | componentWrapper.trigger('click'); 247 | 248 | expect(state.counter).toEqual(0); 249 | }); 250 | }); 251 | }); 252 | -------------------------------------------------------------------------------- /docs/README.en.md: -------------------------------------------------------------------------------- 1 | # bem-vue 2 | Vue.js version of package [@bem-react/core](https://github.com/bem/bem-react/). 3 | helps organize and manage components with [BEM modifiers](https://en.bem.info/methodology/key-concepts/#modifier) in Vue.js. 4 | 5 | ## Composition 6 | - `cn` - proxy of [@bem-react/classname](https://github.com/bem/bem-react/tree/master/packages/classname) package. 7 | - `withBemMod` - helping to create component variants. 8 | - `compose` - compose variants in one component. 9 | 10 | ## Install 11 | npm i bem-vue --save 12 | 13 | ## Usage 14 | 15 | For example, you have an initial App file structure as follows: 16 | ``` 17 | App.vue 18 | Components/ 19 | Button/ 20 | Button.vue 21 | ``` 22 | 23 | ### Step 1 24 | In `Components/Button/index.js` define the class space `Button`: 25 | ```js 26 | import { cn } from 'bem-vue'; 27 | 28 | export const cnButton = cn('Button'); 29 | ``` 30 | 31 | 32 | ### Step 2 33 | Create base variant of `Button` variant which will be rendered if no modifiers props are set in the parent component. Inside `Components/Button.vue`: 34 | ```vue 35 | 40 | 55 | ``` 56 | 57 | ### Step 3 58 | Set up the **optional** `withButtonTypeLink` and **optional** `withButtonThemeAction`, variants that will be rendered if `{type: 'link'}` and/or `{theme: 'action'}` modifiers are set in the parent component respectively. 59 | Inside your `Components/Button/` add folders `_type/` with `Button_type_link.js` and `_theme/` with `Button_theme_action.js`. 60 | ``` 61 | App.vue 62 | Components/ 63 | Button/ 64 | Button.vue 65 | index.js 66 | + _type/ 67 | + Button_type_link.js 68 | + _theme/ 69 | + Button_theme_action.js 70 | ``` 71 | 72 | Set up the variants: 73 | 74 | 1) In `Components/Button/_type/Button_type_link.js` 75 | ```js 76 | import { withBemMod } from 'bem-vue'; 77 | import { cnButton } from '../index'; 78 | 79 | export const withButtonTypeLink = withBemMod( 80 | cnButton(), 81 | {type: 'link'}, 82 | (Button) => { 83 | return { 84 | functional: true, 85 | render(h, context) { 86 | return h(Button, { 87 | ...context.data, 88 | props: {...context.props, tag: 'a'}, 89 | }, context.children); 90 | }, 91 | }; 92 | }, 93 | ); 94 | ``` 95 | 2) In `Components/Button/_type/Button_theme_action.js` 96 | ```js 97 | import { withBemMod } from 'bem-vue'; 98 | import { cnButton } from '../index'; 99 | 100 | export const withButtonThemeAction = withBemMod( 101 | cnButton(), 102 | {theme: 'action'}, 103 | ); 104 | ``` 105 | 106 | ### Step 4 107 | Finally, in `App.vue` you need compose only necessary the variants with the basic Button. Be careful with the import order - it directly affects your CSS rules. 108 | ```vue 109 | 124 | 141 | ``` 142 | -------------------------------------------------------------------------------- /docs/README.ru.md: -------------------------------------------------------------------------------- 1 | # bem-vue 2 | Адаптированная версия пакета [@bem-react/core](https://github.com/bem/bem-react/) для работы с vuejs. 3 | Помогает организовать и управлять компонентами с БЭМ модификаторами во Vue.js с помощью HOC. 4 | 5 | ## Состав пакета 6 | - `cn` - проброс библиотеки [@bem-react/classname](https://github.com/bem/bem-react/tree/master/packages/classname). Доукументация там же. 7 | - `withBemMod` - функция для создания компонентов-модификаторов. 8 | - `compose` - функция для композиции компонентов. 9 | 10 | ## Установка 11 | npm i bem-vue --save 12 | 13 | ## Пример использования 14 | 15 | Допустим, начальная структура приложения будет такой: 16 | ``` 17 | App.vue 18 | Components/ 19 | Button/ 20 | Button.vue 21 | ``` 22 | 23 | ### Шаг 1 24 | В `Components/Button/index.js` определим пространство `Button`: 25 | ```js 26 | import { cn } from 'bem-vue'; 27 | 28 | export const cnButton = cn('Button'); 29 | ``` 30 | 31 | 32 | ### Шаг 2 33 | Создадим базовый вариант `Button` без установленных от родителя модификаторов в файле `Components/Button.vue` 34 | 35 | ```vue 36 | 41 | 56 | ``` 57 | 58 | ### Шаг 3 59 | Добавим опционально варианты `withButtonTypeLink` и/или `withButtonThemeAction`, которые будут отрисовываться, когда в родительском компоненте будут переданы свойства `{type: 'link'}` и/или `{theme: 'action'}` соответственно. 60 | Внутри `Components/Button/` создадим папки `_type/` с файлом `Button_type_link.js` и `_theme/` с файлом `Button_theme_action.js`. 61 | ``` 62 | App.vue 63 | Components/ 64 | Button/ 65 | Button.vue 66 | index.js 67 | + _type/ 68 | + Button_type_link.js 69 | + _theme/ 70 | + Button_theme_action.js 71 | ``` 72 | Создадим модификаторы: 73 | 74 | 1) В `Components/Button/_type/Button_type_link.js` 75 | ```js 76 | import { withBemMod } from 'bem-vue'; 77 | import { cnButton } from '../index'; 78 | 79 | export const withButtonTypeLink = withBemMod( 80 | cnButton(), 81 | {type: 'link'}, 82 | (Button) => { 83 | return { 84 | functional: true, 85 | render(h, context) { 86 | return h(Button, { 87 | ...context.data, 88 | props: {...context.props, tag: 'a'}, 89 | }, context.children); 90 | }, 91 | }; 92 | }, 93 | ); 94 | ``` 95 | 2) В `Components/Button/_type/Button_theme_action.js` 96 | ```js 97 | import { withBemMod } from 'bem-vue'; 98 | import { cnButton } from '../index'; 99 | 100 | export const withButtonThemeAction = withBemMod( 101 | cnButton(), 102 | {theme: 'action'}, 103 | ); 104 | ``` 105 | 106 | ### Шаг 4 107 | Наконец, в `App.vue` мы можем добавить только необходимые модификаторы к нашей кнопке.\ 108 | Будьте осторожны с порядком импорта, он может влиять напрямую на правила CSS. 109 | ```vue 110 | 125 | 142 | ``` 143 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //TODO: dev or prod 4 | // ts to js 5 | import { compose as compose1, withBemMod as withBemMod1 } from './src'; 6 | 7 | export const compose = compose1; 8 | export const withBemMod = withBemMod1; 9 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": [ 3 | "js", 4 | "ts", 5 | "json", 6 | "vue" 7 | ], 8 | "moduleNameMapper": { 9 | "@/(.*)": "/src/$1" 10 | }, 11 | "transform": { 12 | "^.+\\.tsx?$": "ts-jest" 13 | }, 14 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$" 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sp1ker/bem-vue", 3 | "version": "1.0.0", 4 | "description": "Package helps organize and manage components with BEM modifiers in Vue.", 5 | "main": "lib/vue-bem.js", 6 | "umd:main": "lib/vue-bem.umd.js", 7 | "module": "lib/vue-bem.module.js", 8 | "types": "lib/vue-bem.d.ts", 9 | "files": [ 10 | "lib/" 11 | ], 12 | "publishConfig": { 13 | "registry": "https://npm.pkg.github.com/" 14 | }, 15 | "scripts": { 16 | "test": "jest --config jestconfig.json", 17 | "start": "cross-env TARGET=es rollup -c -w", 18 | "build": "rollup -c", 19 | "format": "prettier --write \"__tests__/**/*.ts\" \"src/**/*.ts\" \"src/**/*.js\"", 20 | "lint": "tslint -p tsconfig.json", 21 | "prepublishOnly": "npm test && npm run lint", 22 | "prepare": "npm run build", 23 | "preversion": "npm run lint", 24 | "pub": "npm publish" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/sp1ker/bem-vue.git" 29 | }, 30 | "keywords": [ 31 | "bem", 32 | "vue", 33 | "vuejs" 34 | ], 35 | "author": "Gavrilov Evgeniy", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/sp1ker/bem-vue/issues" 39 | }, 40 | "homepage": "https://github.com/sp1ker/bem-vue#readme", 41 | "dependencies": { 42 | "@bem-react/classname": "1.5.4" 43 | }, 44 | "peerDependencies": { 45 | "vue": "^2.6.10" 46 | }, 47 | "devDependencies": { 48 | "@babel/plugin-proposal-export-default-from": "^7.5.2", 49 | "@babel/preset-env": "^7.6.2", 50 | "@types/jasmine": "^3.4.0", 51 | "@vue/test-utils": "^1.0.0-beta.29", 52 | "babel-jest": "^24.9.0", 53 | "babel-preset-env": "^1.7.0", 54 | "jest": "^24.9.0", 55 | "prettier": "^1.18.2", 56 | "regenerator-runtime": "^0.13.3", 57 | "rollup": "^1.21.4", 58 | "rollup-plugin-commonjs": "^10.1.0", 59 | "rollup-plugin-node-resolve": "^5.2.0", 60 | "rollup-plugin-replace": "^2.2.0", 61 | "rollup-plugin-terser": "^5.1.2", 62 | "rollup-plugin-typescript2": "^0.24.2", 63 | "ts-jest": "^24.1.0", 64 | "tslib": "^1.10.0", 65 | "tslint": "^5.20.0", 66 | "tslint-config-prettier": "^1.18.0", 67 | "typescript": "^3.6.3", 68 | "vue": "^2.6.10", 69 | "vue-template-compiler": "^2.6.10" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import nodeResolve from 'rollup-plugin-node-resolve'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import resolve from 'rollup-plugin-node-resolve'; 6 | import { terser } from 'rollup-plugin-terser'; 7 | import replace from 'rollup-plugin-replace'; 8 | 9 | const builds = { 10 | 'cjs-dev': { 11 | outFile: 'bem-vue.js', 12 | format: 'cjs', 13 | mode: 'development', 14 | }, 15 | 'cjs-prod': { 16 | outFile: 'bem-vue.min.js', 17 | format: 'cjs', 18 | mode: 'production', 19 | }, 20 | 'umd-dev': { 21 | outFile: 'bem-vue.umd.js', 22 | format: 'umd', 23 | mode: 'development', 24 | }, 25 | 'umd-prod': { 26 | outFile: 'bem-vue.umd.min.js', 27 | format: 'umd', 28 | mode: 'production', 29 | }, 30 | es: { 31 | outFile: 'bem-vue.module.js', 32 | format: 'es', 33 | mode: 'development', 34 | }, 35 | }; 36 | 37 | function getAllBuilds() { 38 | return Object.keys(builds).map(key => genConfig(builds[key])); 39 | } 40 | 41 | function genConfig({ outFile, format, mode }) { 42 | const isProd = mode === 'production'; 43 | return { 44 | input: './src/index.ts', 45 | output: { 46 | file: path.join('./lib', outFile), 47 | format: format, 48 | globals: { 49 | vue: 'Vue', 50 | }, 51 | name: format === 'umd' ? 'bem-vue' : undefined, 52 | }, 53 | external: ['vue'], 54 | plugins: [ 55 | typescript({ 56 | typescript: require('typescript'), 57 | }), 58 | nodeResolve({ 59 | jsnext: true, 60 | main: true 61 | }), 62 | commonjs({ 63 | namedExports: { 64 | '@bem-react/classname': [ 'cn', 'withNaming' ] 65 | } 66 | }), 67 | resolve(), 68 | replace({ 'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development') }), 69 | isProd && terser(), 70 | ].filter(Boolean), 71 | }; 72 | } 73 | 74 | let buildConfig; 75 | 76 | if (process.env.TARGET) { 77 | buildConfig = genConfig(builds[process.env.TARGET]); 78 | } else { 79 | buildConfig = getAllBuilds(); 80 | } 81 | 82 | export default buildConfig; 83 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ClassNameFormatter, cn, NoStrictEntityMods } from '@bem-react/classname'; 2 | import vue, { ComponentOptions, CreateElement, FunctionalComponentOptions, RenderContext } from 'vue'; 3 | 4 | export * from '@bem-react/classname'; 5 | export type Enhance = ( 6 | WrappedComponent: ComponentOptions | FunctionalComponentOptions, 7 | ) => FunctionalComponentOptions; 8 | 9 | export function withBemMod(blockName: string, mod: NoStrictEntityMods, enhance?: Enhance) { 10 | return function WithBemMod(WrappedComponent: ComponentOptions | FunctionalComponentOptions) { 11 | let ModifiedComponent: ComponentOptions | FunctionalComponentOptions; 12 | 13 | return { 14 | functional: true, 15 | name: WrappedComponent.name, 16 | props: Object.keys(mod).reduce((mods: { [key: string]: {} }, modName: string) => { 17 | mods[modName] = {}; 18 | return mods; 19 | }, {}), 20 | render(h: CreateElement, context: RenderContext) { 21 | const entity: ClassNameFormatter = cn(blockName); 22 | const props = context.props; 23 | const isMatched = (key: string) => props[key] === mod[key]; 24 | const isStarMatched = (key: string) => mod[key] === '*' && Boolean(props[key]); 25 | 26 | if (Object.keys(mod).every(key => isMatched(key) || isStarMatched(key))) { 27 | const mods = Object.keys(mod).reduce((acc: any, key: string) => { 28 | if (mod[key] !== '*') { 29 | acc[key] = mod[key]; 30 | } 31 | 32 | return acc; 33 | }, {}); 34 | 35 | const modifierClassName = (entity(mods) + ' ').replace(`${entity()} `, '').trim(); 36 | 37 | context.data.class = { 38 | ...context.data.class, 39 | [modifierClassName]: true, 40 | }; 41 | 42 | if (enhance !== undefined) { 43 | if (ModifiedComponent === undefined) { 44 | ModifiedComponent = enhance(WrappedComponent); 45 | } 46 | } else { 47 | ModifiedComponent = WrappedComponent; 48 | } 49 | 50 | return h( 51 | ModifiedComponent, 52 | { 53 | ...context.data, 54 | props, 55 | }, 56 | context.children, 57 | ); 58 | } 59 | 60 | return h( 61 | WrappedComponent, 62 | { 63 | ...context.data, 64 | props, 65 | }, 66 | context.children, 67 | ); 68 | }, 69 | }; 70 | }; 71 | } 72 | 73 | export function compose(...funcs: any[]) { 74 | let mods = {}; 75 | 76 | return funcs.reduce( 77 | (a, b) => { 78 | return (component: any) => { 79 | mods = Object.assign({}, mods, component.hasOwnProperty('props') ? component.props : {}); 80 | component.props = mods; 81 | 82 | return a(b(component)); 83 | }; 84 | }, 85 | (arg: any) => { 86 | arg.props = Object.assign({}, arg.props ? arg.props : {}, mods); 87 | return arg; 88 | }, 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "strictNullChecks": true, 8 | "strictFunctionTypes": true, 9 | "noImplicitAny": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "importHelpers": true, 14 | "stripInternal": true, 15 | "downlevelIteration": true, 16 | "noUnusedLocals": true, 17 | "target": "es5", 18 | "esModuleInterop": true, 19 | "strict": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "noUnusedParameters": false, 22 | "outDir": "./lib", 23 | "lib": [ 24 | "esnext", 25 | "dom" 26 | ], 27 | "baseUrl": "src", 28 | "paths": { 29 | "@/*": [ 30 | "*" 31 | ] 32 | } 33 | }, 34 | "include": [ 35 | "./src" 36 | ], 37 | "exclude": [ 38 | "node_modules", 39 | "**/*.test.ts" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"] 3 | } 4 | --------------------------------------------------------------------------------