|
38 | 38 | </div>
|
39 | 39 | </template>
|
40 | 40 |
|
41 |
| -<script lang="ts"> |
42 |
| -import * as Popper from '@popperjs/core' |
| 41 | +<script setup lang="ts"> |
| 42 | +// import type {BDropdownEmits, BDropdownProps} from '@/types/components' |
| 43 | +import type Popper from '@popperjs/core' |
43 | 44 | import Dropdown from 'bootstrap/js/dist/dropdown'
|
44 |
| -import {ComponentPublicInstance, computed, defineComponent, onMounted, PropType, ref} from 'vue' |
45 |
| -import BButton from '../../components/BButton/BButton.vue' |
46 |
| -import {ButtonVariant, Size} from '../../types' |
47 |
| -import mergeDeep from '../../utils/mergeDeep' |
48 |
| -import useId from '../../composables/useId' |
49 |
| -import useEventListener from '../../composables/useEventListener' |
50 |
| -import {HTMLElement} from '../../types/safeTypes' |
| 45 | +import {ComponentPublicInstance, computed, onMounted, ref} from 'vue' |
| 46 | +import BButton from '@/components/BButton/BButton.vue' |
| 47 | +import type {ButtonType, ButtonVariant, Size} from '@/types' |
| 48 | +import mergeDeep from '@/utils/mergeDeep' |
| 49 | +import useId from '@/composables/useId' |
| 50 | +import useEventListener from '@/composables/useEventListener' |
51 | 51 |
|
52 |
| -export default defineComponent({ |
53 |
| - name: 'BDropdown', |
54 |
| - components: {BButton}, |
55 |
| - props: { |
56 |
| - autoClose: {type: [Boolean, String], default: true}, |
57 |
| - block: {type: Boolean, default: false}, |
58 |
| - boundary: { |
59 |
| - type: [HTMLElement, String] as PropType<Popper.Boundary>, |
60 |
| - default: 'clippingParents', |
61 |
| - }, |
62 |
| - dark: {type: Boolean, default: false}, |
63 |
| - disabled: {type: Boolean, default: false}, |
64 |
| - dropup: {type: Boolean, default: false}, |
65 |
| - dropright: {type: Boolean, default: false}, |
66 |
| - dropleft: {type: Boolean, default: false}, |
67 |
| - id: {type: String}, |
68 |
| - menuClass: {type: [Array, Object, String]}, |
69 |
| - noFlip: {type: Boolean, default: false}, |
70 |
| - offset: {type: [Number, String], default: 0}, |
71 |
| - popperOpts: {type: Object, default: () => ({})}, |
72 |
| - right: {type: Boolean, default: false}, |
73 |
| - role: {type: String, default: 'menu'}, |
74 |
| - size: {type: String as PropType<Size>}, |
75 |
| - split: {type: Boolean, default: false}, |
76 |
| - splitButtonType: {type: String as PropType<'button' | 'submit' | 'reset'>, default: 'button'}, |
77 |
| - splitClass: {type: [Array, Object, String]}, |
78 |
| - splitHref: {type: String, default: null}, |
79 |
| - noCaret: {type: Boolean, default: false}, |
80 |
| - splitVariant: {type: String as PropType<ButtonVariant>}, |
81 |
| - text: {type: String}, |
82 |
| - toggleClass: {type: [Array, Object, String]}, |
83 |
| - toggleText: {type: String, default: 'Toggle dropdown'}, |
84 |
| - variant: {type: String as PropType<ButtonVariant>, default: 'secondary'}, |
85 |
| - }, |
86 |
| - emits: ['show', 'shown', 'hide', 'hidden'], |
87 |
| - setup(props, {emit}) { |
88 |
| - const parent = ref<HTMLElement>() |
89 |
| - const dropdown = ref<ComponentPublicInstance<HTMLElement>>() |
90 |
| - const instance = ref<Dropdown>() |
91 |
| - const computedId = useId(props.id, 'dropdown') |
| 52 | +// TODO it seems that some of these props are actually just Popper options |
| 53 | +// So some of them could be converted to their pure types similar to Popper.Boundary |
| 54 | +interface BDropdownProps { |
| 55 | + id: string |
| 56 | + menuClass: Array<string> | Record<string, unknown> | string |
| 57 | + size: Size |
| 58 | + splitClass: Array<string> | Record<string, unknown> | string |
| 59 | + splitVariant: ButtonVariant |
| 60 | + text: string |
| 61 | + toggleClass: Array<string> | Record<string, unknown> | string |
| 62 | + autoClose?: boolean | 'inside' | 'outside' |
| 63 | + block?: boolean |
| 64 | + boundary?: Popper.Boundary |
| 65 | + dark?: boolean |
| 66 | + disabled?: boolean |
| 67 | + dropup?: boolean |
| 68 | + dropright?: boolean |
| 69 | + dropleft?: boolean |
| 70 | + noFlip?: boolean |
| 71 | + offset?: number | string |
| 72 | + popperOpts?: Record<string, unknown> |
| 73 | + right?: boolean |
| 74 | + role?: string |
| 75 | + split?: boolean |
| 76 | + splitButtonType?: ButtonType |
| 77 | + splitHref?: string |
| 78 | + noCaret?: boolean |
| 79 | + toggleText?: string |
| 80 | + variant?: ButtonVariant |
| 81 | +} |
| 82 | +
|
| 83 | +const props = withDefaults(defineProps<BDropdownProps>(), { |
| 84 | + autoClose: true, |
| 85 | + block: false, |
| 86 | + boundary: 'clippingParents', |
| 87 | + dark: false, |
| 88 | + disabled: false, |
| 89 | + dropup: false, |
| 90 | + dropright: false, |
| 91 | + dropleft: false, |
| 92 | + noFlip: false, |
| 93 | + offset: 0, |
| 94 | + popperOpts: () => ({}), |
| 95 | + right: false, |
| 96 | + role: 'menu', |
| 97 | + split: false, |
| 98 | + splitButtonType: 'button', |
| 99 | + splitHref: undefined, |
| 100 | + noCaret: false, |
| 101 | + toggleText: 'Toggle dropdown', |
| 102 | + variant: 'secondary', |
| 103 | +}) |
92 | 104 |
|
93 |
| - useEventListener(parent, 'show.bs.dropdown', () => emit('show')) |
94 |
| - useEventListener(parent, 'shown.bs.dropdown', () => emit('shown')) |
95 |
| - useEventListener(parent, 'hide.bs.dropdown', () => emit('hide')) |
96 |
| - useEventListener(parent, 'hidden.bs.dropdown', () => emit('hidden')) |
| 105 | +interface BDropdownEmits { |
| 106 | + (e: 'show'): void |
| 107 | + (e: 'shown'): void |
| 108 | + (e: 'hide'): void |
| 109 | + (e: 'hidden'): void |
| 110 | +} |
97 | 111 |
|
98 |
| - const classes = computed(() => ({ |
99 |
| - 'd-grid': props.block, |
100 |
| - 'd-flex': props.block && props.split, |
101 |
| - })) |
| 112 | +const emit = defineEmits<BDropdownEmits>() |
102 | 113 |
|
103 |
| - const buttonClasses = computed(() => ({ |
104 |
| - 'dropdown-toggle': !props.split, |
105 |
| - 'dropdown-toggle-no-caret': props.noCaret && !props.split, |
106 |
| - 'w-100': props.split && props.block, |
107 |
| - })) |
| 114 | +const parent = ref<HTMLElement>() |
| 115 | +const dropdown = ref<ComponentPublicInstance<HTMLElement>>() |
| 116 | +const instance = ref<Dropdown>() |
| 117 | +const computedId = useId(props.id, 'dropdown') |
108 | 118 |
|
109 |
| - const dropdownMenuClasses = computed(() => ({ |
110 |
| - 'dropdown-menu-dark': props.dark, |
111 |
| - })) |
| 119 | +useEventListener(parent, 'show.bs.dropdown', () => emit('show')) |
| 120 | +useEventListener(parent, 'shown.bs.dropdown', () => emit('shown')) |
| 121 | +useEventListener(parent, 'hide.bs.dropdown', () => emit('hide')) |
| 122 | +useEventListener(parent, 'hidden.bs.dropdown', () => emit('hidden')) |
112 | 123 |
|
113 |
| - const buttonAttr = computed(() => ({ |
114 |
| - 'data-bs-toggle': props.split ? null : 'dropdown', |
115 |
| - 'aria-expanded': props.split ? null : false, |
116 |
| - 'ref': props.split ? null : dropdown, |
117 |
| - 'href': props.split ? props.splitHref : null, |
118 |
| - })) |
| 124 | +const classes = computed(() => ({ |
| 125 | + 'd-grid': props.block, |
| 126 | + 'd-flex': props.block && props.split, |
| 127 | +})) |
119 | 128 |
|
120 |
| - const splitAttr = computed(() => ({ |
121 |
| - ref: props.split ? dropdown : null, |
122 |
| - })) |
| 129 | +const buttonClasses = computed(() => ({ |
| 130 | + 'dropdown-toggle': !props.split, |
| 131 | + 'dropdown-toggle-no-caret': props.noCaret && !props.split, |
| 132 | + 'w-100': props.split && props.block, |
| 133 | +})) |
123 | 134 |
|
124 |
| - const hide = () => { |
125 |
| - instance.value?.hide() |
126 |
| - } |
| 135 | +const dropdownMenuClasses = computed(() => ({ |
| 136 | + 'dropdown-menu-dark': props.dark, |
| 137 | +})) |
127 | 138 |
|
128 |
| - onMounted(() => { |
129 |
| - instance.value = new Dropdown( |
130 |
| - dropdown.value?.$el, |
131 |
| - { |
132 |
| - autoClose: props.autoClose, |
133 |
| - boundary: props.boundary, |
134 |
| - offset: props.offset.toString(), |
135 |
| - reference: props.offset || props.split ? 'parent' : 'toggle', |
136 |
| - popperConfig: (defaultConfig?: Partial<Popper.Options>) => { |
137 |
| - const dropDownConfig = { |
138 |
| - placement: 'bottom-start', |
139 |
| - modifiers: !props.noFlip |
140 |
| - ? [] |
141 |
| - : [ |
142 |
| - { |
143 |
| - name: 'flip', |
144 |
| - options: { |
145 |
| - fallbackPlacements: [], |
146 |
| - }, |
147 |
| - }, |
148 |
| - ], |
149 |
| - } |
| 139 | +const buttonAttr = computed(() => ({ |
| 140 | + 'data-bs-toggle': props.split ? undefined : 'dropdown', |
| 141 | + 'aria-expanded': props.split ? undefined : false, |
| 142 | + 'ref': props.split ? undefined : dropdown, |
| 143 | + 'href': props.split ? props.splitHref : undefined, |
| 144 | +})) |
150 | 145 |
|
151 |
| - if (props.dropup) { |
152 |
| - dropDownConfig.placement = props.right ? 'top-end' : 'top-start' |
153 |
| - } else if (props.dropright) { |
154 |
| - dropDownConfig.placement = 'right-start' |
155 |
| - } else if (props.dropleft) { |
156 |
| - dropDownConfig.placement = 'left-start' |
157 |
| - } else if (props.right) { |
158 |
| - dropDownConfig.placement = 'bottom-end' |
159 |
| - } |
160 |
| - return mergeDeep(defaultConfig, mergeDeep(dropDownConfig, props.popperOpts)) |
161 |
| - }, |
162 |
| - } as unknown as Dropdown.Options /* TODO: remove when added in Dropdown options by https://www.npmjs.com/package/@types/bootstrap */ |
163 |
| - ) |
164 |
| - }) |
| 146 | +const splitAttr = computed(() => ({ |
| 147 | + ref: props.split ? dropdown : undefined, |
| 148 | +})) |
165 | 149 |
|
166 |
| - return { |
167 |
| - parent, |
168 |
| - computedId, |
169 |
| - classes, |
170 |
| - buttonClasses, |
171 |
| - buttonAttr, |
172 |
| - splitAttr, |
173 |
| - dropdownMenuClasses, |
174 |
| - dropdown, |
175 |
| - hide, |
176 |
| - } |
177 |
| - }, |
| 150 | +const hide = (): void => { |
| 151 | + instance.value?.hide() |
| 152 | +} |
| 153 | +
|
| 154 | +onMounted((): void => { |
| 155 | + instance.value = new Dropdown(dropdown.value?.$el, { |
| 156 | + autoClose: props.autoClose, |
| 157 | + boundary: props.boundary, |
| 158 | + offset: props.offset ? props.offset.toString() : '', |
| 159 | + reference: props.offset || props.split ? 'parent' : 'toggle', |
| 160 | + popperConfig: (defaultConfig?: Partial<Popper.Options>) => { |
| 161 | + const dropDownConfig = { |
| 162 | + placement: 'bottom-start', |
| 163 | + modifiers: !props.noFlip |
| 164 | + ? [] |
| 165 | + : [ |
| 166 | + { |
| 167 | + name: 'flip', |
| 168 | + options: { |
| 169 | + fallbackPlacements: [], |
| 170 | + }, |
| 171 | + }, |
| 172 | + ], |
| 173 | + } |
| 174 | +
|
| 175 | + if (props.dropup) { |
| 176 | + dropDownConfig.placement = props.right ? 'top-end' : 'top-start' |
| 177 | + } else if (props.dropright) { |
| 178 | + dropDownConfig.placement = 'right-start' |
| 179 | + } else if (props.dropleft) { |
| 180 | + dropDownConfig.placement = 'left-start' |
| 181 | + } else if (props.right) { |
| 182 | + dropDownConfig.placement = 'bottom-end' |
| 183 | + } |
| 184 | + return mergeDeep(defaultConfig, mergeDeep(dropDownConfig, props.popperOpts)) |
| 185 | + }, |
| 186 | + }) |
178 | 187 | })
|
179 | 188 | </script>
|
0 commit comments