diff --git a/packages/devui-vue/devui/ripple/__tests__/ripple.spec.ts b/packages/devui-vue/devui/ripple/__tests__/ripple.spec.ts deleted file mode 100644 index e4e3fddc36720ed65073df08ece25f0ff1a44128..0000000000000000000000000000000000000000 --- a/packages/devui-vue/devui/ripple/__tests__/ripple.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { nextTick, createApp } from 'vue' -import { mount } from '@vue/test-utils' -import Ripple from '../index' -import { DEFAULT_PLUGIN_OPTIONS } from '../src/options' -// 全局属性 -const global = { - directives: { - ripple: Ripple - } -} -describe('ripple', () => { - it('ripple should render correctly', async () => { - const wrapper = mount( - { - template: ` -
- ` - }, - { - global - } - ) - await nextTick() - const rippleElement = wrapper.find('.ripple-container') as any - await rippleElement.trigger('click') - console.log(rippleElement.element.childElementCount) - - expect(wrapper.find('div').exists()).toBeTruthy() - }) - it('test ripple plugin', () => { - const app = createApp({}).use(Ripple) - expect(app.directive('ripple', Ripple)).toBeTruthy() - }) - - it('ripple default options', () => { - expect(DEFAULT_PLUGIN_OPTIONS).toEqual({ - directive: 'ripple', - color: 'currentColor', - initialOpacity: 0.2, - finalOpacity: 0.1, - duration: 0.8, - easing: 'ease-out', - delayTime: 75, - disabled: false - }) - }) -}) diff --git a/packages/devui-vue/devui/ripple/src/options.ts b/packages/devui-vue/devui/ripple/src/options.ts deleted file mode 100644 index 18f04ca94b1129d11fc1ce9b0a8e14998c035e41..0000000000000000000000000000000000000000 --- a/packages/devui-vue/devui/ripple/src/options.ts +++ /dev/null @@ -1,95 +0,0 @@ -interface IRippleDirectiveOptions { - /** - * - * @remarks - * Y* 你可以设置 ·currentColor· to 能够自动使用元素的文本颜色 - * - * @default - * 'currentColor' - */ - color: string - /** - * 第一次出现的透明度 - * - * @default - * 0.2 默认opacity 0.2 - */ - initialOpacity: number - /** - * 在透明度 结束的时候 stopped 的时候 我们设置透明度的大小 - * - * @default - * 0.1 - */ - finalOpacity: number - /** - * 动画持续事件 - * - * @default - * 0.4 - */ - duration: number - /** - * css 动画 从开始到结束 以相同的时间来执行动画 - * - * @default - * 'ease-out' - */ - easing: string - /** - * 取消延迟时间 - * - * @note - * 类似于 debounceTime - * @default - * 75 - */ - delayTime: number - /** - * 禁止 水波 - * - * @note - * 类似于 debounceTime - * @default - * 75 - */ - disabled: boolean -} - -interface IRipplePluginOptions extends IRippleDirectiveOptions { - /** - * 用于覆盖指令的名称 - * - * @remarks - * - * @example - * - * @default - * 默认指令 ripple - */ - directive: string -} - -// 给可预见值 value 添加类型 - -interface IRippleDirectiveOptionWithBinding { - value: IRippleDirectiveOptions -} - -const DEFAULT_PLUGIN_OPTIONS: IRipplePluginOptions = { - directive: 'ripple', - color: 'currentColor', - initialOpacity: 0.2, - finalOpacity: 0.1, - duration: 0.8, - easing: 'ease-out', - delayTime: 75, - disabled: false -} - -export { - DEFAULT_PLUGIN_OPTIONS, - IRipplePluginOptions, - IRippleDirectiveOptions, - IRippleDirectiveOptionWithBinding -} diff --git a/packages/devui-vue/devui/ripple/src/ripple-directive.ts b/packages/devui-vue/devui/ripple/src/ripple-directive.ts deleted file mode 100644 index e4ad61ebdb798e5f20a38686cb883b4f0109e10e..0000000000000000000000000000000000000000 --- a/packages/devui-vue/devui/ripple/src/ripple-directive.ts +++ /dev/null @@ -1,30 +0,0 @@ -// can export function. 解构参数类型冗余 新定义insterface IRippleDirectiveOptionWithBinding -import { - DEFAULT_PLUGIN_OPTIONS, - IRippleDirectiveOptions, - IRippleDirectiveOptionWithBinding -} from './options' -import { ripple } from './v-ripple' -const optionMap = new WeakMap | false>() -const globalOptions = { ...DEFAULT_PLUGIN_OPTIONS } -export default { - mounted(el: HTMLElement, binding: IRippleDirectiveOptionWithBinding) { - optionMap.set(el, binding.value ?? {}) - - el.addEventListener('pointerdown', (event) => { - const options = optionMap.get(el) - // 必须确保disabled 属性存在 否则指令终止报错 - if (binding.value && binding.value.disabled) return - - if (options === false) return - - ripple(event, el, { - ...globalOptions, - ...options - }) - }) - }, - updated(el: HTMLElement, binding: IRippleDirectiveOptionWithBinding) { - optionMap.set(el, binding.value ?? {}) - } -} diff --git a/packages/devui-vue/devui/statistic/__tests__/statistic.spec.ts b/packages/devui-vue/devui/statistic/__tests__/statistic.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb9c9e8903d8935a48e5f2b5946f2dd62edeac24 --- /dev/null +++ b/packages/devui-vue/devui/statistic/__tests__/statistic.spec.ts @@ -0,0 +1,8 @@ +import { mount } from '@vue/test-utils' +import { Statistic } from '../index' + +describe('statistic test', () => { + it('statistic init render', async () => { + // todo + }) +}) diff --git a/packages/devui-vue/devui/statistic/index.ts b/packages/devui-vue/devui/statistic/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..116831ee7fa9e81dfad9691db365ec9d382c0145 --- /dev/null +++ b/packages/devui-vue/devui/statistic/index.ts @@ -0,0 +1,17 @@ +import type { App } from 'vue' +import Statistic from './src/statistic' + +Statistic.install = function(app: App): void { + app.component(Statistic.name, Statistic) +} + +export { Statistic } + +export default { + title: 'Statistic 统计数值', + category: '数据展示', + status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + app.use(Statistic as any) + } +} diff --git a/packages/devui-vue/devui/statistic/src/statistic-types.ts b/packages/devui-vue/devui/statistic/src/statistic-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..db27965c2a682798630622fedb5a1bf1ddc0d0b7 --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/statistic-types.ts @@ -0,0 +1,63 @@ +import type { PropType, ExtractPropTypes, CSSProperties } from 'vue' +import type { easingType } from './utils/animation' +export const statisticProps = { + title: { + type: String, + default: '' + }, + value: { + type: [Number, String] + }, + prefix: { + type: String + }, + suffix: { + type: String + }, + precision: { + type: Number + }, + groupSeparator: { + type: String, + default: ',' + }, + showGroupSeparator: { + type: Boolean, + default: false + }, + titleStyle: { + type: Object as PropType + }, + contentStyle: { + type: Object as PropType + }, + animationDuration: { + type: Number, + default: 2000 + }, + valueFrom: { + type: Number + }, + animation: { + type: Boolean, + default: false + }, + start: { + type: Boolean, + default: false + }, + extra: { + type: String, + default: '' + }, + easing: { + type: String as PropType, + default: 'easeOutCubic' + }, + delay: { + type: Number, + default: 0 + } +} as const + +export type StatisticProps = ExtractPropTypes diff --git a/packages/devui-vue/devui/statistic/src/statistic.scss b/packages/devui-vue/devui/statistic/src/statistic.scss new file mode 100644 index 0000000000000000000000000000000000000000..a619e6ce5c2c119d0cc0160ff82ad1796237c4b7 --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/statistic.scss @@ -0,0 +1,34 @@ +.devui-statistic { + box-sizing: border-box; + margin: 0; + padding: 0; + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5715; + list-style: none; + + &-title { + margin-bottom: 4 px; + opacity: 0.7; + font-size: 14px; + } + + &-content { + font-size: 24px; + display: flex; + align-items: center; + vertical-align: center; + } + + &-prefix { + margin-right: 6px; + } + + &-suffix { + margin-left: 6px; + } + + &--value { + display: inline-block; + } +} diff --git a/packages/devui-vue/devui/statistic/src/statistic.tsx b/packages/devui-vue/devui/statistic/src/statistic.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e57ebf1127858cfabba21b0e4b83618caba7cdbd --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/statistic.tsx @@ -0,0 +1,85 @@ +import { defineComponent, computed, ref, onMounted, watch } from 'vue' +import { statisticProps, StatisticProps } from './statistic-types' +import { analysisValueType } from './utils/separator' +import { Tween } from './utils/animation' +import './statistic.scss' + +export default defineComponent({ + name: 'DStatistic', + props: statisticProps, + inheritAttrs: false, + setup(props: StatisticProps, ctx) { + const innerValue = ref(props.valueFrom ?? props.value) + const tween = ref(null) + + const animation = ( + from: number = props.valueFrom ?? 0, + to: number = typeof props.value === 'number' ? props.value : Number(props.value) + ) => { + if (from !== to) { + tween.value = new Tween({ + from: { + value: from + }, + to: { + value: to + }, + delay: props.delay, + duration: props.animationDuration, + easing: props.easing, + onUpdate: (keys: any) => { + innerValue.value = keys.value + }, + onFinish: () => { + innerValue.value = to + } + }) + tween.value.start() + } + } + + const statisticValue = computed(() => { + return analysisValueType( + innerValue.value, + props.value, + props.groupSeparator, + props.precision, + props.showGroupSeparator, + props.animation + ) + }) + onMounted(() => { + if (props.animation && props.start) { + animation() + } + }) + + watch( + () => props.start, + (value) => { + if (value && !tween.value) { + animation() + } + } + ) + return () => { + return ( +
+
+ {ctx.slots.title?.() || props.title} +
+
+ {props.prefix || ctx.slots.prefix?.() ? ( + {ctx.slots.prefix?.() || props.prefix} + ) : null} + {statisticValue.value} + {props.suffix || ctx.slots.suffix?.() ? ( + {ctx.slots.suffix?.() || props.suffix} + ) : null} +
+ {ctx.slots.extra?.() || props.extra} +
+ ) + } + } +}) diff --git a/packages/devui-vue/devui/statistic/src/utils/animation.ts b/packages/devui-vue/devui/statistic/src/utils/animation.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b1ef6958715dbf7c28eb18dc17af1d584ee08f3 --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/utils/animation.ts @@ -0,0 +1,129 @@ +import * as easing from './easing' + +export type easingType = 'easeOutCubic' | 'linear' | 'easeOutExpo' | 'easeInOutExpo' +export interface startFunc { + (key: number): number +} +export interface updateFunc { + (key: any): any +} +export interface finishFunc { + (key: any): any +} +export interface fromType { + value: number +} +export interface toType { + value: number +} +export interface AnimationOptions { + from: fromType + to: toType + duration?: number + delay?: number + easing?: easingType + onStart?: startFunc + onUpdate?: updateFunc + onFinish?: finishFunc +} + +export class Tween { + from: fromType + to: toType + duration?: number + delay?: number + easing?: easingType + onStart?: startFunc + onUpdate?: updateFunc + onFinish?: finishFunc + startTime?: number + started?: boolean + finished?: boolean + timer?: null | number + time?: number + elapsed?: number + keys?: any + constructor(options: AnimationOptions) { + const { from, to, duration, delay, easing, onStart, onUpdate, onFinish } = options + for (const key in from) { + if (to[key] === undefined) { + to[key] = from[key] + } + } + + for (const key in to) { + if (from[key] === undefined) { + from[key] = to[key] + } + } + + this.from = from + this.to = to + this.duration = duration + this.delay = delay + this.easing = easing || 'linear' + this.onStart = onStart + this.onUpdate = onUpdate || function () {} + this.onFinish = onFinish + this.startTime = Date.now() + this.delay + this.started = false + this.finished = false + this.timer = null + this.keys = {} + } + + update() { + this.time = Date.now() + // delay some time + if (this.time < this.startTime) { + return + } + if (this.finished) { + return + } + // finish animation + if (this.elapsed === this.duration) { + if (!this.finished) { + this.finished = true + this.onFinish && this.onFinish(this.keys) + } + return + } + // elapsed 时间 和 duration 时间比较 逝去光阴 + this.elapsed = this.time - this.startTime + // 防止 时间 一直 流逝 ~ + this.elapsed = this.elapsed > this.duration ? this.duration : this.elapsed + // 从0 到 1 elapsed time + for (const key in this.to) { + this.keys[key] = + this.from[key] + + (this.to[key] - this.from[key]) * easing[this.easing](this.elapsed / this.duration) + } + if (!this.started) { + this.onStart && this.onStart(this.keys) + this.started = true + } + this.onUpdate(this.keys) + } + + // 递归 重绘 + start() { + this.startTime = Date.now() + this.delay + const tick = () => { + this.update() + this.timer = requestAnimationFrame(tick) + + if (this.finished) { + // 在判断 update中 结束后 停止 重绘 + cancelAnimationFrame(this.timer) + this.timer = null + } + } + tick() + } + + stop() { + cancelAnimationFrame(this.timer) + this.timer = null + } +} diff --git a/packages/devui-vue/devui/statistic/src/utils/easing.ts b/packages/devui-vue/devui/statistic/src/utils/easing.ts new file mode 100644 index 0000000000000000000000000000000000000000..6dfa4ca2f810be95f0928ea2360fae73bec1fdf0 --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/utils/easing.ts @@ -0,0 +1,27 @@ +// pow 返回 基数的指数次幂 t ** power +const pow = Math.pow +const sqrt = Math.sqrt + +export const easeOutCubic = function (x: number) { + return 1 - pow(1 - x, 3) +} +export const linear = (x) => x +export const easeOutExpo = function (x: number) { + return x === 1 ? 1 : 1 - pow(2, -10 * x) +} + +export const easeInOutExpo = function (x: number) { + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? pow(2, 20 * x - 10) / 2 + : (2 - pow(2, -20 * x + 10)) / 2 +} +export const easeInExpo = function (x: number) { + return x === 0 ? 0 : pow(2, 10 * x - 10) +} +export const easeInOutCirc = function (x: number) { + return x < 0.5 ? (1 - sqrt(1 - pow(2 * x, 2))) / 2 : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2 +} diff --git a/packages/devui-vue/devui/statistic/src/utils/separator.ts b/packages/devui-vue/devui/statistic/src/utils/separator.ts new file mode 100644 index 0000000000000000000000000000000000000000..91178bccbc0862d6ce8ff48467dff169505f75ec --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/utils/separator.ts @@ -0,0 +1,54 @@ +export type valueType = string | number + +export const separator = ( + SeparatorString: string, // value + groupSeparator: string, // 千分位分隔符 + showGroupSeparator: boolean // 是否展示千分位分隔符 +): string => { + const res = SeparatorString.replace(/\d+/, function (n) { + // 先提取整数部分 + return n.replace(/(\d)(?=(\d{3})+$)/g, function ($1) { + return $1 + `${showGroupSeparator ? groupSeparator : ''}` + }) + }) + return res +} + +export const isHasDot = (value: number): boolean => { + if (!isNaN(value)) { + return (value + '').indexOf('.') !== -1 + } +} +export const analysisValueType = ( + value: valueType, // 动态value 值 + propsValue: valueType, // 用户传入value + groupSeparator: string, // 千位分隔符 + splitPrecisionNumber: number, // 分割精度, 小数点 + showGroupSeparator: boolean // 是否展示千分位分隔符 +): string => { + const fixedNumber = + propsValue.toString().indexOf('.') !== -1 + ? propsValue.toString().length - propsValue.toString().indexOf('.') - 1 + : 0 + if (typeof value === 'number') { + if (isHasDot(value)) { + return splitPrecisionNumber + ? separator( + value.toFixed(splitPrecisionNumber).toString(), + groupSeparator, + showGroupSeparator + ) + : separator(value.toFixed(fixedNumber).toString(), groupSeparator, showGroupSeparator) + } else { + return splitPrecisionNumber + ? separator( + value.toFixed(splitPrecisionNumber).toString(), + groupSeparator, + showGroupSeparator + ) + : separator(value.toString(), groupSeparator, showGroupSeparator) + } + } else { + return value + } +} diff --git a/packages/devui-vue/docs/components/ripple/index.md b/packages/devui-vue/docs/components/ripple/index.md deleted file mode 100644 index 3212227f787c627f59fa0a6e6ceaa964fba1a6e6..0000000000000000000000000000000000000000 --- a/packages/devui-vue/docs/components/ripple/index.md +++ /dev/null @@ -1,219 +0,0 @@ -# Ripple 水波纹指令 - -`v-ripple` 指令 用于用户动作交互场景, 可以应用于任何块级元素 `注:只能作用于块级元素` - -### 使用 - -:::demo 用户 可以在组件 或者 HTML 元素上任意使用 `v-ripple` 指令 使用基本的 `v-ripple` 指令, `v-ripple` 接收 一个对象 - -```vue - - -``` - -::: - -### 自定义色彩 - -### 通过修改文本颜色来动态改变 - -:::demo - -```vue - -``` - -::: - - -### 应用于其他组件 - -Button 组件 - -:::demo - -```vue - -``` - -::: - -Card 组件 - -:::demo -```vue - - -``` - -::: - - - -### API - -| 参数 | 类型 | 默认 | 说明 | -| :-------------: | :-------: | :---------: | :-------------------------------- | -| color | `string` | `#00000050` | 可选,默认当前文本颜色 | -| initial-opacity | `number` | `0.1` | 可选,初始交互效果透明度大小 | -| final-opacity | `number` | `0.1` | 可选,结束交互效果长按透明度大小 | -| duration | `number` | `0.4s` | 可选,持续时间 | -| easing | `string` | `ease-out` | 可选,缓动动画 | -| delay-time | `number` | `75ms` | 可选,延迟 debouceTime 时间后调用 | -| disabled | `boolean` | `false` | 可选,禁止水波效果 | diff --git a/packages/devui-vue/docs/components/statistic/index.md b/packages/devui-vue/docs/components/statistic/index.md new file mode 100644 index 0000000000000000000000000000000000000000..2b8809b45ed7f39cc3269ef7a533005d88c88719 --- /dev/null +++ b/packages/devui-vue/docs/components/statistic/index.md @@ -0,0 +1,191 @@ +# Statistic 统计数值 + +### 何时使用 + +当需要展示带描述的统计类数据时使用 + +### 基本用法 + +:::demo + +```vue + +``` + +::: + +### 在卡片中使用 + +在卡片中展示统计数值。 +:::demo + +```vue + +``` + +::: + +### 数值动画 + +我们可以通过设置 animation 属性 开启数值动画。可以在页面加载时开始动画,也可以手动控制 +:::demo + +```vue + + +``` + +::: + +### 插槽的使用 + +前缀和后缀插槽 +:::demo + +```vue + +``` + +::: + +### d-statistic + +| 参数 | 类型 | 默认 | 说明 | +| ------------------ | ------------------ | -------- | ---------------- | +| title | `string \| v-slot` | - | 数值的标题 | +| extra | `string \| v-slot` | - | 额外内容 | +| value | `number \| string` | - | 数值内容 | +| group-separator | `string` | , | 设置千分位标识符 | +| precision | `number` | - | 设置数值精度 | +| suffix | `string \| v-slot` | - | 设置数值的后缀 | +| prefix | `string \| v-slot` | - | 设置数值的前缀 | +| title-style | `style` | - | 标题样式 | +| content-style | `style` | - | 内容样式 | +| animation-duration | `number` | 2000 | 动画持续时间 | +| delay | `number` | 0 | 延迟进行动画时间 | +| valueFrom | `number` | 0 | 动画初始值 | +| animation | `boolean` | false | 是否开启动画 | +| easing | `string` | quartOut | 数字动画效果 | +| start | `boolean` | false | 是否开始动画 | + +d-statistic 事件 + +| 事件 | 类型 | 说明 | 跳转 Demo | +| ---- | ---- | ---- | --------- | +| | | | | +| | | | | +| | | | | diff --git a/packages/devui-vue/docs/en-US/components/ripple/index.md b/packages/devui-vue/docs/en-US/components/ripple/index.md deleted file mode 100644 index 193770a83f7bcc9b8d7509d98ea9e39d14ebdca0..0000000000000000000000000000000000000000 --- a/packages/devui-vue/docs/en-US/components/ripple/index.md +++ /dev/null @@ -1,221 +0,0 @@ -# RippleDirective - -`v-ripple`The v-ripple directive is used to show action from a user. It can be applied to any block level element.`tips: It can be applied to any block level element.` - -### When to Use - -:::demo User can be use Basic ripple functionality can be enabled just by using v-ripple directive on a component or an HTML element `v-ripple`Basic ripple functionality `v-ripple` Directive `v-ripple` Accept an object - -```vue - - -``` - -::: - -### Custom color - -### Change the ripple color dynamically by changing the text color or the ripple color - -:::demo - -```vue - -``` - -::: - - -### Ripple in components - -### Some components provide the ripple prop that allows you to control the ripple effect. - -Button Component - -:::demo - -```vue - -``` - -::: - -Card Component - -:::demo -```vue - - -``` - -::: - - - -### API - -| 参数 | 类型 | 默认 | 说明 | -| :-------------: | :-------: | :---------: | :---------------------------------------------------------------------------- | -| color | `string` | `#00000050` | Choose Default current text color | -| initial-opacity | `number` | `0.1` | Choose Initial interaction Opacity size | -| final-opacity | `number` | `0.1` | Choose, end the interactive effect and press the Opacity size for a long time | -| duration | `number` | `0.4s` | Choose, duration | -| easing | `string` | `ease-out` | Choose, animation easing | -| delay-time | `number` | `75ms` | Choose, slow animation is delayed after debouceTime time. | -| disabled | `boolean` | `false` | Choose, disabled ripple effect | diff --git a/packages/devui-vue/docs/en-US/components/statistic/index.md b/packages/devui-vue/docs/en-US/components/statistic/index.md new file mode 100644 index 0000000000000000000000000000000000000000..5bc17b46ab39d261e7847c1e9bb3b5e2914694bd --- /dev/null +++ b/packages/devui-vue/docs/en-US/components/statistic/index.md @@ -0,0 +1,191 @@ +# Statistic + +### When to use + +Used when it is necessary to display statistical data with description + +### Basic Usage + +:::demo + +```vue + +``` + +::: + +### Use in card + +Display statistics in cards. +:::demo + +```vue + +``` + +::: + +### Numerical animation + +We can start numerical animation by setting the animation attribute. You can start the animation when the page loads, or you can control it manually +:::demo + +```vue + + +``` + +::: + +### Use of slots + +Prefix and suffix slots +:::demo + +```vue + +``` + +::: + +### d-statistic + +| 参数 | 类型 | 默认 | 说明 | +| ------------------ | ------------------ | -------- | ---------------------------- | +| title | `string \| v-slot` | - | Title of value | +| extra | `string \| v-slot` | - | Extra content | +| value | `number \| string` | - | Value content | +| group-separator | `string` | , | Set group-separator | +| precision | `number` | - | Set numeric precision | +| suffix | `string \| v-slot` | - | Sets the suffix of the value | +| prefix | `string \| v-slot` | - | Sets the prefix of the value | +| title-style | `style` | - | Title Style | +| content-style | `style` | - | Content style | +| animation-duration | `number` | 2000 | Animation duration | +| delay | `number` | 0 | Delay animation time | +| valueFrom | `number` | 0 | Animation initial value | +| animation | `boolean` | false | Turn on animation | +| easing | `string` | quartOut | Digital animation effect | +| start | `boolean` | false | Start animation | + +d-statistic 事件 + +| 事件 | 类型 | 说明 | 跳转 Demo | +| ---- | ---- | ---- | --------- | +| | | | | +| | | | | +| | | | |