Vue/Vuetify transition only one way - css

Is it possible to set transition to trigger only one way for the v-btn?
I created button with text that changes depending on the condition and use transition to change it smoothly. However if i change view and condition changes the transition reverses and it looks glitchy. I dont want this behaviour, and only would like to have transition trigger one way.
<v-btn
class="ma-1"
#click="addToList"
width
:disabled="isAdded"
>
<span v-if="!isAdded">
Add to List
</span>
<v-slide-x-transition>
<span v-if="isAdded">
Added
<v-icon>
mdi-check
</v-icon>
</span>
</v-slide-x-transition>
</v-btn>
Here is example how this works:
https://codepen.io/pokepim/pen/VwKNzxj?editors=101
In the carousel when you switch between slides you can see that button reverses back and it extends width and generally this looks somewhat glitchy. Is it possible to only keep transition when clicking on the button in this case (so no backward transition from disable to active button)?
EDIT:
I tried Dan's answer, and while it works fine with the carousel case it still does not solve glitchy transition in general, here is the example of same behaviour using method instead of computed property:
https://codesandbox.io/s/vuetifyvuex-store-testing-ground-wyl4n?file=/src/App.vue

There's only one isAdded condition being used for all of the slides. When that condition is false on the next slide, it gets set before the animation is finished and it looks bad.
Use a method instead of a computed for isAdded so each slide can maintain its own button state:
methods: {
addToList() {
this.btnlist.push(this.model);
},
isAdded(i) {
return this.btnlist.includes(i);
}
}
And change all isAdded in the template to isAdded(i).
Here's the updated demo
To your edit, try the hide-on-leave attribute on the transition:
<v-slide-x-transition hide-on-leave>
<span v-if="isAdded(this.idUn)">
Added
<v-icon> mdi-check </v-icon>
</span>
</v-slide-x-transition>

Related

angular material drag&drop calls callback after css transition end

I have implemented list of elements which can be drag and dropped via angular material drag and drop feature
like in tutorial https://material.angular.io/cdk/drag-drop/overview
I've implemented function drop(event) however in my case i need not just move elements in angular model. I need to send request to server and when response from server will come update it. So my function is not changing anything, it creates request to server.
Problem here is that because angular model is not changed at drop function there is an element "jumping" occurs: after element drop element returns to the position where it was and when response from server comes and model is updated element move to new position. For user it's very painful to see that
I want to hide somehow from user that some work is going in the background.
Idea to have duplicated list which is for user and another one for server and update them (user list at the moment of drop and server list at the moment of response) I left as last decision because it's hard to maintain.
I'm trying to resolve it with css animations. As you can see in drag&drop example it uses
.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
"transition: transform" to show smooth animation when user drops element. So i want to show user animation about 0.5s. This time should be enough for response from server and when animation is finished model is already updated.
What i'm seeing is that
(cdkDropListDropped)="drop($event)"
drop function is called AFTER transition is ended. So even if I do transform 2000ms drop function will be called after transition end, so after 2s. Is it expected? I thought function should be called after mouse release.
Do you have any thoughts for initial problem(hide model update) and for this css animation transition problem in particular?
Update:
I've created example of my problem
https://stackblitz.com/edit/angular-3nhsxx?file=src%2Fapp%2Fcdk-drag-drop-disabled-sorting-example.css,src%2Fapp%2Fcdk-drag-drop-disabled-sorting-example.ts
drop function is called AFTER transition is ended. So even if I do transform 2000ms drop function will be called after transition end, so after 2s. Is it expected? I thought function should be called after mouse release.
Yes, it is expected. The reason for this has a technically background. cdkDrag create a deep copy of the dragged element and set the position of the other items with position fixed. If the drag finished (mouse up) the animation starts and first when the animation is finished Angular will "resort" the items for a good transitions. Otherwise the elements will "jump" back to start order and then to new order.
You can use cdkDragReleased which fires directly before the animation ends. I think this is what you want. Stackblitz is updated.
Additional informations (container, index, item) can found in cdkDragExited and cdkDragEntered as example. More in the official docs.
Demo:
<div cdkDropListGroup>
<div class="example-container">
<h2>Available items</h2>
<div
cdkDropList
id="test"
[cdkDropListData]="items"
class="example-list"
cdkDropListSortingDisabled
(cdkDropListDropped)="drop($event)"
>
<div class="example-box" *ngFor="let item of items" (cdkDragEntered)="cdkDropListEntered($event)"
(cdkDragExited)="cdkDropListExited($event)" (cdkDragReleased)="dragEnd($event)" cdkDrag>{{item}}</div>
</div>
</div>
<div class="example-container">
<h2>Shopping basket</h2>
<div
id="super"
cdkDropList
[cdkDropListData]="basket"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
<div class="example-box" *ngFor="let item of basket" (cdkDragEntered)="cdkDropListEntered($event)"
(cdkDragExited)="cdkDropListExited($event)" (cdkDragReleased)="dragEnd($event)" cdkDrag>{{item}}</div>
</div>
</div>
</div>
Code
dragEnd(event: any) {
console.log('End', event);
}
cdkDropListEntered(event: any) {
console.log(event);
}
cdkDropListExited(event: any) {
console.log(event);
}

