Popup är en ramverkskomponent som används tillsammans med andra komponenter för att kunna visa en komponent som overlay eller inline.
Visa kod
<template >
<div >
<button
ref ="popupAnchor"
type ="button"
class ="button button--secondary"
@click ="onClickOpen"
>
Öppna popup
</button >
<i-popup :is-open ="isOpen" :anchor ="$refs.popupAnchor" @close ="onClose" >
<div class ="my-awesome-popup" >
<p >
Träutensilierna i ett tryckeri äro ingalunda en oviktig faktor, för trevnadens,
ordningens och ekonomiens upprätthållande, och dock är det icke sällan som
sorgliga erfarenheter göras på grund af det oförstånd med hvilket kaster,
formbräden och regaler tillverkas och försäljas Kaster som äro dåligt hopkomna
och af otillräckligt.
</p >
<button type ="button" class ="button button--tertiary" @click ="onClickClose" >
Stäng popup
</button >
</div >
</i-popup >
</div >
</template >
<script >
import { defineComponent } from "vue" ;
import { IPopup } from "@fkui/vue" ;
export default defineComponent ({
name : "IPopupExample" ,
components : { IPopup },
data ( ) {
return {
isOpen : false ,
};
},
methods : {
onClickOpen ( ) {
this .isOpen = true ;
},
onClickClose ( ) {
this .isOpen = false ;
},
onClose ( ) {
this .isOpen = false ;
},
},
});
</script >
<style lang ="scss" >
.my-awesome-popup {
border : 1px dashed lightgray;
padding : 2rem ;
background : white;
.button {
margin-bottom : 0px ;
width : auto;
}
}
.popup--overlay .my-awesome-popup {
width : 25rem ;
}
.popup--inline .my-awesome-popup {
width : 100% ;
}
</style >
Som standard har popup enbart box-shadow
när den är overlay och konsumenten ansvarar för all annan styling samt layout.
För att ändra styling beroende på om popup är overlay eller inline så kan du använda klass selector .popup--overlay
och .popup--inline
.
.popup--overlay .my-awesome-popup {
width : 25rem ;
}
.popup--inline .my-awesome-popup {
width : 100% ;
}
Som standard hanterar popup fokus själv genom att flytta fokus till första tabbningsbara element när den öppnas,
samt att fokus återställs när popup stängs.
Om du behöver sätta fokus på ett annat element när popup öppnas kan du
använda focus-element
som tar en funktion som returnerar det element du vill ska få fokus.
<i-popup
+ :focus-element="() => myFocusElement"
>
Du kan stänga av den automatiska fokushanteringen genom att sätta set-focus
till false
.
För att själv sätta fokus i detta fallet kan du lyssna på "open"
eventet
som sänds när popup är synlig och beräkning av position är klar.
<i-popup
+ :set-focus="false"
+ @open="onOpenSetFocus"
>
Komponenten teleporteras till body-elementet som standard när den visas som overlay.
Detta kan ändras till valfritt element genom att ändra värdet för config.teleportTarget
.
Container är den yta som begränsar området som en popup måste hålla sig inom.
Popupens position kommer inte hamna utanför containern (med undantag för om ingen lämplig position kan hittas).
Containern kommer att väljas utfrån följande lista (i ordning):
Explicit element som skickas med container
prop.
En förälder med klassen popup__container
.
Default-värde från config.popupContainer
.
Detta interaktiva exempel visar hur positionering beter sig vid olika placeringar och scrollningar.
Positioneringen föredrar:
Placera popup under istället för ovanför ankare.
Placera popup så den linjerar med vänster kant över höger kant.
"A" - popup under och linjering mot vänster kant.
"B" - popup under och linjering mot höger kant.
"C" - popup ovanför och linjering mot vänster kant.
"D" - popup ovanför och linjering mot höger kant.
"E" - popup höger om ankaren vertikalt centrerad.
"F" - popup vänster om ankaren vertikalt centrerad.
"G" - popup höger om ankaren linjering i toppen av begränsade ytan.
"H" - popup vänster om ankaren linjering i toppen av begränsade ytan.
"I" - popup vertikalt och horisontellt centrerad i begränsade ytan.
Ifall ingen av ovanstående positioner fungerar kommer en fallback användas.
Om inline
är satt till never
positioneras popup under ankare utan linjering.
Om inline
är satt till auto
kommer popup visas inline under ankare.
I följande exempel visas tre varianter:
Viewport
: använder <body>
som yta och fönster som viewport
Container
: använder container som yta, ingen viewport
Viewport + container
: använder container som yta, fönster som viewport
Visa kod
<template >
<div class ="wrapper" >
<label > Begränsa till: </label >
<select v-model ="constraint" @change ="onChangeConstraint" >
<option value ="viewport" > Viewport</option >
<option value ="container" > Container</option >
<option value ="combo" > Viewport + container</option >
</select >
<p > Dra <i > ankaret</i > med hjälp av musen.</p >
<div ref ="area" class ="area" >
<div ref ="anchor" class ="pos-anchor" @mousedown ="onMouseDown" > Ankare</div >
<div ref ="target" class ="pos-target" > Popup</div >
</div >
</div >
</template >
<script >
import { defineComponent } from "vue" ;
import { fitInsideArea, clamp, Placement } from "../IPopupUtils" ;
const SPACING = 10 ;
export default defineComponent ({
name : "IPopupPositioning" ,
data ( ) {
return {
constraint : "viewport" ,
drag : null ,
};
},
computed : {
areaElement ( ) {
switch (this .constraint ) {
case "combo" :
return this .$refs .area ;
case "viewport" :
return document .body ;
case "container" :
return this .$refs .area ;
default :
return undefined ;
}
},
viewportElement ( ) {
switch (this .constraint ) {
case "combo" :
return document .documentElement ;
case "viewport" :
return document .documentElement ;
case "container" :
return undefined ;
default :
return undefined ;
}
},
},
mounted ( ) {
document .addEventListener ("mouseup" , this .onMouseUp );
document .addEventListener ("mousemove" , this .onMouseMove );
this .$nextTick().then (() => {
this .updatePosition ();
});
},
destroy ( ) {
document .removeEventListener ("mouseup" , this .onMouseUp );
document .removeEventListener ("mousemove" , this .onMouseMove );
},
methods : {
onChangeConstraint ( ) {
const { anchor : anchorElement } = this .$refs ;
anchorElement.style .top = "10px" ;
anchorElement.style .left = "10px" ;
},
onMouseDown (event ) {
const { anchor : anchorElement } = this .$refs ;
const { clientX, clientY } = event;
this .drag = [anchorElement.offsetLeft - clientX, anchorElement.offsetTop - clientY];
},
onMouseUp ( ) {
this .drag = null ;
this .updatePosition ();
},
onMouseMove (event ) {
if (!this .drag ) {
return ;
}
event.preventDefault ();
const { anchor : anchorElement, area : areaElement } = this .$refs ;
const { clientX, clientY } = event;
const area = areaElement.getBoundingClientRect ();
const anchor = anchorElement.getBoundingClientRect ();
const left = clamp (
clientX + this .drag [0 ],
SPACING ,
area.width - anchor.width - SPACING - 2 ,
);
const top = clamp (
clientY + this .drag [1 ],
SPACING ,
area.height - anchor.height - SPACING - 2 ,
);
anchorElement.style .left = `${left} px` ;
anchorElement.style .top = `${top} px` ;
this .updatePosition ();
},
updatePosition ( ) {
if (!this .drag ) {
return ;
}
const { anchor, target } = this .$refs ;
const area = this .areaElement ;
const viewport = this .viewportElement ;
if (!anchor) {
return ;
}
const result = fitInsideArea ({
area,
anchor,
target,
viewport,
spacing : SPACING ,
});
if (result.placement === Placement .Fallback ) {
target.classList .add ("pos-target--inline" );
target.style .removeProperty ("left" );
target.style .removeProperty ("top" );
} else {
target.classList .remove ("pos-target--inline" );
target.style .left = `${result.x} px` ;
target.style .top = `${result.y} px` ;
}
target.innerText = `Popup (${result.placement} )` ;
},
},
});
</script >
<style >
.wrapper {
position : relative;
}
.area {
margin-top : 1rem ;
height : 400px ;
border : 1px dashed grey;
position : relative;
}
.pos-anchor {
position : absolute;
display : flex;
align-items : center;
justify-content : center;
width : 75px ;
height : 75px ;
top : 10px ;
left : 10px ;
color : #000 ;
background : rgb (200 , 200 , 255 );
border : 1px solid rgb (0 , 0 , 0 );
cursor : move;
user-select : none;
z-index : 1 ;
}
.pos-target {
position : absolute;
display : flex;
align-items : center;
justify-content : center;
width : 200px ;
height : 250px ;
max-width : calc (100% - 20px );
top : 100px ;
left : 25px ;
background : rgb (255 , 255 , 255 );
border : 1px solid rgb (0 , 0 , 0 );
z-index : 1 ;
}
.pos-target--inline {
position : static;
}
</style >
När popup öppnas som inline kommer den att skrolla så att den är synlig i fönstret.
Popup använder först och främst sin närmaste förälder med klassen scroll-target
som skrollyta.
Om popup inte hittar en förälder med scroll-target
så kommer den istället använda fönstret (window
).
Props Name Description Type Required Default isOpen
Toggle open/closed popup.
boolean
true
‐
anchor
DOM element to position popup at.
HTMLElement | null | undefined
false
undefined
inline
Type of inline behaviour.
"auto"
changes between overlay or inline depending on window size.
"always"
forces the popup to always be inline.
"never"
forces the popup to never be inline.
string
false
"auto"
alwaysInline
Force popup to always display inline.
boolean
false
false
container
Which element to use as container.
HTMLElement | null | undefined
false
undefined
viewport
Which element to use as viewport.
HTMLElement
false
function() {
return document.documentElement;
}
keyboardTrap
Prevents tabbing outside of component.
boolean
false
true
focusElement
Function that returns the element that will receive focus
() => HTMLElement | null
false
null
setFocus
Set focus on first tabbable element (or element in the focusElement
prop if provided) when opened.
boolean
false
true
Events Name Description Properties open
Emitted when popup is visible and placement is fully calculated.
‐
close
‐
‐
Slots Name Description Bindings default
‐
toggleIsOpen
placement