@eturnity/eturnity_reusable_components 7.45.5-qa-elisee-7.48.0 → 7.45.5-qa-16-11.13.1
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.
- package/package.json +1 -1
- package/src/components/icon/index.vue +3 -13
- package/src/components/inputs/inputNumber/index.vue +63 -77
- package/src/components/inputs/select/index.vue +57 -5
- package/src/components/modals/modal/index.vue +27 -27
- package/src/helpers/numberConverter.js +1 -1
- package/src/components/icon/icon.spec.js +0 -66
package/package.json
CHANGED
@@ -1,24 +1,14 @@
|
|
1
1
|
<template>
|
2
|
-
<Wrapper
|
3
|
-
:cursor="cursor"
|
4
|
-
data-test-id="icon_wrapper"
|
5
|
-
:disabled="disabled"
|
6
|
-
:size="size"
|
7
|
-
>
|
2
|
+
<Wrapper :cursor="cursor" :disabled="disabled" :size="size">
|
8
3
|
<IconImage
|
9
4
|
:animation="animation"
|
10
5
|
:background-color="backgroundColor"
|
11
6
|
:color="color"
|
12
|
-
data-test-id="icon_image"
|
13
7
|
:hovered-color="hoveredColor"
|
14
8
|
>
|
15
|
-
<i
|
9
|
+
<i v-html="icon.html"></i>
|
16
10
|
</IconImage>
|
17
|
-
<StrikedLine
|
18
|
-
v-if="isStriked"
|
19
|
-
:color="color"
|
20
|
-
data-test-id="icon_striked_line"
|
21
|
-
/>
|
11
|
+
<StrikedLine v-if="isStriked" :color="color" />
|
22
12
|
</Wrapper>
|
23
13
|
</template>
|
24
14
|
|
@@ -47,7 +47,7 @@
|
|
47
47
|
:show-linear-unit-name="showLinearUnitName"
|
48
48
|
:slot-size="slotSize"
|
49
49
|
:text-align="textAlign"
|
50
|
-
:value="
|
50
|
+
:value="formattedValue"
|
51
51
|
@blur="onInputBlur($event)"
|
52
52
|
@focus="focusInput()"
|
53
53
|
@input="onInput($event)"
|
@@ -59,7 +59,7 @@
|
|
59
59
|
|
60
60
|
<UnitContainer
|
61
61
|
v-if="unitName && showLinearUnitName && !hasSlot"
|
62
|
-
:has-length="
|
62
|
+
:has-length="hasLength"
|
63
63
|
:is-error="isError"
|
64
64
|
>{{ unitName }}</UnitContainer
|
65
65
|
>
|
@@ -76,7 +76,7 @@
|
|
76
76
|
:disabled="isSelectDisabled"
|
77
77
|
:select-width="`${selectWidth}px`"
|
78
78
|
:show-border="false"
|
79
|
-
@input-change="
|
79
|
+
@input-change="handleSelectChange"
|
80
80
|
>
|
81
81
|
<template #selector>
|
82
82
|
<SelectText>{{ getSelectValue }}</SelectText>
|
@@ -339,8 +339,18 @@
|
|
339
339
|
background-color: ${({ theme }) => theme.colors.grey4};
|
340
340
|
`
|
341
341
|
|
342
|
+
const EVENT_TYPES = {
|
343
|
+
INPUT_FOCUS: 'input-focus',
|
344
|
+
INPUT_CHANGE: 'input-change',
|
345
|
+
INPUT_BLUR: 'input-blur',
|
346
|
+
PRESS_ENTER: 'on-enter-click',
|
347
|
+
INPUT_DRAG: 'on-input-drag',
|
348
|
+
SELECT_CHANGE: 'select-change',
|
349
|
+
}
|
350
|
+
|
342
351
|
export default {
|
343
352
|
name: 'InputNumber',
|
353
|
+
emits: [...Object.values(EVENT_TYPES)],
|
344
354
|
components: {
|
345
355
|
Container,
|
346
356
|
InputContainer,
|
@@ -519,9 +529,10 @@
|
|
519
529
|
},
|
520
530
|
data() {
|
521
531
|
return {
|
522
|
-
textInput: '',
|
523
532
|
isFocused: false,
|
524
533
|
warningIcon: warningIcon,
|
534
|
+
inputValue: null,
|
535
|
+
enteredValue: null,
|
525
536
|
}
|
526
537
|
},
|
527
538
|
computed: {
|
@@ -544,6 +555,14 @@
|
|
544
555
|
|
545
556
|
return item ? item.label : '-'
|
546
557
|
},
|
558
|
+
formattedValue() {
|
559
|
+
return this.formatWithCurrency(
|
560
|
+
this.isFocused ? this.enteredValue : this.inputValue
|
561
|
+
)
|
562
|
+
},
|
563
|
+
hasLength() {
|
564
|
+
return this.formattedValue !== null && this.formattedValue.length > 0
|
565
|
+
},
|
547
566
|
},
|
548
567
|
watch: {
|
549
568
|
focus(value) {
|
@@ -554,30 +573,19 @@
|
|
554
573
|
clearInput: function (value) {
|
555
574
|
if (value) {
|
556
575
|
// If the value is typed, then we should clear the textInput on Continue
|
557
|
-
this.
|
576
|
+
this.inputValue = ''
|
577
|
+
this.enteredValue = ''
|
558
578
|
}
|
559
579
|
},
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
}
|
568
|
-
}
|
569
|
-
this.textInput = numberToString({
|
570
|
-
value: this.defaultNumber,
|
571
|
-
numberPrecision: this.numberPrecision,
|
572
|
-
minDecimals: this.minDecimals,
|
573
|
-
})
|
574
|
-
} else if (this.minNumber !== null) {
|
575
|
-
this.textInput = numberToString({
|
576
|
-
value: this.minNumber,
|
577
|
-
numberPrecision: this.numberPrecision,
|
578
|
-
minDecimals: this.minDecimals,
|
579
|
-
})
|
580
|
-
}
|
580
|
+
value: {
|
581
|
+
immediate: true,
|
582
|
+
handler(val) {
|
583
|
+
if (this.value !== this.inputValue && !Number.isNaN(this.value)) {
|
584
|
+
this.inputValue = this.value
|
585
|
+
this.enteredValue = this.value
|
586
|
+
}
|
587
|
+
},
|
588
|
+
},
|
581
589
|
},
|
582
590
|
mounted() {
|
583
591
|
if (this.focus) {
|
@@ -586,29 +594,28 @@
|
|
586
594
|
},
|
587
595
|
methods: {
|
588
596
|
onEnterPress() {
|
589
|
-
this.$emit(
|
597
|
+
this.$emit(EVENT_TYPES.PRESS_ENTER)
|
590
598
|
this.$refs.inputField1.$el.blur()
|
591
599
|
},
|
592
|
-
onChangeHandler(
|
593
|
-
if (isNaN(
|
594
|
-
|
600
|
+
onChangeHandler(value) {
|
601
|
+
if (isNaN(value) || value === '') {
|
602
|
+
value = this.defaultNumber
|
595
603
|
? this.defaultNumber
|
596
604
|
: this.minNumber || this.minNumber === 0
|
597
605
|
? this.minNumber
|
598
|
-
:
|
606
|
+
: value
|
599
607
|
}
|
600
608
|
if (!this.allowNegative) {
|
601
|
-
|
609
|
+
value = Math.abs(value)
|
602
610
|
}
|
603
|
-
|
611
|
+
value = parseFloat(value)
|
604
612
|
// Need to return an integer rather than a string
|
605
|
-
|
613
|
+
return parseFloat(value)
|
606
614
|
},
|
607
|
-
onEvaluateCode(
|
615
|
+
onEvaluateCode(value) {
|
608
616
|
// function to perform math on the code
|
609
617
|
// filter the string in case of any malicious content
|
610
|
-
|
611
|
-
let filtered = val.replace('(auto)', '').replace(/[^-()\d/*+.,]/g, '')
|
618
|
+
let filtered = value.replace('(auto)', '').replace(/[^-()\d/*+.,]/g, '')
|
612
619
|
filtered = filtered.split(/([-+*/()])/)
|
613
620
|
let formatted = filtered.map((item) => {
|
614
621
|
if (
|
@@ -666,47 +673,28 @@
|
|
666
673
|
return array
|
667
674
|
},
|
668
675
|
onInput(event) {
|
669
|
-
|
676
|
+
this.enteredValue = event.target.value
|
677
|
+
if (!this.isFocused || this.enteredValue === this.inputValue) {
|
670
678
|
return
|
671
679
|
}
|
672
|
-
if (event.target.value === '') {
|
673
|
-
this.$emit('on-input', '')
|
674
|
-
}
|
675
680
|
let evaluatedVal
|
676
681
|
try {
|
677
|
-
evaluatedVal = this.onEvaluateCode(
|
682
|
+
evaluatedVal = this.onEvaluateCode(String(this.enteredValue))
|
678
683
|
} finally {
|
679
|
-
|
680
|
-
|
684
|
+
this.inputValue = this.onChangeHandler(evaluatedVal)
|
685
|
+
|
686
|
+
if (this.isFocused && typeof this.enteredValue !== 'number') {
|
687
|
+
this.$emit(EVENT_TYPES.INPUT_CHANGE, this.inputValue)
|
681
688
|
}
|
682
689
|
}
|
683
690
|
},
|
684
691
|
onInputBlur(e) {
|
685
692
|
this.isFocused = false
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
value:
|
692
|
-
evaluatedInput && value.length
|
693
|
-
? evaluatedInput
|
694
|
-
: this.defaultNumber
|
695
|
-
? this.defaultNumber
|
696
|
-
: this.minNumber,
|
697
|
-
numberPrecision: this.numberPrecision,
|
698
|
-
minDecimals: this.minDecimals,
|
699
|
-
})
|
700
|
-
}
|
701
|
-
let adjustedMinValue =
|
702
|
-
evaluatedInput && evaluatedInput.length
|
703
|
-
? evaluatedInput
|
704
|
-
: this.defaultNumber
|
705
|
-
? this.defaultNumber
|
706
|
-
: this.minNumber || this.minNumber === 0
|
707
|
-
? this.minNumber
|
708
|
-
: ''
|
709
|
-
this.$emit('input-blur', adjustedMinValue)
|
693
|
+
this.enteredValue = this.inputValue
|
694
|
+
this.$emit(
|
695
|
+
EVENT_TYPES.INPUT_BLUR,
|
696
|
+
this.onEvaluateCode(String(this.inputValue))
|
697
|
+
)
|
710
698
|
},
|
711
699
|
focusInput() {
|
712
700
|
if (this.disabled) {
|
@@ -716,7 +704,7 @@
|
|
716
704
|
this.$nextTick(() => {
|
717
705
|
this.$refs.inputField1.$el.select()
|
718
706
|
})
|
719
|
-
this.$emit(
|
707
|
+
this.$emit(EVENT_TYPES.INPUT_FOCUS, this.inputValue)
|
720
708
|
},
|
721
709
|
blurInput() {
|
722
710
|
if (this.disabled) {
|
@@ -749,6 +737,8 @@
|
|
749
737
|
return input + ' ' + unit
|
750
738
|
} else if (!adjustedMinValue && adjustedMinValue !== 0) {
|
751
739
|
return ''
|
740
|
+
} else if (this.isFocused && this.numberPrecision > 0) {
|
741
|
+
return value
|
752
742
|
} else {
|
753
743
|
return this.numberToStringEnabled
|
754
744
|
? numberToString({
|
@@ -769,14 +759,7 @@
|
|
769
759
|
e.preventDefault()
|
770
760
|
let value = parseFloat(this.value || 0)
|
771
761
|
value += parseFloat(this.interactionStep) * parseInt(e.movementX)
|
772
|
-
this.$emit(
|
773
|
-
|
774
|
-
this.textInput = numberToString({
|
775
|
-
value: value && value.length ? value : this.minNumber,
|
776
|
-
numberPrecision: this.numberPrecision,
|
777
|
-
minDecimals: this.minDecimals,
|
778
|
-
})
|
779
|
-
//this.value=value
|
762
|
+
this.$emit(EVENT_TYPES.INPUT_DRAG, this.onChangeHandler(value))
|
780
763
|
},
|
781
764
|
stopInteract(e) {
|
782
765
|
e.preventDefault()
|
@@ -784,6 +767,9 @@
|
|
784
767
|
window.removeEventListener('mouseup', this.stopInteract, false)
|
785
768
|
this.blurInput()
|
786
769
|
},
|
770
|
+
handleSelectChange(value) {
|
771
|
+
this.$emit(EVENT_TYPES.SELECT_CHANGE, value)
|
772
|
+
},
|
787
773
|
},
|
788
774
|
}
|
789
775
|
</script>
|
@@ -120,6 +120,11 @@
|
|
120
120
|
<SelectDropdown
|
121
121
|
v-show="isSelectDropdownShown"
|
122
122
|
ref="dropdown"
|
123
|
+
:style="{
|
124
|
+
transform: `translate(${dropdownPosition?.left}px, ${
|
125
|
+
noRelative ? 'auto' : `${dropdownPosition?.top}px`
|
126
|
+
})`,
|
127
|
+
}"
|
123
128
|
:bg-color="
|
124
129
|
dropdownBgColor || colorMode == 'dark' ? 'black' : 'white'
|
125
130
|
"
|
@@ -344,9 +349,8 @@
|
|
344
349
|
box-sizing: border-box;
|
345
350
|
z-index: ${(props) => (props.isActive ? '2' : '99999')};
|
346
351
|
position: absolute;
|
347
|
-
top:
|
348
|
-
|
349
|
-
left: ${(props) => props.dropdownPosition?.left}px;
|
352
|
+
top: 0px;
|
353
|
+
left: 0px;
|
350
354
|
border: ${BORDER_WIDTH} solid ${(props) => props.theme.colors.grey4};
|
351
355
|
border-radius: 4px;
|
352
356
|
display: flex;
|
@@ -606,6 +610,10 @@
|
|
606
610
|
},
|
607
611
|
dropdownWidth: null,
|
608
612
|
hoveredValue: null,
|
613
|
+
isDisplayedAtBottom: true,
|
614
|
+
selectTopPosition: 0,
|
615
|
+
selectAndDropdownDistance: 0,
|
616
|
+
animationFrameId: null,
|
609
617
|
}
|
610
618
|
},
|
611
619
|
computed: {
|
@@ -676,8 +684,13 @@
|
|
676
684
|
}, 10)
|
677
685
|
await this.$nextTick()
|
678
686
|
this.handleSetDropdownOffet()
|
687
|
+
this.calculateSelectTopPosition()
|
679
688
|
} else {
|
680
689
|
this.dropdownPosition.left = null
|
690
|
+
if (this.animationFrameId) {
|
691
|
+
cancelAnimationFrame(this.animationFrameId)
|
692
|
+
this.animationFrameId = null
|
693
|
+
}
|
681
694
|
setTimeout(() => {
|
682
695
|
this.isClickOutsideActive = false
|
683
696
|
}, 10)
|
@@ -690,11 +703,27 @@
|
|
690
703
|
})
|
691
704
|
}
|
692
705
|
},
|
706
|
+
isSelectDropdownShown(isShown) {
|
707
|
+
if (!isShown) return
|
708
|
+
|
709
|
+
// Need to wait for 1ms to make sure the dropdown menu is shown in the DOM
|
710
|
+
// before getting the distance between the select and the dropdown menu
|
711
|
+
setTimeout(() => {
|
712
|
+
this.getDistanceBetweenSelectAndDropdownMenu()
|
713
|
+
}, 100)
|
714
|
+
},
|
715
|
+
selectTopPosition() {
|
716
|
+
this.dropdownPosition.top =
|
717
|
+
this.selectTopPosition +
|
718
|
+
this.$refs.select.$el.clientHeight +
|
719
|
+
this.selectAndDropdownDistance
|
720
|
+
},
|
693
721
|
},
|
694
722
|
mounted() {
|
695
723
|
this.observeDropdownHeight()
|
696
724
|
this.observeSelectWidth()
|
697
725
|
window.addEventListener('resize', this.handleSetDropdownOffet)
|
726
|
+
document.body.addEventListener('scroll', this.calculateSelectTopPosition)
|
698
727
|
},
|
699
728
|
beforeMount() {
|
700
729
|
this.selectedValue = this.value
|
@@ -703,6 +732,10 @@
|
|
703
732
|
window.removeEventListener('resize', this.handleSetDropdownOffet)
|
704
733
|
if (this.dropdownResizeObserver) this.dropdownResizeObserver.disconnect()
|
705
734
|
if (this.selectResizeObserver) this.selectResizeObserver.disconnect()
|
735
|
+
document.body.removeEventListener(
|
736
|
+
'scroll',
|
737
|
+
this.calculateSelectTopPosition
|
738
|
+
)
|
706
739
|
},
|
707
740
|
unmounted() {
|
708
741
|
document.removeEventListener('click', this.clickOutside)
|
@@ -808,11 +841,11 @@
|
|
808
841
|
return
|
809
842
|
}
|
810
843
|
await this.$nextTick()
|
811
|
-
|
844
|
+
this.isDisplayedAtBottom = await this.generateDropdownPosition()
|
812
845
|
// If the dropdown menu is going to be displayed at the bottom,
|
813
846
|
// we need reverify its position after a dom update (nextTick)
|
814
847
|
await this.$nextTick()
|
815
|
-
if (isDisplayedAtBottom) this.generateDropdownPosition()
|
848
|
+
if (this.isDisplayedAtBottom) this.generateDropdownPosition()
|
816
849
|
},
|
817
850
|
async generateDropdownPosition() {
|
818
851
|
const isDropdownNotCompletelyVisible =
|
@@ -905,6 +938,25 @@
|
|
905
938
|
}
|
906
939
|
}
|
907
940
|
},
|
941
|
+
getDistanceBetweenSelectAndDropdownMenu() {
|
942
|
+
const wholeSelectTopPosition =
|
943
|
+
this.selectTopPosition + this.$refs.select.$el.clientHeight
|
944
|
+
this.selectAndDropdownDistance =
|
945
|
+
this.dropdownPosition.top - wholeSelectTopPosition
|
946
|
+
},
|
947
|
+
calculateSelectTopPosition() {
|
948
|
+
const selectRef = this.$refs.select
|
949
|
+
if (selectRef) {
|
950
|
+
const currentTopPosition =
|
951
|
+
selectRef.$el.getBoundingClientRect().top + window.scrollY
|
952
|
+
if (this.selectTopPosition !== currentTopPosition) {
|
953
|
+
this.selectTopPosition = currentTopPosition
|
954
|
+
}
|
955
|
+
}
|
956
|
+
this.animationFrameId = requestAnimationFrame(
|
957
|
+
this.calculateSelectTopPosition
|
958
|
+
)
|
959
|
+
},
|
908
960
|
},
|
909
961
|
}
|
910
962
|
</script>
|
@@ -35,37 +35,37 @@
|
|
35
35
|
visibility: ${(props) => (props.visible ? 'inherit' : 'hidden')};
|
36
36
|
`
|
37
37
|
|
38
|
-
const pageAttrs = {
|
38
|
+
const pageAttrs = { backdrop: String, position: String }
|
39
39
|
const PageWrapper = styled('div', pageAttrs)`
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
40
|
+
position: ${(props) => props.position}
|
41
|
+
display: grid;
|
42
|
+
top: 0;
|
43
|
+
left: 0;
|
44
|
+
width: 100%;
|
45
|
+
height: 100%;
|
46
|
+
background-color: ${(props) =>
|
47
|
+
props.backdrop == 'dark'
|
48
|
+
? 'rgba(0, 0, 0, 0.4)'
|
49
|
+
: 'rgba(255, 255, 255, 0.9)'};
|
50
|
+
z-index: 99999;
|
51
|
+
overflow: auto;
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
&.visible {
|
54
|
+
visibility: visible;
|
55
|
+
opacity: 1;
|
56
|
+
transition: visibility 0s linear 0s, opacity 300ms;
|
57
|
+
}
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
&.hidden {
|
60
|
+
visibility: hidden;
|
61
|
+
opacity: 0;
|
62
|
+
transition: visibility 0s linear 300ms, opacity 300ms;
|
63
|
+
}
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
`
|
65
|
+
@media (max-width: 425px) {
|
66
|
+
background: white;
|
67
|
+
}
|
68
|
+
`
|
69
69
|
|
70
70
|
const ModalContainer = styled.div`
|
71
71
|
align-self: center;
|
@@ -94,7 +94,7 @@ export const stringToNumber = ({
|
|
94
94
|
|
95
95
|
export const numberToString = ({ value, numberPrecision, minDecimals }) => {
|
96
96
|
const minimumRounding = minDecimals ? minDecimals : 0
|
97
|
-
value = parseFloat(value)
|
97
|
+
value = !Number.isNaN(parseFloat(value)) ? parseFloat(value) : 0
|
98
98
|
return value.toLocaleString(langForLocaleString(), {
|
99
99
|
minimumFractionDigits: minimumRounding, // removing this for now. Why do we need this to be a minimum amount?
|
100
100
|
maximumFractionDigits:
|
@@ -1,66 +0,0 @@
|
|
1
|
-
import { mount } from '@vue/test-utils'
|
2
|
-
import RCIcon from '@/components/icon'
|
3
|
-
import theme from '@/assets/theme'
|
4
|
-
import 'jest-styled-components'
|
5
|
-
jest.mock('@/components/icon/iconCache.mjs', () => ({
|
6
|
-
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
7
|
-
}))
|
8
|
-
|
9
|
-
describe('RCIcon.vue', () => {
|
10
|
-
it('renders icon with default props', async () => {
|
11
|
-
const wrapper = mount(RCIcon, {
|
12
|
-
props: { name: 'House' },
|
13
|
-
global: {
|
14
|
-
provide: {
|
15
|
-
theme,
|
16
|
-
},
|
17
|
-
},
|
18
|
-
})
|
19
|
-
|
20
|
-
const iconWrapper = wrapper.find('[data-test-id="icon_wrapper"]')
|
21
|
-
const iconImage = wrapper.find('[data-test-id="icon_image"]')
|
22
|
-
|
23
|
-
expect(iconWrapper.exists()).toBe(true)
|
24
|
-
expect(iconImage.exists()).toBe(true)
|
25
|
-
expect(wrapper.props('size')).toBe('30px')
|
26
|
-
})
|
27
|
-
|
28
|
-
it('renders striked line when isStriked is true', async () => {
|
29
|
-
const wrapper = mount(RCIcon, {
|
30
|
-
props: {
|
31
|
-
name: 'House',
|
32
|
-
isStriked: true,
|
33
|
-
color: 'red',
|
34
|
-
},
|
35
|
-
global: {
|
36
|
-
provide: {
|
37
|
-
theme,
|
38
|
-
},
|
39
|
-
},
|
40
|
-
})
|
41
|
-
|
42
|
-
const strikedLine = wrapper.find('[data-test-id="icon_striked_line"]')
|
43
|
-
expect(strikedLine.exists()).toBe(true)
|
44
|
-
})
|
45
|
-
|
46
|
-
it('applies correct styling based on props', async () => {
|
47
|
-
const wrapper = mount(RCIcon, {
|
48
|
-
props: {
|
49
|
-
name: 'House',
|
50
|
-
size: '60px',
|
51
|
-
color: 'red',
|
52
|
-
hoveredColor: 'blue',
|
53
|
-
backgroundColor: 'yellow',
|
54
|
-
},
|
55
|
-
global: {
|
56
|
-
provide: {
|
57
|
-
theme,
|
58
|
-
},
|
59
|
-
},
|
60
|
-
})
|
61
|
-
|
62
|
-
const iconImage = wrapper.find('[data-test-id="icon_image"]')
|
63
|
-
expect(iconImage.exists()).toBe(true)
|
64
|
-
// Add assertions for styling if needed
|
65
|
-
})
|
66
|
-
})
|