Button within react-router Link?

Say I have an element with the following structure:
<Link to={`/games/${game.id}`}>
<GameInfo/>
<CustomButton/>
</Link>
Is it possible for the button inside the link to behave independently from the link beneath without using the z-index css property? I'd like to do this while keeping the current behaviour in which hovering the button triggers both the hover effect for the button and for the link below.
Right now, if I click on the Add to Library button, the game gets added to the library but the Link below also gets triggered and the game profile page is open, which is not intended.
The only solution I can think of so far is something like this:
<div> // <= move link hover effects here
<Link to={`/games/${game.id}`}>
<GameInfo/>
</Link>
<CustomButton/>
</div>
But it's not ideal, because I would still like the area around the button to be part of the Link (so hover and the link itself work in the areas shown below).
Is z-index the only solution?
You can make use of preventDefault and stopPropagation to achieve this inside button click use like this below
<Link to="/game">
<div>
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
//function to do your stuff
}}
>
Click Me
</button>
</div>
</Link>
You can check the demo here
https://codesandbox.io/s/adoring-sun-dwnde?fontsize=14&hidenavigation=1&theme=dark

Why does the angular material design disappear when I add a custom angular class to a button?

I am trying to add a custom css class directive to my button, but whenever I do that, the angular material design goes away and the button goes back to its default ugly state. How can I make sure that my custom css class gets added to the button without it removing the mat-buutton effects?
<button
mat-button
[class]="todoItems.completed ? 'delete': null"
(click)="deleteTodo(todoItems._id)"
[disabled]="!todoItems.completed">
<mat-icon >delete</mat-icon>
</button>
Here is the css
.delete:hover {
color: red
}
I want my class to be applied only if the completed property is true and when that property is true the delete button will turn red when I hover on it.
The hovering part works fine but the problem I have is that the material design disappears and it turns into the default style of button
mat-button applies its own css classes to the <button> at run time.
When you are doing [class]="todoItems.completed ? 'delete': null" it is hard overridding these classes.
To append your class to the existing ones, you need to use ngClass instead.

Tab accessibility within a hover state

