@@ -61,6 +61,21 @@ const selectArray = [
6161 },
6262 },
6363
64+ {
65+ title: 'Simple (top placement)',
66+ props: {
67+ inputId: getRandomId(),
68+ placement: 'top',
69+ options: [
70+ 'foo',
71+ 'bar',
72+ 'baz',
73+ 'qux',
74+ 'quux',
75+ ],
76+ },
77+ },
78+
6479 {
6580 title: 'Multiple (with placeholder)',
6681 props: {
@@ -508,6 +523,14 @@ export default {
508523<script >
509524import VueSelect from ' vue-select'
510525import ' vue-select/dist/vue-select.css'
526+ import {
527+ autoUpdate ,
528+ computePosition ,
529+ flip ,
530+ limitShift ,
531+ offset ,
532+ shift ,
533+ } from ' @floating-ui/dom'
511534
512535import ChevronDown from ' vue-material-design-icons/ChevronDown.vue'
513536import Close from ' vue-material-design-icons/Close.vue'
@@ -537,6 +560,32 @@ export default {
537560 // Add VueSelect props to $props
538561 ... VueSelect .props ,
539562
563+ /**
564+ * Append the dropdown element to the end of the body
565+ * and size/position it dynamically.
566+ *
567+ * @see https://vue-select.org/api/props.html#appendtobody
568+ */
569+ appendToBody: {
570+ type: Boolean ,
571+ default: true ,
572+ },
573+
574+ /**
575+ * When `appendToBody` is true, this function is responsible for
576+ * positioning the drop down list.
577+ *
578+ * If a function is returned from `calculatePosition`, it will
579+ * be called when the drop down list is removed from the DOM.
580+ * This allows for any garbage collection you may need to do.
581+ *
582+ * @see https://vue-select.org/api/props.html#calculateposition
583+ */
584+ calculatePosition: {
585+ type: Function ,
586+ default: null ,
587+ },
588+
540589 /**
541590 * Close the dropdown when selecting an option
542591 *
@@ -673,6 +722,16 @@ export default {
673722 default: ' ' ,
674723 },
675724
725+ /**
726+ * When `appendToBody` is true, this sets the placement of the dropdown
727+ *
728+ * @type {'bottom' | 'top'}
729+ */
730+ placement: {
731+ type: String ,
732+ default: ' bottom' ,
733+ },
734+
676735 /**
677736 * Enable the user selector with avatars
678737 *
@@ -723,6 +782,66 @@ export default {
723782 },
724783
725784 computed: {
785+ localCalculatePosition () {
786+ if (this .calculatePosition !== null ) {
787+ return this .calculatePosition
788+ }
789+
790+ return (dropdownMenu , component , { width }) => {
791+ dropdownMenu .style .width = width
792+
793+ const addClass = {
794+ name: ' addClass' ,
795+ fn (_middlewareArgs ) {
796+ dropdownMenu .classList .add (' vs__dropdown-menu--floating' )
797+ return {}
798+ },
799+ }
800+
801+ const togglePlacementClass = {
802+ name: ' togglePlacementClass' ,
803+ fn ({ placement }) {
804+ component .$el .classList .toggle (
805+ ' select--drop-up' ,
806+ placement === ' top' ,
807+ )
808+ dropdownMenu .classList .toggle (
809+ ' vs__dropdown-menu--floating-placement-top' ,
810+ placement === ' top' ,
811+ )
812+ return {}
813+ },
814+ }
815+
816+ const updatePosition = () => {
817+ computePosition (component .$refs .toggle , dropdownMenu, {
818+ placement: this .placement ,
819+ middleware: [
820+ offset (- 1 ),
821+ addClass,
822+ togglePlacementClass,
823+ // Match popperjs default collision prevention behavior by appending the following middleware in order
824+ flip (),
825+ shift ({ limiter: limitShift () }),
826+ ],
827+ }).then (({ x, y }) => {
828+ Object .assign (dropdownMenu .style , {
829+ left: ` ${ x} px` ,
830+ top: ` ${ y} px` ,
831+ })
832+ })
833+ }
834+
835+ const cleanup = autoUpdate (
836+ component .$refs .toggle ,
837+ dropdownMenu,
838+ updatePosition,
839+ )
840+
841+ return cleanup
842+ }
843+ },
844+
726845 localFilterBy () {
727846 if (this .filterBy !== null ) {
728847 return this .filterBy
@@ -752,17 +871,20 @@ export default {
752871 propsToForward () {
753872 const {
754873 // Custom overrides of vue-select props
874+ calculatePosition,
755875 filterBy,
756876 label,
757877 // Props handled by the component itself
758878 noWrap,
879+ placement,
759880 userSelect,
760881 // Props to forward
761882 ... initialPropsToForward
762883 } = this .$props
763884
764885 const propsToForward = {
765886 ... initialPropsToForward,
887+ calculatePosition: this .localCalculatePosition ,
766888 label: this .localLabel ,
767889 }
768890
@@ -776,8 +898,8 @@ export default {
776898}
777899 </script >
778900
779- <style lang="scss" scoped >
780- .select {
901+ <style lang="scss">
902+ :root {
781903 /* Set custom vue-select CSS variables */
782904
783905 /* Search Input */
@@ -826,26 +948,54 @@ export default {
826948
827949 /* Transitions */
828950 --vs-transition-duration : 0ms ;
951+ }
829952
953+ .v-select.select {
830954 /* Override default vue-select styles */
831955 min-height : $clickable-area ;
832956 min-width : 260px ;
833957 margin : 0 ;
834958
959+ .vs__selected {
960+ min-height : 36px ;
961+ padding : 0 0.5em ;
962+ }
963+
964+ .vs__clear {
965+ margin-right : 2px ;
966+ }
967+
835968 & --no-wrap {
836- & : deep ( .vs__selected-options ) {
969+ .vs__selected-options {
837970 flex-wrap : nowrap ;
838971 overflow : auto ;
839972 }
840973 }
841974
842- & :deep(.vs__selected ) {
843- min-height : 36px ;
844- padding : 0 0.5em ;
975+ & --drop-up {
976+ & .vs--open {
977+ .vs__dropdown-toggle {
978+ border-radius : 0 0 var (--vs-border-radius ) var (--vs-border-radius );
979+ border-top-color : transparent ;
980+ border-bottom-color : var (--vs-border-color );
981+ }
982+ }
845983 }
984+ }
846985
847- & :deep(.vs__clear ) {
848- margin-right : 2px ;
986+ .vs__dropdown-menu {
987+ & --floating {
988+ width : max-content ;
989+ position : absolute ;
990+ top : 0 ;
991+ left : 0 ;
992+
993+ & -placement-top {
994+ border-radius : var (--vs-border-radius ) var (--vs-border-radius ) 0 0 ;
995+ border-top-style : var (--vs-border-style );
996+ border-bottom-style : none ;
997+ box-shadow : 0px -1px 1px 0px var (--color-box-shadow );
998+ }
849999 }
8501000}
8511001 </style >
0 commit comments