Fix unresponsive circle icon in revert button

This commit fixes a UI bug where the circle icon of the revertbutton was
unresponsive to clicks. The solution involves replacing the
pseudo-element (`:before`) with an actual HTML element, enabling direct
event binding.

Additional improvements include:

- Removal of redundant `z-index` properties to simplify click event
  handling and reduce complexity.
- Programmatic toggle of `isChecked` on click, providing more controlled
  and explicit behavior and avoiding issues with native checkbox
  behavior, especially when overlaid on a pseudo-element.
This commit is contained in:
undergroundwires
2023-12-19 10:44:54 +01:00
parent efa05f42bc
commit 645c333787
3 changed files with 42 additions and 11 deletions

View File

@@ -1,7 +1,7 @@
<template> <template>
<div <div
class="toggle-switch" class="toggle-switch"
@click="handleClickPropagation" @click="onClick"
> >
<input <input
v-model="isChecked" v-model="isChecked"
@@ -9,6 +9,7 @@
class="toggle-input" class="toggle-input"
> >
<div class="toggle-animation"> <div class="toggle-animation">
<div class="circle" />
<span class="label-off">{{ label }}</span> <span class="label-off">{{ label }}</span>
<span class="label-on">{{ label }}</span> <span class="label-on">{{ label }}</span>
</div> </div>
@@ -48,15 +49,16 @@ export default defineComponent({
}, },
}); });
function handleClickPropagation(event: Event): void { function onClick(event: Event): void {
if (props.stopClickPropagation) { if (props.stopClickPropagation) {
event.stopPropagation(); event.stopPropagation();
} }
isChecked.value = !isChecked.value;
} }
return { return {
isChecked, isChecked,
handleClickPropagation, onClick,
}; };
}, },
}); });
@@ -114,7 +116,6 @@ $gap : 0.25em;
width: 100%; width: 100%;
height: 100%; height: 100%;
opacity: 0; opacity: 0;
z-index: 2;
@include clickable; @include clickable;
} }
@@ -127,8 +128,7 @@ $gap : 0.25em;
background-color: $color-bg-unchecked; background-color: $color-bg-unchecked;
transition: background-color 0.25s ease-out; transition: background-color 0.25s ease-out;
&:before { .circle {
content: "";
display: block; display: block;
position: absolute; position: absolute;
left: $padding-horizontal; left: $padding-horizontal;
@@ -141,7 +141,6 @@ $gap : 0.25em;
border-radius: 50%; border-radius: 50%;
background-color: $color-toggle-unchecked; background-color: $color-toggle-unchecked;
transition: left 0.3s ease-out; transition: left 0.3s ease-out;
z-index: 10;
} }
} }
@@ -149,7 +148,7 @@ $gap : 0.25em;
background-color: $color-bg-checked; background-color: $color-bg-checked;
flex-direction: row-reverse; flex-direction: row-reverse;
&:before { .circle {
$left-offset: calc(100% - #{$size-circle}); $left-offset: calc(100% - #{$size-circle});
$padded-left-offset: calc(#{$left-offset} - #{$padding-horizontal}); $padded-left-offset: calc(#{$left-offset} - #{$padding-horizontal});
left: $padded-left-offset; left: $padded-left-offset;

View File

@@ -24,7 +24,7 @@ describe('revert toggle', () => {
.contains('revert'); .contains('revert');
}); });
it('should render label completely without clipping', () => { it('should render label completely without clipping', () => { // Regression test for a bug where label is partially rendered (clipped)
cy cy
.get('@toggleSwitch') .get('@toggleSwitch')
.find('span') .find('span')
@@ -37,6 +37,22 @@ describe('revert toggle', () => {
expect(expectedMinimumTextWidth).to.be.lessThan(containerWidth); expect(expectedMinimumTextWidth).to.be.lessThan(containerWidth);
}); });
}); });
it('should toggle the revert state when clicked', () => {
cy.get('@toggleSwitch').then(($toggleSwitch) => {
// arrange
const initialState = $toggleSwitch.find('.toggle-input').is(':checked');
// act
cy.wrap($toggleSwitch).click();
// assert
cy.wrap($toggleSwitch).find('.toggle-input').should(($input) => {
const newState = $input.is(':checked');
expect(newState).to.not.equal(initialState);
});
});
});
}); });
}); });

View File

@@ -9,6 +9,7 @@ import ToggleSwitch from '@/presentation/components/Scripts/View/Tree/NodeConten
const DOM_INPUT_TOGGLE_CHECKBOX_SELECTOR = 'input.toggle-input'; const DOM_INPUT_TOGGLE_CHECKBOX_SELECTOR = 'input.toggle-input';
const DOM_INPUT_TOGGLE_LABEL_OFF_SELECTOR = 'span.label-off'; const DOM_INPUT_TOGGLE_LABEL_OFF_SELECTOR = 'span.label-off';
const DOM_INPUT_TOGGLE_LABEL_ON_SELECTOR = 'span.label-on'; const DOM_INPUT_TOGGLE_LABEL_ON_SELECTOR = 'span.label-on';
const DOM_INPUT_CIRCLE_ICON_SELECTOR = '.circle';
describe('ToggleSwitch.vue', () => { describe('ToggleSwitch.vue', () => {
describe('initial state', () => { describe('initial state', () => {
@@ -130,10 +131,25 @@ describe('ToggleSwitch.vue', () => {
}); });
}); });
}); });
it('emits when the circle icon is clicked', async () => { // Regression test for unresponsive circle icon
// arrange
const initialCheckValue = false;
const expectedCheckValue = true;
const wrapper = mountComponent({
properties: {
modelValue: initialCheckValue,
},
});
// act
const circleIcon = wrapper.find(DOM_INPUT_CIRCLE_ICON_SELECTOR);
await circleIcon.trigger('click');
// assert
expect(wrapper.emitted('update:modelValue')).to.deep.equal([[expectedCheckValue]]);
});
}); });
describe('click propagation', () => { describe('click propagation', () => {
it('stops propagation `stopClickPropagation` is true', async () => { it('stops propagation if `stopClickPropagation` is true', async () => {
// arrange // arrange
const { wrapper: parentWrapper, parentClickEventName } = mountToggleSwitchParent( const { wrapper: parentWrapper, parentClickEventName } = mountToggleSwitchParent(
{ stopClickPropagation: true }, { stopClickPropagation: true },
@@ -148,7 +164,7 @@ describe('ToggleSwitch.vue', () => {
const receivedEvents = parentWrapper.emitted(parentClickEventName); const receivedEvents = parentWrapper.emitted(parentClickEventName);
expect(receivedEvents).to.equal(undefined); expect(receivedEvents).to.equal(undefined);
}); });
it('allows propagation `stopClickPropagation` is false', async () => { it('allows propagation if `stopClickPropagation` is false', async () => {
// arrange // arrange
const { wrapper: parentWrapper, parentClickEventName } = mountToggleSwitchParent( const { wrapper: parentWrapper, parentClickEventName } = mountToggleSwitchParent(
{ stopClickPropagation: false }, { stopClickPropagation: false },