cbvirtua 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/package.json +1 -1
  2. package/vant/style/README.md +79 -0
  3. package/vant/style/README.zh-CN.md +79 -0
  4. package/vant/style/animation.less +139 -0
  5. package/vant/style/base.less +10 -0
  6. package/vant/style/clearfix.less +5 -0
  7. package/vant/style/demo/index.vue +110 -0
  8. package/vant/style/ellipsis.less +13 -0
  9. package/vant/style/hairline.less +47 -0
  10. package/vant/style/mixins/clearfix.less +7 -0
  11. package/vant/style/mixins/ellipsis.less +15 -0
  12. package/vant/style/mixins/hairline.less +39 -0
  13. package/vant/style/normalize.less +38 -0
  14. package/vant/style/reset.less +171 -0
  15. package/vant/style/var.less +901 -0
  16. package/vant/tab/README.md +307 -0
  17. package/vant/tab/README.zh-CN.md +342 -0
  18. package/vant/tab/demo/index.vue +193 -0
  19. package/vant/tab/index.js +95 -0
  20. package/vant/tab/index.less +17 -0
  21. package/vant/tab/test/__snapshots__/demo.spec.js.snap +349 -0
  22. package/vant/tab/test/__snapshots__/index.spec.js.snap +352 -0
  23. package/vant/tab/test/__snapshots__/insert.spec.js.snap +63 -0
  24. package/vant/tab/test/demo.spec.js +4 -0
  25. package/vant/tab/test/index.spec.js +435 -0
  26. package/vant/tab/test/insert.spec.js +75 -0
  27. package/vant/tabs/Content.js +79 -0
  28. package/vant/tabs/Title.js +91 -0
  29. package/vant/tabs/index.js +453 -0
  30. package/vant/tabs/index.less +153 -0
  31. package/vant/tabs/utils.ts +53 -0
  32. package/vant/utils/constant.ts +11 -0
  33. package/vant/utils/create/bem.ts +45 -0
  34. package/vant/utils/create/component.ts +86 -0
  35. package/vant/utils/create/i18n.ts +16 -0
  36. package/vant/utils/create/index.ts +14 -0
  37. package/vant/utils/deep-assign.ts +27 -0
  38. package/vant/utils/deep-clone.ts +23 -0
  39. package/vant/utils/dom/event.ts +56 -0
  40. package/vant/utils/dom/node.ts +7 -0
  41. package/vant/utils/dom/raf.ts +40 -0
  42. package/vant/utils/dom/reset-scroll.ts +16 -0
  43. package/vant/utils/dom/scroll.ts +81 -0
  44. package/vant/utils/dom/style.ts +11 -0
  45. package/vant/utils/format/number.ts +52 -0
  46. package/vant/utils/format/string.ts +15 -0
  47. package/vant/utils/format/unit.ts +61 -0
  48. package/vant/utils/functional.ts +73 -0
  49. package/vant/utils/index.ts +79 -0
  50. package/vant/utils/interceptor.ts +27 -0
  51. package/vant/utils/router.ts +54 -0
  52. package/vant/utils/test/bem.spec.js +39 -0
  53. package/vant/utils/test/index.spec.js +152 -0
  54. package/vant/utils/test/interceptor.spec.js +50 -0
  55. package/vant/utils/types.ts +40 -0
  56. package/vant/utils/validate/date.ts +8 -0
  57. package/vant/utils/validate/email.ts +5 -0
  58. package/vant/utils/validate/mobile.ts +6 -0
  59. package/vant/utils/validate/number.ts +12 -0
  60. package/vant/utils/validate/system.ts +13 -0
  61. package/vant/utils/vnodes.ts +33 -0
  62. package/vue2/ex.vue +66 -0
  63. package/vue2/exx.vue +44 -0
