show
Updated on September 7, 2024Source codeTests
show
is a function that can conditionally show or hide elements in the DOM, using the CSS display
property under the hood. It works with static values—conditionally displaying once—and reactive values—showing and hiding elements each time the value changes.
Example
The useTablist
example is another good demo—it uses show
under the hood to toggle an array of tab panel elements.
Usage
To conditionally display a DOM element, call the show
function, which requires two parameters: the element or elements you're conditionally showing, and the condition the element or elements need to meet. show
also accepts an optional options
object as its final parameter.
import { show } from '@baleada/vue-features'
export function myCompositionFunction (...) {
show(elementOrListOrPlane, condition[, options])
}
Here's a breakdown of the required parameters:
elementOrListOrPlane
A reactive reference to the DOM element or elements you're binding data to.
elementOrListOrPlane
can be one of the following types:
- HTMLElement
- Array of HTMLElements
Plane
of HTMLElements- Reactive reference to any of the above types
See the How to format your condition section for more guidance on conditionally displaying specific elements in a reactive array.
condition
Indicates whether or not elementOrListOrPlane
should be displayed.
See the How to format your condition section for more guidance on formatting your condition.
And here's a breakdown of the options
object:
transition
An object that contains methods and/or CSS classes for enter/leave transitions.
See the How to format enter/leave transitions section for more guidance, including tips for type-safe transition definitions using TypeScript.
How to format your condition
There are several different ways to format the condition that show
uses to determine whether or not an element should be displayed.
The simplest type of condition is a reactive reference to a Boolean:
import { ref } from 'vue'
const isShown = ref(true),
myElement = ref(null)
// ☝️ A Vue component would populate this ref
// with a DOM element after the component is
// mounted.
show(
myElement,
isShown,
)
show
watches your reactive value for changes. When the value is true
, your element is displayed, and when it's false
, the element is hidden.
But what about when the element
is a reactive array of elements, rather than a single reactive element reference? How do we make each element is correctly shown or hidden?
If you only need to conditionally display your elements once, you can pass the value getter instead of a reactive Boolean. The value getter is a callback function that receives only one argument: the index
(Number) of a given element in your reactive array of elements. It should return true
for elements that should be shown, and false
for elements that should be hidden.
But more commonly, you'll need to conditionally display an array of elements based on some other piece of reactive data. For those cases, you can pass a reactive value getter as the key's value.
A reactive value getter is an object, as described in the bind
guide. Here's a breakdown of that object:
get
watchSource
A single watch source, or an array of watch sources. No need to pass your reactive array of elements—that data is already watched automatically.
Each time show
detects a change in your watch sources (or the reactive array of elements), it will iterate through your array of elements, calling get
for each one.
Here's an example of how useTablist
uses this feature to conditionally display tab panels, displaying only the currently selected panel:
export function useTablist (...) {
...
show(
// Reactive array of tab panel elements
panels.list,
{
// `selected.value.newest` is a reactive reference to the
// index of the currently selected tab panel.
//
// Every panel should be hidden, except for the panel
// whose index is a match.
//
// This `get` callback should run again each time
// `selected.value.newest` changes.
get: index => index === selected.value.newest,
watchSource: () => selected.value.newest,
},
options
)
...
}
Finally, let's look at how this all works with a Plane
, the Baleada Features data structure for an array of arrays of elements.
The value format for a Plane
is almost identical to the format for arrays of elements. When you only need to conditionally display the elements in the Plane
once, you can pass the value getter. The value getter for a Plane
is a callback function that receives two arguments: the row
index (Number) and column
index (Number) of a given element in your Plane
of elements. It should return true
for elements that should be shown, and false
for elements that should be hidden.
Here's an example of how that would look:
import { show } from '@baleada/vue-features'
export function myComposable (...) {
...
show(
// Reactive `Plane` of elements
myPlane,
{
get: (row, column) => {
// Based on `row` and `column`, return `true` for
// elements that should be shown, and `false` for
// elements that should be hidden.
},
watchSource: [
// Any watch sources that should cause the `get` callback
// to re-run and conditionally display each element again.
],
},
options
)
...
}
How to format enter/leave transitions
As outlined above, show
accepts an optional options
object as its final parameter, and that object's only property is transition
.
You can use this transition
property to configure an enter/leave transition that will more smoothly show and hide your element or elements.
The API for show
's transition
option is inspired by the API of Vue's Transition
component. Both CSS and JS transitions are supported.
The value of the transition
property should be an object configuring your transition. That object can have these properties:
appear
enter
leave
Each property of your transition config can either be a CSS transition or a JS transition, whose formats are explained in more detail in the next section.
Before diving in to transition formats, note a few points:
- It's possible to mix and match transitions, i.e. you can have a CSS
enter
transition and a JSleave
transition. - It's not required to define a transition for every state, i.e. you can define an
enter
transition with noleave
transition. - Just like with Vue's
Transition
component, you can setappear
totrue
, and your element will use theenter
transition when it first appears.
Now, let's go through the CSS and JS transition formats individually, and finish up by looking at type-safe transitions for TypeScript users.
CSS transitions
CSS transitions are configured by objects. Here's a breakdown of the CSS transition object:
from
active
from
classes get added, and kept on the element until it finishes transitioning.to
start
show
will call with no arguments, right before adding from
classes. Useful for performing pre-transition side effects.end
show
will call with no arguments, one animation frame after the transition ends, in the same animation frame that active
and to
classes are removed. Useful for performing post-transition side effects, like focusing a form control.cancel
show
will call with no arguments, one animation frame after the transition cancels. Useful for performing post-cancel side effects, like focusing a form control.Here's a more detailed breakdown of transition timing:
- The optional
start
callback gets called from
classes get added- One animation frame later,
active
classes get added - One animation frame later,
from
classes get removed, andto
classes get added - Transition eventually ends
- One animation frame later,
active
andto
classes get removed, andshow
calls the optionalend
callback
Cancel timing:
- Transition is canceled by setting
transition-property
tonone
and removingfrom
,active
, andto
classes - One animation frame later,
transition-property
is restored to its original value (if any) andshow
calls the optionalcancel
callback show
never calls the optionalend
callback
import { show } from '@baleada/vue-features'
show(
myElement,
myCondition,
{
transition: {
// Example fade-in transition using Tailwind classes
enter: {
from: 'opacity-0 scale-[98%]',
active: 'transition ease-out duration-150',
to: 'opacity-100 scale-100',
// Optional callbacks for post-transition side effects
end: () => myElement.value.focus(),
cancel: () => someOtherElement.value.focus(),
}
}
}
)
JS transitions
JS transitions are configured by objects. Here's a breakdown of the JS transition object:
before
Performs side effects before the transition starts.
See the list below this table for more info on before
parameters.
active
Performs the bulk of JS transition work
Important: in addition to the parameters explained below this table, active
receives a final done
parameter.
done
is a callback that you should call with no arguments when the JS transition has been completed.
after
Performs side effects after the transition ends.
See the list below this table for more info on before
parameters.
cancel
Performs side effects after the transition cancels.
See the list below this table for more info on before
parameters.
More info on the parameters passed to these callbacks:
- When you're transitioning a single element, the callbacks receive no parameters.
- When you're transitioning a list, the callbacks get called for each element, and the receive the index (number) of the element currently being transitioned.
- When you're transitioning a
Plane
, the callbacks get called for each element, and they receive the row (Number) and column (Number) of the element currently being transitioned. - Remember that
active
receives itsdone
parameter after any index/row/column parameters. Calldone
with no arguments when the JS transition has been completed.
import { show } from '@baleada/vue-features'
show(
required,
{
transition: {
enter: {
before: ...,
active: ...,
after: ...,
cancel: ...,
},
leave: {
before: ...,
active: ...,
after: ...,
cancel: ...,
},
appear: {
before: ...,
active: ...,
after: ...,
cancel: ...,
},
}
}
)
Here's a more detailed breakdown of JS transition timing:
appear.before
display
property is changed to show the element for the first time.appear.active
display
property is changed to show the element for the first time.appear.after
appear.active
, assuming the transition was not canceled.appear.cancel
show
to hide the element after appear.active
starts AND before the done
function has been called inside appear.active
.enter.before
display
property is changed to show the element. Does not run when this happens for the first time.enter.active
display
property is changed to show the element. Does not run when this happens for the first time.enter.after
enter.active
, assuming the transition was not canceled.enter.cancel
show
to hide the element after enter.active
starts AND before the done
function has been called inside enter.active
.leave.before
show
to hide the element.leave.active
leave.before
.leave.after
display
property is changed to hide the element, which in turn happens right after the done
function is called inside leave.active
.leave.cancel
show
to show the element after leave.active
starts AND before the done
function has been called inside leave.active
.Type-safe transitions
Baleada Features exports a defineTransition
helper function that you can use to define type-safe transition configurations for appear
, enter
, and leave
.
defineTransition
is a no-op—you pass in a transition configuration object as its first and only parameter, and the exact same object gets returned.
Type safety comes from the generic types that you can pass to defineTransition
.
BindElement
A type that describes the type of element being transitioned. Valid types include:
HTMLElement
HTMLElement[]
HTMLElement[][]
- Vue's
Ref
type, with any of the above types passed in as a generic
Normally, you'll use the typeof
operator to infer this type—see example below this table.
TransitionType
'css'
or 'js'
, depending on what kind of transition you're configuring.Here's a code example:
import { ref } from 'vue'
import {
show,
defineTransition,
useElementApi
} from '@baleada/vue-features'
const element = ref<HTMLElement>()
const elementIsShown = ref(false)
show(
element,
elementIsShown,
{
transition: {
appear: true,
// It's recommended to use the `typeof` operator to infer
// the first generic type, instead of manually passing a
// valid type.
//
// The second generic type defines `enter` as a CSS transition.
enter: defineTransition<typeof element, 'css'>({
// `from`, `active`, and `to` are type-checked as strings
from: ..., active: ..., to: ...,
// `end` and `cancel` are type checked as functions
end: ..., cancel: ...,
}),
// You can pass 'js' as the second generic to define a
// type-safe JS transition:
leave: defineTransition<typeof element, 'js'>({
// All properties are type-checked as functions
before: () => { ... },
after: () => { ... },
cancel: () => { ... },
// The `active` function actually has a few different
// signatures. For single elements, it just receives
// the `done` callback as its first and only parameter.
//
// For lists and planes, though, it can receive the `index`
// or the `row` & `column` as parameters, with the `done`
// callback always passed as the last parameter. TypeScript
// will correctly type-check all three of these variations
// with the help of your generic types.
active: (done) => { ... },
}
}
)