I have a component that, upon a hover, shows a button and a link that you can click on. This is not a menu... just a box in the middle of the page.
For accessibility, I would like a user to be able to tab into the container (happens now, and displays the content in the .HiddenUntilHover class) AND also continue to tab to the button and link that show up on the hover/focused state.
Right now you can focus on the container and see the hover state; however, when you tab it just goes to the next element and does not allow you to tab to the button or link WITHIN the hover state.
Pseudo code example:
/* My component .jsx */
<div tabIndex="0" className="MainContainer">
<div className="SomeOtherClass">
<div className="HiddenUntilHover">
/* I would like to be able to tab to these clickable things! */
<button>Click me!</button>
I am also clickable
</div>
</div>
</div>
And my SCSS:
.HiddenUntilHover {
display: none;
}
MainContainer:focus,
MainContainer:hover,
> .HiddenUntilHover {
display: block
}
I ran into this issue a few days ago and I solved it using css classes to make the hovered content accessible via keyboard navigation.
The way I got this working was to use css pseudo-classes to ensure that when the div element is active & focused that the buttons inside also display. Specifically the additional use of :focus-within & :focus-visible should ensure that when you tab over the list items, their contents are also displayed and keyboard accessible.
.MainContainer {
&:not(:hover, :focus, :active, :focus-visible, :focus-within) {
.HiddenUntilHover {
visibility: hidden;
}
}
}
<body>
<div tabIndex="0" className="MainContainer">
Content
<div className="SomeOtherClass">
<div className="HiddenUntilHover">
<button>Click me!</button>
I am also clickable
</div>
</div>
</div>
</body>
Here's a link to the Codesandbox demo of this working
When the box is in focus, tabbing further to the button will make the box blur, which will hide it, and its contents, so focus will move to the next accessible element. I think this is the behavior you are experiencing.
You might consider using inserting an aria-activedescendant or tabindex attribute when the box comes into focus. This requires a little javascript.
Strictly speaking, you don't need to rely on the hover state to make that control accessible. You could have an offscreen (or clipped) button/link that is not a DOM child of the hidden (display:none) box. If you take this approach, read up on the aria-owns attribute.
As long as it is marked up as a button or link (or has a tabindex="0" setting), and is not 'really' hidden, it ought to be possible to tab to it.
Try increasing the properties of the class MainContainer
for example.
.MainContainer {
width: 100%;
height: 100px;
}
.MainContainer .HiddenUntilHover {
display: none;
}
.MainContainer:hover .HiddenUntilHover, .MainContainer:focus .HiddenUntilHover {
display: block;
}
Elements appearing on hover are inherently inaccessible. You are experiencing one side of the problem with your code, where it is difficult to make it keyboard accessible.
But think about touch screens that have no real concept of hover: is there some way to reach your button on a smarphone or tablet?
For a more pragmatic answer, if you need to stay with hover, a less hacky solution than the two already posted ones could be the following:
use focusin and focusout events. See for example this question for explanations and differences with focus/blur, and this w3school doc for browser compatibility.
You will have to structure your HTML differently, such as:
<div id="outer">
<div id="hover">
...
</div><!--hover-->
<button>Your button which only appears on hover</utton>
</div><!--outer-->
As well as use a bit of js:
$('#outer').on('focusin', __=>$('#hover').classNames.add('keep-visible'));
$('#outer').on('focusout', __=>$('#hover').classNames.remove('keep-visible'));
With a corresponding .keep-visible class which will leave the element display:block (I'm not a CSS expert, I let you write the code).
The overal functionning is the following: when some element within #outer takes the focus, the focusin element is fired due to bubbling. In the event, you put your class .keep-visible which makes the element to stay visible.
The focusout event is fired when the focus leaves the last element within #outer. At that point you remove the .keep-visible class, which makes the element to disappear.
According to the link above, onfocusin/out aren't standard, but are supported by all major browsers including IE. Firefox is the last one to implement it in 52.0, so it's a kind of defacto standard; we can reasonably expect that it won't disappear soon.

Is it possible to position a div on top of a <dialog> tag that is not its parent?

The <dialog> tag, when opened with showModal(), will display the elements between it and its closing tag while disabling all other elements on the page. My question is: is it possible to override this behavior for a specific element? Example:
HTML:
<div id="container">
<dialog id="myDialog">
<button id="close" type="reset">Close</button>
<button id="create">Add Element</button>
</dialog>
</div>
<menu>
<button id="openButton">Open Dialog</button>
</menu>
CSS:
.new-element {
width: 50px;
height: 50px;
border: 3px solid black;
background-color: blue;
position: fixed;
top: 50%;
}
JS:
const container = document.getElementById('container');
const openButton = document.getElementById('openButton');
const closeButton = document.getElementById('close');
const createButton = document.getElementById('create');
const myDialog = document.getElementById('myDialog');
openButton.addEventListener('click', function() {
myDialog.showModal();
});
closeButton.addEventListener('click', function() {
myDialog.close();
});
createButton.addEventListener('click', function() {
const div = document.createElement('div');
div.classList.add('new-element')
container.appendChild(div);
});
In a JSFiddle: https://jsfiddle.net/y7bkxvd4/
I'd like to find a way to position the blue square on top of the dialog. I realize it would be far easier to just append the new div to the dialog itself, but I've run into this in a situation where overflow was a concern and in using a module that uses react-portal. If it's not possible, cool, I can get behind that. But if it is, I'd like to know.
z-index has no effect, obviously.
The <dialog> element is added to the 'Top Layer' of the dom which has its own stacking order (z-index does not affect this -- it is set strictly by the order by which the elements are added). I don't believe you can manually add elements to this Top Layer, it is done in functions such as showModal(). You can find more information at: https://fullscreen.spec.whatwg.org/#new-stacking-layer but because the feature is still not universally supported its tough finding documentation on it. For example:
To remove an element from a top layer, remove element from top layer.
Real helpful..
A couple work arounds:
Change the added element to a dialog as well and call .showModal() when the element is appended. The problem with this approach is that .showModal() makes all element outside that element unavailable for user interaction. That means that your blue box is on top, but its also means you can't click "Close" or "Add Element" on the other modal. (NOTE: You'll also notice the "Close/Add Element" dialog is greyed out -- you can override this by changing .new-element::backdrop{...} but it still won't the change the fact you can't click "Close" or "Add Element") Example here (with the backdrop removed)
Change the added element to dialog, call .show() when the element is appended, and change the click event for 'Open Dialog' to .show() instead of .showModal. This allows you to also click past the blue box (even though its 'on top'), but it also allows you to click anywhere on the page (kind of defeating the purpose of a modal). The ::backdrop pseudo element is also not available because you are not using .showModal If you take this approach you would need to attach closing the blue box to the "Close" click event handler. Example here
My recommendation is to either use a plugin for modals (such as Bootstrap's) or make your own with the functionality you want (using Javascript). Dialogs are technically experiential technology so it won't be easy trying to get the behavior you want out of the box. This is probably as close as you will get, though you could improve it by adding your own "backdrop".

Resources