@@ -0,0 +1,453 @@
1
+ // Utils
2
+ import { createNamespace, isDef, addUnit } from '../utils';
3
+ import { scrollLeftTo, scrollTopTo } from './utils';
4
+ import { route } from '../utils/router';
5
+ import { isHidden } from '../utils/dom/style';
6
+ import { on, off } from '../utils/dom/event';
7
+ import { unitToPx } from '../utils/format/unit';
8
+ import { BORDER_TOP_BOTTOM } from '../utils/constant';
9
+ import { callInterceptor } from '../utils/interceptor';
10
+ import {
11
+ getScroller,
12
+ getVisibleTop,
13
+ getElementTop,
14
+ getVisibleHeight,
15
+ setRootScrollTop,
16
+ } from '../utils/dom/scroll';
17
+
18
+ // Mixins
19
+ import { ParentMixin } from '../mixins/relation';
20
+ import { BindEventMixin } from '../mixins/bind-event';
21
+
22
+ // Components
23
+ import Title from './Title';
24
+ import Sticky from '../sticky';
25
+ import Content from './Content';
26
+
27
+ const [createComponent, bem] = createNamespace('tabs');
28
+
29
+ export default createComponent({
30
+ mixins: [
31
+ ParentMixin('vanTabs'),
32
+ BindEventMixin(function (bind) {
33
+ if (!this.scroller) {
34
+ this.scroller = getScroller(this.$el);
35
+ }
36
+
37
+ bind(window, 'resize', this.resize, true);
38
+
39
+ if (this.scrollspy) {
40
+ bind(this.scroller, 'scroll', this.onScroll, true);
41
+ }
42
+ }),
43
+ ],
44
+
45
+ inject: {
46
+ vanPopup: {
47
+ default: null,
48
+ },
49
+ },
50
+
51
+ model: {
52
+ prop: 'active',
53
+ },
54
+
55
+ props: {
56
+ color: String,
57
+ border: Boolean,
58
+ sticky: Boolean,
59
+ animated: Boolean,
60
+ swipeable: Boolean,
61
+ scrollspy: Boolean,
62
+ background: String,
63
+ lineWidth: [Number, String],
64
+ lineHeight: [Number, String],
65
+ beforeChange: Function,
66
+ titleActiveColor: String,
67
+ titleInactiveColor: String,
68
+ type: {
69
+ type: String,
70
+ default: 'line',
71
+ },
72
+ active: {
73
+ type: [Number, String],
74
+ default: 0,
75
+ },
76
+ ellipsis: {
77
+ type: Boolean,
78
+ default: true,
79
+ },
80
+ duration: {
81
+ type: [Number, String],
82
+ default: 0.3,
83
+ },
84
+ offsetTop: {
85
+ type: [Number, String],
86
+ default: 0,
87
+ },
88
+ lazyRender: {
89
+ type: Boolean,
90
+ default: true,
91
+ },
92
+ swipeThreshold: {
93
+ type: [Number, String],
94
+ default: 5,
95
+ },
96
+ },
97
+
98
+ data() {
99
+ return {
100
+ position: '',
101
+ currentIndex: null,
102
+ lineStyle: {
103
+ backgroundColor: this.color,
104
+ },
105
+ };
106
+ },
107
+
108
+ computed: {
109
+ // whether the nav is scrollable
110
+ scrollable() {
111
+ return this.children.length > this.swipeThreshold || !this.ellipsis;
112
+ },
113
+
114
+ navStyle() {
115
+ return {
116
+ borderColor: this.color,
117
+ background: this.background,
118
+ };
119
+ },
120
+
121
+ currentName() {
122
+ const activeTab = this.children[this.currentIndex];
123
+
124
+ if (activeTab) {
125
+ return activeTab.computedName;
126
+ }
127
+ },
128
+
129
+ offsetTopPx() {
130
+ return unitToPx(this.offsetTop);
131
+ },
132
+
133
+ scrollOffset() {
134
+ if (this.sticky) {
135
+ return this.offsetTopPx + this.tabHeight;
136
+ }
137
+ return 0;
138
+ },
139
+ },
140
+
141
+ watch: {
142
+ color: 'setLine',
143
+
144
+ active(name) {
145
+ if (name !== this.currentName) {
146
+ this.setCurrentIndexByName(name);
147
+ }
148
+ },
149
+
150
+ children() {
151
+ this.setCurrentIndexByName(this.active);
152
+ this.setLine();
153
+
154
+ this.$nextTick(() => {
155
+ this.scrollIntoView(true);
156
+ });
157
+ },
158
+
159
+ currentIndex() {
160
+ this.scrollIntoView();
161
+ this.setLine();
162
+
163
+ // scroll to correct position
164
+ if (this.stickyFixed && !this.scrollspy) {
165
+ setRootScrollTop(Math.ceil(getElementTop(this.$el) - this.offsetTopPx));
166
+ }
167
+ },
168
+
169
+ scrollspy(val) {
170
+ if (val) {
171
+ on(this.scroller, 'scroll', this.onScroll, true);
172
+ } else {
173
+ off(this.scroller, 'scroll', this.onScroll);
174
+ }
175
+ },
176
+ },
177
+
178
+ mounted() {
179
+ this.init();
180
+
181
+ // https://github.com/vant-ui/vant/issues/7959
182
+ if (this.vanPopup) {
183
+ this.vanPopup.onReopen(() => {
184
+ this.setLine();
185
+ });
186
+ }
187
+ },
188
+
189
+ activated() {
190
+ this.init();
191
+ this.setLine();
192
+ },
193
+
194
+ methods: {
195
+ // @exposed-api
196
+ resize() {
197
+ this.setLine();
198
+ },
199
+
200
+ init() {
201
+ this.$nextTick(() => {
202
+ this.inited = true;
203
+ this.tabHeight = getVisibleHeight(this.$refs.wrap);
204
+ this.scrollIntoView(true);
205
+ });
206
+ },
207
+
208
+ // update nav bar style
209
+ setLine() {
210
+ const shouldAnimate = this.inited;
211
+
212
+ this.$nextTick(() => {
213
+ const { titles } = this.$refs;
214
+
215
+ if (
216
+ !titles ||
217
+ !titles[this.currentIndex] ||
218
+ this.type !== 'line' ||
219
+ isHidden(this.$el)
220
+ ) {
221
+ return;
222
+ }
223
+
224
+ const title = titles[this.currentIndex].$el;
225
+ const { lineWidth, lineHeight } = this;
226
+ const left = title.offsetLeft + title.offsetWidth / 2;
227
+
228
+ const lineStyle = {
229
+ width: addUnit(lineWidth),
230
+ backgroundColor: this.color,
231
+ transform: `translateX(${left}px) translateX(-50%)`,
232
+ };
233
+
234
+ if (shouldAnimate) {
235
+ lineStyle.transitionDuration = `${this.duration}s`;
236
+ }
237
+
238
+ if (isDef(lineHeight)) {
239
+ const height = addUnit(lineHeight);
240
+ lineStyle.height = height;
241
+ lineStyle.borderRadius = height;
242
+ }
243
+
244
+ this.lineStyle = lineStyle;
245
+ });
246
+ },
247
+
248
+ // correct the index of active tab
249
+ setCurrentIndexByName(name) {
250
+ const matched = this.children.filter((tab) => tab.computedName === name);
251
+ const defaultIndex = (this.children[0] || {}).index || 0;
252
+ this.setCurrentIndex(matched.length ? matched[0].index : defaultIndex);
253
+ },
254
+
255
+ setCurrentIndex(currentIndex) {
256
+ const newIndex = this.findAvailableTab(currentIndex);
257
+
258
+ if (!isDef(newIndex)) {
259
+ return;
260
+ }
261
+
262
+ const newTab = this.children[newIndex];
263
+ const newName = newTab.computedName;
264
+ const shouldEmitChange = this.currentIndex !== null;
265
+
266
+ this.currentIndex = newIndex;
267
+
268
+ if (newName !== this.active) {
269
+ this.$emit('input', newName);
270
+
271
+ if (shouldEmitChange) {
272
+ this.$emit('change', newName, newTab.title);
273
+ }
274
+ }
275
+ },
276
+
277
+ findAvailableTab(index) {
278
+ const diff = index < this.currentIndex ? -1 : 1;
279
+
280
+ while (index >= 0 && index < this.children.length) {
281
+ if (!this.children[index].disabled) {
282
+ return index;
283
+ }
284
+
285
+ index += diff;
286
+ }
287
+ },
288
+
289
+ // emit event when clicked
290
+ onClick(item, index) {
291
+ const { title, disabled, computedName } = this.children[index];
292
+ if (disabled) {
293
+ this.$emit('disabled', computedName, title);
294
+ } else {
295
+ callInterceptor({
296
+ interceptor: this.beforeChange,
297
+ args: [computedName],
298
+ done: () => {
299
+ this.setCurrentIndex(index);
300
+ this.scrollToCurrentContent();
301
+ },
302
+ });
303
+
304
+ this.$emit('click', computedName, title);
305
+ route(item.$router, item);
306
+ }
307
+ },
308
+
309
+ // scroll active tab into view
310
+ scrollIntoView(immediate) {
311
+ const { titles } = this.$refs;
312
+
313
+ if (!this.scrollable || !titles || !titles[this.currentIndex]) {
314
+ return;
315
+ }
316
+
317
+ const { nav } = this.$refs;
318
+ const title = titles[this.currentIndex].$el;
319
+ const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
320
+
321
+ scrollLeftTo(nav, to, immediate ? 0 : +this.duration);
322
+ },
323
+
324
+ onSticktScroll(params) {
325
+ this.stickyFixed = params.isFixed;
326
+ this.$emit('scroll', params);
327
+ },
328
+
329
+ // @exposed-api
330
+ scrollTo(name) {
331
+ this.$nextTick(() => {
332
+ this.setCurrentIndexByName(name);
333
+ this.scrollToCurrentContent(true);
334
+ });
335
+ },
336
+
337
+ scrollToCurrentContent(immediate = false) {
338
+ if (this.scrollspy) {
339
+ const target = this.children[this.currentIndex];
340
+ const el = target?.$el;
341
+
342
+ if (el) {
343
+ const to = getElementTop(el, this.scroller) - this.scrollOffset;
344
+
345
+ this.lockScroll = true;
346
+ scrollTopTo(this.scroller, to, immediate ? 0 : +this.duration, () => {
347
+ this.lockScroll = false;
348
+ });
349
+ }
350
+ }
351
+ },
352
+
353
+ onScroll() {
354
+ if (this.scrollspy && !this.lockScroll) {
355
+ const index = this.getCurrentIndexOnScroll();
356
+ this.setCurrentIndex(index);
357
+ }
358
+ },
359
+
360
+ getCurrentIndexOnScroll() {
361
+ const { children } = this;
362
+
363
+ for (let index = 0; index < children.length; index++) {
364
+ const top = getVisibleTop(children[index].$el);
365
+
366
+ if (top > this.scrollOffset) {
367
+ return index === 0 ? 0 : index - 1;
368
+ }
369
+ }
370
+
371
+ return children.length - 1;
372
+ },
373
+ },
374
+
375
+ render() {
376
+ const { type, animated, scrollable } = this;
377
+
378
+ const Nav = this.children.map((item, index) => (
379
+ <Title
380
+ ref="titles"
381
+ refInFor
382
+ type={type}
383
+ dot={item.dot}
384
+ info={item.badge ?? item.info}
385
+ title={item.title}
386
+ color={this.color}
387
+ style={item.titleStyle}
388
+ class={item.titleClass}
389
+ isActive={index === this.currentIndex}
390
+ disabled={item.disabled}
391
+ scrollable={scrollable}
392
+ activeColor={this.titleActiveColor}
393
+ inactiveColor={this.titleInactiveColor}
394
+ scopedSlots={{
395
+ default: () => item.slots('title'),
396
+ }}
397
+ onClick={() => {
398
+ this.onClick(item, index);
399
+ }}
400
+ />
401
+ ));
402
+
403
+ const Wrap = (
404
+ <div
405
+ ref="wrap"
406
+ class={[
407
+ bem('wrap', { scrollable }),
408
+ { [BORDER_TOP_BOTTOM]: type === 'line' && this.border },
409
+ ]}
410
+ >
411
+ <div
412
+ ref="nav"
413
+ role="tablist"
414
+ class={bem('nav', [type, { complete: this.scrollable }])}
415
+ style={this.navStyle}
416
+ >
417
+ {this.slots('nav-left')}
418
+ {Nav}
419
+ {type === 'line' && (
420
+ <div class={bem('line')} style={this.lineStyle} />
421
+ )}
422
+ {this.slots('nav-right')}
423
+ </div>
424
+ </div>
425
+ );
426
+
427
+ return (
428
+ <div class={bem([type])}>
429
+ {this.sticky ? (
430
+ <Sticky
431
+ container={this.$el}
432
+ offsetTop={this.offsetTop}
433
+ onScroll={this.onSticktScroll}
434
+ >
435
+ {Wrap}
436
+ </Sticky>
437
+ ) : (
438
+ Wrap
439
+ )}
440
+ <Content
441
+ count={this.children.length}
442
+ animated={animated}
443
+ duration={this.duration}
444
+ swipeable={this.swipeable}
445
+ currentIndex={this.currentIndex}
446
+ onChange={this.setCurrentIndex}
447
+ >
448
+ {this.slots()}
449
+ </Content>
450
+ </div>
451
+ );
452
+ },
453
+ });
@@ -0,0 +1,153 @@
1
+ @import '../style/var';
2
+
3
+ .van-tab {
4
+ position: relative;
5
+ display: flex;
6
+ flex: 1;
7
+ align-items: center;
8
+ justify-content: center;
9
+ box-sizing: border-box;
10
+ padding: 0 @padding-base;
11
+ color: @tab-text-color;
12
+ font-size: @tab-font-size;
13
+ line-height: @tab-line-height;
14
+ cursor: pointer;
15
+
16
+ &--active {
17
+ color: @tab-active-text-color;
18
+ font-weight: @font-weight-bold;
19
+ }
20
+
21
+ &--disabled {
22
+ color: @tab-disabled-text-color;
23
+ cursor: not-allowed;
24
+ }
25
+
26
+ &__text {
27
+ &--ellipsis {
28
+ display: -webkit-box;
29
+ overflow: hidden;
30
+ -webkit-line-clamp: 1;
31
+ -webkit-box-orient: vertical;
32
+ }
33
+ }
34
+
35
+ &__text-wrapper {
36
+ position: relative;
37
+ }
38
+ }
39
+
40
+ .van-tabs {
41
+ position: relative;
42
+
43
+ &__wrap {
44
+ overflow: hidden;
45
+
46
+ &--page-top {
47
+ position: fixed;
48
+ }
49
+
50
+ &--content-bottom {
51
+ top: auto;
52
+ bottom: 0;
53
+ }
54
+
55
+ &--scrollable {
56
+ .van-tab {
57
+ flex: 1 0 auto;
58
+ padding: 0 @padding-sm;
59
+ }
60
+
61
+ .van-tabs__nav {
62
+ overflow-x: auto;
63
+ overflow-y: hidden;
64
+ -webkit-overflow-scrolling: touch;
65
+
66
+ &::-webkit-scrollbar {
67
+ display: none;
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ &__nav {
74
+ position: relative;
75
+ display: flex;
76
+ background-color: @tabs-nav-background-color;
77
+ user-select: none;
78
+
79
+ &--line {
80
+ box-sizing: content-box;
81
+ height: 100%;
82
+ padding-bottom: 15px; /* 15px padding to hide scrollbar in mobile safari */
83
+ }
84
+
85
+ &--line&--complete {
86
+ padding-right: @padding-xs;
87
+ padding-left: @padding-xs;
88
+ }
89
+
90
+ &--card {
91
+ box-sizing: border-box;
92
+ height: @tabs-card-height;
93
+ margin: 0 @padding-md;
94
+ border: @border-width-base solid @tabs-default-color;
95
+ border-radius: @border-radius-sm;
96
+
97
+ .van-tab {
98
+ color: @tabs-default-color;
99
+ border-right: @border-width-base solid @tabs-default-color;
100
+
101
+ &:last-child {
102
+ border-right: none;
103
+ }
104
+
105
+ &.van-tab--active {
106
+ color: @white;
107
+ background-color: @tabs-default-color;
108
+ }
109
+
110
+ &--disabled {
111
+ color: @tab-disabled-text-color;
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ &__line {
118
+ position: absolute;
119
+ bottom: 15px;
120
+ left: 0;
121
+ z-index: 1;
122
+ width: @tabs-bottom-bar-width;
123
+ height: @tabs-bottom-bar-height;
124
+ background-color: @tabs-bottom-bar-color;
125
+ border-radius: @tabs-bottom-bar-height;
126
+ }
127
+
128
+ &__track {
129
+ position: relative;
130
+ display: flex;
131
+ width: 100%;
132
+ height: 100%;
133
+ will-change: left;
134
+ }
135
+
136
+ &__content {
137
+ &--animated {
138
+ overflow: hidden;
139
+ }
140
+ }
141
+
142
+ &--line {
143
+ .van-tabs__wrap {
144
+ height: @tabs-line-height;
145
+ }
146
+ }
147
+
148
+ &--card {
149
+ > .van-tabs__wrap {
150
+ height: @tabs-card-height;
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,53 @@
1
+ import { raf } from '../utils/dom/raf';
2
+ import { getScrollTop, setScrollTop } from '../utils/dom/scroll';
3
+
4
+ export function scrollLeftTo(
5
+ scroller: HTMLElement,
6
+ to: number,
7
+ duration: number
8
+ ) {
9
+ let count = 0;
10
+ const from = scroller.scrollLeft;
11
+ const frames = duration === 0 ? 1 : Math.round((duration * 1000) / 16);
12
+
13
+ function animate() {
14
+ scroller.scrollLeft += (to - from) / frames;
15
+
16
+ if (++count < frames) {
17
+ raf(animate);
18
+ }
19
+ }
20
+
21
+ animate();
22
+ }
23
+
24
+ export function scrollTopTo(
25
+ scroller: HTMLElement,
26
+ to: number,
27
+ duration: number,
28
+ callback: Function
29
+ ) {
30
+ let current = getScrollTop(scroller);
31
+
32
+ const isDown = current < to;
33
+ const frames = duration === 0 ? 1 : Math.round((duration * 1000) / 16);
34
+ const step = (to - current) / frames;
35
+
36
+ function animate() {
37
+ current += step;
38
+
39
+ if ((isDown && current > to) || (!isDown && current < to)) {
40
+ current = to;
41
+ }
42
+
43
+ setScrollTop(scroller, current);
44
+
45
+ if ((isDown && current < to) || (!isDown && current > to)) {
46
+ raf(animate);
47
+ } else if (callback) {
48
+ raf(callback as FrameRequestCallback);
49
+ }
50
+ }
51
+
52
+ animate();
53
+ }
@@ -0,0 +1,11 @@
1
+ // color
2
+ export const RED = '#ee0a24';
3
+
4
+ // border
5
+ export const BORDER = 'van-hairline';
6
+ export const BORDER_TOP = `${BORDER}--top`;
7
+ export const BORDER_LEFT = `${BORDER}--left`;
8
+ export const BORDER_BOTTOM = `${BORDER}--bottom`;
9
+ export const BORDER_SURROUND = `${BORDER}--surround`;
10
+ export const BORDER_TOP_BOTTOM = `${BORDER}--top-bottom`;
11
+ export const BORDER_UNSET_TOP_BOTTOM = `${BORDER}-unset--top-bottom`;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * bem helper
3
+ * b() // 'button'
4
+ * b('text') // 'button__text'
5
+ * b({ disabled }) // 'button button--disabled'
6
+ * b('text', { disabled }) // 'button__text button__text--disabled'
7
+ * b(['disabled', 'primary']) // 'button button--disabled button--primary'
8
+ */
9
+
10
+ export type Mod = string | { [key: string]: any };
11
+ export type Mods = Mod | Mod[];
12
+
13
+ function gen(name: string, mods?: Mods): string {
14
+ if (!mods) {
15
+ return '';
16
+ }
17
+
18
+ if (typeof mods === 'string') {
19
+ return ` ${name}--${mods}`;
20
+ }
21
+
22
+ if (Array.isArray(mods)) {
23
+ return mods.reduce<string>((ret, item) => ret + gen(name, item), '');
24
+ }
25
+
26
+ return Object.keys(mods).reduce(
27
+ (ret, key) => ret + (mods[key] ? gen(name, key) : ''),
28
+ ''
29
+ );
30
+ }
31
+
32
+ export function createBEM(name: string) {
33
+ return function (el?: Mods, mods?: Mods): Mods {
34
+ if (el && typeof el !== 'string') {
35
+ mods = el;
36
+ el = '';
37
+ }
38
+
39
+ el = el ? `${name}__${el}` : name;
40
+
41
+ return `${el}${gen(el, mods)}`;
42
+ };
43
+ }
44
+
45
+ export type BEM = ReturnType<typeof createBEM>;