Skip to content

Commit dded463

Browse files
committed
refactor(BContainer): use template syntax
chore(BContainer): comment out unnecessary toast instances chore(BModal): remove type button on closebutton chore(BOffcanvas): remove type button on closebutton test: container.spec.ts chore(BFormTag): remove type button on closebutton fix(BToaster): experiment with adding an id attr for teleporting fix(BToast): experiment with fixes for BToast, not finished! test: toast.spec.ts -> will be outdated in V2 of component
1 parent b7106f4 commit dded463

File tree

8 files changed

+477
-172
lines changed

8 files changed

+477
-172
lines changed
Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,48 @@
1-
<script lang="ts">
2-
import {computed, defineComponent, h, onMounted, PropType, ref, VNode} from 'vue'
3-
import type {Breakpoint, Position} from '../types'
4-
import BToaster from './BToast/BToaster.vue'
5-
import {ToastInstance, useToast} from './BToast/plugin'
6-
export default defineComponent({
7-
name: 'BContainer',
8-
props: {
9-
gutterX: {type: String, default: null},
10-
gutterY: {type: String, default: null},
11-
fluid: {type: [Boolean, String] as PropType<boolean | Breakpoint>, default: false},
12-
toast: {type: Object},
13-
position: {type: String as PropType<Position>, required: false},
14-
tag: {type: String, default: 'div'},
15-
},
16-
setup(props, {slots, expose}) {
17-
const container = ref()
18-
let toastInstance: ToastInstance | undefined
1+
<template>
2+
<component :is="tag" ref="container" :class="[classes, position]">
3+
<!-- <b-toaster
4+
v-for="(pos, index) in toasts"
5+
:key="index"
6+
:instance="toastInstance"
7+
:position="pos"
8+
/> -->
9+
<slot />
10+
</component>
11+
</template>
12+
13+
<script setup lang="ts">
14+
import {computed, ref} from 'vue'
15+
import type {Position} from '../types'
16+
// import BToaster from './BToast/BToaster.vue'
17+
// import {ToastInstance} from './BToast/plugin'
18+
19+
interface Props {
20+
gutterX?: string
21+
gutterY?: string
22+
fluid?: boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' // boolean | Breakpoint
23+
toast?: Record<string, unknown> // Make this strongly typed
24+
position?: Position
25+
tag?: string
26+
}
27+
28+
const props = withDefaults(defineProps<Props>(), {
29+
fluid: false,
30+
tag: 'div',
31+
})
1932
20-
const classes = computed(() => ({
21-
container: !props.fluid,
22-
[`container-fluid`]: typeof props.fluid === 'boolean' && props.fluid,
23-
[`container-${props.fluid}`]: typeof props.fluid === 'string',
24-
[`gx-${props.gutterX}`]: props.gutterX !== null,
25-
[`gy-${props.gutterY}`]: props.gutterY !== null,
26-
}))
33+
const container = ref()
34+
35+
const classes = computed(() => ({
36+
container: props.fluid === false,
37+
[`container-fluid`]: props.fluid === true,
38+
[`container-${props.fluid}`]: typeof props.fluid === 'string',
39+
[`gx-${props.gutterX}`]: props.gutterX !== undefined,
40+
[`gy-${props.gutterY}`]: props.gutterY !== undefined,
41+
}))
42+
43+
/* TODO finish this system
44+
const toasts = computed(() => toastInstance?.containerPositions.value)
45+
let toastInstance: ToastInstance | undefined
2746
2847
onMounted(() => {
2948
if (props.toast) {
@@ -51,7 +70,6 @@ export default defineComponent({
5170
slots.default?.(),
5271
])
5372
}
54-
},
55-
methods: {},
56-
})
73+
}
74+
*/
5775
</script>

packages/bootstrap-vue-3/src/components/BFormTags/BFormTag.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
<b-close-button
1414
v-if="!disabledBoolean && !noRemoveBoolean"
1515
aria-keyshortcuts="Delete"
16-
type="button"
1716
:aria-label="removeLabel"
1817
class="b-form-tag-remove"
1918
:white="!['warning', 'info', 'light'].includes(variant)"

packages/bootstrap-vue-3/src/components/BModal.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
</button>
3737
<b-close-button
3838
v-else
39-
type="button"
4039
:aria-label="headerCloseLabel"
4140
data-bs-dismiss="modal"
4241
:white="headerCloseWhiteBoolean"

packages/bootstrap-vue-3/src/components/BOffcanvas.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
</h5>
1818
<b-close-button
1919
v-if="!noHeaderCloseBoolean"
20-
type="button"
2120
class="text-reset"
2221
data-bs-dismiss="offcanvas"
2322
:aria-label="dismissLabel"
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
<template>
2+
<teleport v-bind="teleportAttrs">
3+
<div
4+
:id="id"
5+
class="b-toast"
6+
:class="solidBoolean ? 'b-toast-solid' : ''"
7+
:role="computedRole"
8+
:aria-live="computedAriaLive"
9+
:aria-atomic="computedAriaAtomic"
10+
@mouseenter="pauseTimer"
11+
@mouseleave="unPauseTimer"
12+
>
13+
<b-transition
14+
:no-fade="noFadeBoolean"
15+
@before-enter="onBeforeEnter"
16+
@after-enter="onAfterEnter"
17+
@before-leave="onBeforeLeave"
18+
@after-leave="onAfterLeave"
19+
>
20+
<div v-if="modelValueBoolean" class="toast" :class="toastClasses" tabindex="0">
21+
<component
22+
:is="headerTag"
23+
v-if="!!$slots.title || !!title"
24+
:class="headerClass"
25+
class="toast-header"
26+
>
27+
<slot name="title" :hide="hide">
28+
<strong class="me-auto">
29+
{{ title }}
30+
</strong>
31+
</slot>
32+
<b-close-button v-if="!noCloseButtonBoolean" @click="hide()" />
33+
</component>
34+
<component
35+
:is="computedTag"
36+
v-bind="computedLinkProps"
37+
v-if="!!$slots.default || !!body"
38+
class="toast-body"
39+
:class="bodyClass"
40+
@click="onBodyClick"
41+
>
42+
<slot :hide="hide">{{ body }}</slot>
43+
</component>
44+
</div>
45+
</b-transition>
46+
</div>
47+
</teleport>
48+
</template>
49+
50+
<script lang="ts">
51+
import {computed, defineComponent, onMounted, PropType, ref, toRef} from 'vue'
52+
import {isLink, pluckProps, toInteger} from '../../utils'
53+
import {useBooleanish} from '../../composables'
54+
import type {Booleanish, ClassValue, ColorVariant} from '../../types'
55+
import BTransition from '../BTransition/BTransition.vue'
56+
import BCloseButton from '../BButton/BCloseButton.vue'
57+
import BLink, {BLINK_PROPS} from '../BLink/BLink.vue'
58+
59+
export default defineComponent({
60+
components: {BLink, BTransition, BCloseButton},
61+
props: {
62+
...BLINK_PROPS,
63+
delay: {type: Number, default: 5000},
64+
bodyClass: {type: Object as PropType<ClassValue>, required: false},
65+
body: {type: String, required: false},
66+
headerClass: {type: Object as PropType<ClassValue>, required: false},
67+
headerTag: {type: String, default: 'div'},
68+
id: {type: String, required: false},
69+
isStatus: {type: [Boolean, String] as PropType<Booleanish>, default: false},
70+
autoHide: {type: [Boolean, String] as PropType<Booleanish>, default: true},
71+
noCloseButton: {type: [Boolean, String] as PropType<Booleanish>, default: false},
72+
noFade: {type: [Boolean, String] as PropType<Booleanish>, default: false},
73+
noHoverPause: {type: [Boolean, String] as PropType<Booleanish>, default: false},
74+
solid: {type: [Boolean, String] as PropType<Booleanish>, default: false},
75+
static: {type: [Boolean, String] as PropType<Booleanish>, default: false},
76+
title: {type: String, required: false},
77+
modelValue: {type: [Boolean, String] as PropType<Booleanish>, default: false},
78+
toastClass: {type: Object as PropType<ClassValue>, required: false},
79+
variant: {type: String as PropType<ColorVariant>, required: false},
80+
// TODO get the toaster right for b-toaster
81+
toaster: {type: String, default: 'b-toaster-top-right'},
82+
},
83+
emits: ['update:modelValue', 'hide', 'hidden', 'show', 'shown', 'paused', 'unPaused'],
84+
setup(props, {emit}) {
85+
const MIN_DURATION = 1000
86+
87+
const isStatusBoolean = useBooleanish(toRef(props, 'isStatus'))
88+
const autoHideBoolean = useBooleanish(toRef(props, 'autoHide'))
89+
const noCloseButtonBoolean = useBooleanish(toRef(props, 'noCloseButton'))
90+
const noFadeBoolean = useBooleanish(toRef(props, 'noFade'))
91+
const noHoverPauseBoolean = useBooleanish(toRef(props, 'noHoverPause'))
92+
// TODO even though solid correctly appears in the class list,
93+
// The basic toast does not have a translucent background
94+
const solidBoolean = useBooleanish(toRef(props, 'solid'))
95+
const staticBoolean = useBooleanish(toRef(props, 'static'))
96+
const modelValueBoolean = useBooleanish(toRef(props, 'modelValue'))
97+
98+
const isMounted = ref(false)
99+
onMounted(() => {
100+
isMounted.value = true
101+
})
102+
103+
const toastClasses = computed(() => [
104+
props.toastClass,
105+
{
106+
[`b-toast-${props.variant}`]: props.variant !== undefined,
107+
show: modelValueBoolean.value,
108+
},
109+
])
110+
111+
const computedRole = computed(() =>
112+
!modelValueBoolean.value ? undefined : isStatusBoolean.value ? 'status' : 'alert'
113+
)
114+
115+
const computedAriaLive = computed(() =>
116+
!modelValueBoolean.value ? undefined : isStatusBoolean.value ? 'polite' : 'assertive'
117+
)
118+
119+
const computedAriaAtomic = computed(() => (!modelValueBoolean.value ? undefined : 'true'))
120+
121+
const computedTag = computed<'div' | typeof BLink>(() => (isLink(props) ? BLink : 'div'))
122+
123+
const hide = () => {
124+
emit('update:modelValue', false)
125+
}
126+
127+
const onBodyClick = () => {
128+
if (!isLink(props)) return
129+
hide()
130+
}
131+
132+
// This would be a lot better if the timer was separated from the logic and was an external function
133+
// create a timer
134+
let dismissTimer: ReturnType<typeof setTimeout> | undefined
135+
let dismissStarted: number
136+
let resumeDismiss: number
137+
const computedDuration = computed(() => Math.max(toInteger(props.delay, 0), MIN_DURATION))
138+
// start the timer
139+
const startDismissTimer = () => {
140+
clearDismissTimer()
141+
dismissTimer = setTimeout(hide, resumeDismiss || computedDuration.value)
142+
dismissStarted = Date.now()
143+
resumeDismiss = 0
144+
}
145+
// stop the timer
146+
const clearDismissTimer = () => {
147+
if (dismissTimer !== undefined) return
148+
clearTimeout(dismissTimer)
149+
dismissTimer = undefined
150+
}
151+
// reset timer
152+
const resetTimer = () => {
153+
clearDismissTimer()
154+
dismissStarted = resumeDismiss = 0
155+
}
156+
// pause the timer
157+
const pauseTimer = () => {
158+
if (!autoHideBoolean.value || noHoverPauseBoolean.value || !dismissTimer || resumeDismiss)
159+
return
160+
const passed = Date.now() - dismissStarted
161+
if (passed > 0) {
162+
emit('paused', passed)
163+
clearDismissTimer()
164+
resumeDismiss = Math.max(computedDuration.value - passed, MIN_DURATION)
165+
}
166+
}
167+
const unPauseTimer = () => {
168+
if (!autoHideBoolean.value || noHoverPauseBoolean.value || !resumeDismiss) {
169+
resumeDismiss = dismissStarted = 0
170+
return
171+
}
172+
emit('unPaused')
173+
startDismissTimer()
174+
}
175+
176+
// Emit before transition starts
177+
const onBeforeEnter = () => {
178+
emit('show')
179+
}
180+
// Emit after transition entering ends
181+
// Also responsible for starting the timer
182+
const onAfterEnter = () => {
183+
emit('shown')
184+
if (autoHideBoolean.value) {
185+
startDismissTimer()
186+
}
187+
}
188+
// Emit before transition leaves
189+
const onBeforeLeave = () => {
190+
emit('hide')
191+
}
192+
// Emit after transition leaving ends
193+
// Also responsible for resetting the timer
194+
const onAfterLeave = () => {
195+
emit('hidden')
196+
if (autoHideBoolean.value) {
197+
resetTimer()
198+
}
199+
}
200+
201+
const teleportAttrs = computed(() => ({
202+
// to: `#${props.toaster}`,
203+
to: isMounted.value ? `#${props.toaster}` : 'body',
204+
disabled: staticBoolean.value,
205+
}))
206+
207+
const computedLinkProps = computed(() => (isLink(props) ? pluckProps(props, BLINK_PROPS) : {}))
208+
209+
return {
210+
computedLinkProps,
211+
computedRole,
212+
computedAriaLive,
213+
computedAriaAtomic,
214+
pauseTimer,
215+
unPauseTimer,
216+
noFadeBoolean,
217+
onBeforeEnter,
218+
onAfterEnter,
219+
onBeforeLeave,
220+
onAfterLeave,
221+
modelValueBoolean,
222+
toastClasses,
223+
hide,
224+
noCloseButtonBoolean,
225+
computedTag,
226+
onBodyClick,
227+
teleportAttrs,
228+
solidBoolean,
229+
}
230+
},
231+
})
232+
</script>

packages/bootstrap-vue-3/src/components/BToast/BToaster.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div :class="[positionClass]" class="b-toaster position-fixed p-3" style="z-index: 11">
2+
<div :id="name" :class="[positionClass]" class="b-toaster position-fixed p-3" style="z-index: 11">
33
<b-toast
44
v-for="toast in instance?.toasts(position).value"
55
:id="toast.options.id"
@@ -25,11 +25,14 @@ import BToast from './BToast.vue'
2525
interface BToasterProps {
2626
position?: ContainerPosition
2727
instance?: ToastInstance
28+
name?: string
2829
// appendToast?: Booleanish
2930
}
3031
3132
const props = withDefaults(defineProps<BToasterProps>(), {
3233
position: 'top-right',
34+
// TODO get the name right for b-toast
35+
name: 'b-toaster-top-right',
3336
})
3437
3538
const toastPositions = {

0 commit comments

Comments
 (0)