@jblehm/super-list
v1.0.41
Published
A Vue Combobox
Readme
superlist V1.0.41
A minimally-styled, mobile supported searchable combobox for Vue.
Example styled component implementation in Vue:
Import the following style where the component is used:
<style src="@jblehm/super-list/dist/index.css" />The list component has the following Vue emits which can be used for v-model binding:
const emit = defineEmits({ 'update:selected': null })The list component can be passed a function or an array of options. If a function is passed, it should match the ListRequest type:
type ListRequest = (maxNumItems: number, stringFilter?: string) => Promise<{ data: T[]; totalNum: number; }>The list component has the following Vue properties:
selected: {
type: [String, Number, Object, null, undefined] as PropType<
string | number | object | null | undefined
> // the currently selected item
},
options: {
type: [Function, Array<string | number | object>] as PropType<
ListRequest | Array<string | number | object> //type ListRequest = (maxNumItems: number, stringFilter?: string) => Promise<{ data: T[]; totalNum: number; }>
>,
required: true
},
maxListOptions: {
type: Number as PropType<number>, // maximum number of options to show before enabling the text filter input box
default: 50
},
maxListHeightPX: {
type: Number as PropType<number>, // maximum height of the options list in pixels before scrolling is enabled
default: 200
},
objectLabelKeyName: {
type: String as PropType<string | null>, // when options are objects, the key name to use for the displayed label
default: ''
},
enumKeyToLabelObjectArray: {
type: Object as PropType<EnumType[]>, // type EnumType = { type: string; label: string }
default: undefined
},
listAnimationDurationMs: {
type: Number as PropType<number>, // how long the open/close animation takes in milliseconds
default: 300
},
customIcon: {
type: [Object, Function] as PropType<object | Function | null>, // custom vue icon component to use instead of the default arrows SVG
default: null
},
forceTextFilterVisibilityTo: {
type: Boolean as PropType<boolean>, // Display the text filter always (true) or never (false). Default behavior is to only show when options exceed maxListOptions
default: undefined
},
customPlaceHolderFunction: {
type: Function as PropType<Function | null>, // Display custom placeholder text in the filter input box regardless of how the selected item text appears elsewhere
default: null
},
scrollTextInputToTopOnMobile: {
type: Boolean as PropType<boolean>, // Scroll the text input to the top of the viewport on mobile when the input is focused
default: true
}Example styled code:
<template>
<SuperList
ref="dropDownComponent"
:options="options"
v-model:selected="updatedSelected"
:object-label-key-name="objectLabelKeyName"
:max-list-options="5"
/>
</template>
<script lang="ts">
import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import SuperList from '../super-list.vue'
import { ListRequest } from '../DropDownLibrary'
export default defineComponent({
name: 'DemoStyledList',
components: { SuperList },
emits: ['update:selected'],
props: {
selected: {
type: [String, Number, Object, null, undefined] as PropType<
string | number | object | null | undefined
>,
default: ''
},
options: {
type: [Array, Function] as PropType<string[] | number[] | object[] | ListRequest>,
required: true
},
objectLabelKeyName: {
type: String as PropType<string | null>,
default: ''
}
},
data() {
return {
updatedSelected: '' as string | number | object
}
},
watch: {
updatedSelected: {
handler(newValue) {
if (newValue && newValue !== this.selected) {
this.$emit('update:selected', newValue) // for v-model:selected="boundValue" and @update:selected="boundValue = $event"
}
}
},
selected: {
handler(newValue) {
if (newValue) this.updatedSelected = newValue
},
deep: true,
immediate: true
}
}
})
</script>
<style src="@jblehm/super-list/dist/index.css" />
<style>
.list-button {
padding-top: 2px !important;
padding-bottom: 2px !important;
display: inline-flex;
background-color: rgb(248, 249, 250);
color: rgb(41 51 61 / 1);
border: 1px solid rgb(209 213 219 / 1) !important;
font-size: 0.875rem !important;
border-radius: 0.375rem;
justify-content: center;
text-align: center;
font-weight: 600 !important;
opacity: 1;
translate: none;
transition-property:
outline-color, background, background-color, outline-width, outline-offset, box-shadow,
translate, filter, opacity, color, text-shadow;
transition-timing-function: ease-in-out;
transition-duration: 175ms;
outline-offset: 0.125rem;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 0.55));
outline: transparent solid 0.1rem;
}
.list-button,
.list-filter-text-input,
.list-filter-text-input::placeholder {
color: rgb(17, 24, 39);
}
.list-button,
.list-filter-text-input {
background-color: rgb(248, 249, 250);
color: rgb(41 51 61 / 1);
box-shadow: 1px 1px 5px 0 rgb(0, 0, 0, 0.1);
font-size: 0.875rem !important;
font-weight: 600 !important;
opacity: 1;
transition-property:
outline-color, background, background-color, outline-width, outline-offset, box-shadow,
translate, filter, opacity, color, text-shadow;
transition-timing-function: ease-in-out;
transition-duration: 175ms;
outline-offset: 0.25rem !important;
outline: transparent solid 0.2rem;
}
.list-filter-text-input {
outline-offset: 0.25rem !important;
}
.list-filter-text-input {
border: none;
border-radius: calc(0.375rem - 1px) !important;
background: transparent !important;
}
.list-button:hover:not(:active) {
transition-duration: 75ms;
box-shadow:
1px 2px 3px 2px rgb(0, 0, 0, 0.25),
inset 1px 1px 5px 2px rgb(0, 0, 0, 0);
translate: -0.0875rem -0.05rem;
opacity: 0.9;
background-color: white;
color: black;
}
.list-button:focus,
.list-button:active,
.list-button:focus:active,
.list-button:focus:hover,
.list-button:active:hover,
.list-button:focus:active:hover,
.list-filter-text-input:focus,
.list-filter-text-input:active,
.list-filter-text-input:focus:active,
.list-filter-text-input:focus:hover,
.list-filter-text-input:active:hover,
.list-filter-text-input:focus:active:hover {
translate: none;
transition-duration: 75ms;
opacity: 1;
outline: rgb(77 168 11) solid 0.125rem !important;
border: none !important;
}
.select-list, .list-button, .list-filter-text-input {
font-family: sans-serif;
--superlist-list-border-radius: 0.5rem;
}
.select-list.list-normal {
box-shadow:
0 2px 3px 0 rgb(0 0 0 / 0.1),
0 3px 3px -2px rgb(0 0 0 / 0.1),
2px 4px 5px 2px rgba(0, 0, 0, 0.25);
}
.select-list.list-reverse {
box-shadow:
0 -2px 3px 0 rgb(0 0 0 / 0.1),
0 -3px 3px -2px rgb(0 0 0 / 0.1),
-2px -4px 5px 2px rgba(0, 0, 0, 0.25);
}
</style>
