Skip to content
This repository was archived by the owner on Mar 27, 2025. It is now read-only.

Commit af0a97d

Browse files
committed
feat: BOffcanvas & BModal body scrolling prevented
fix(BFormFile): unnecessary wrapper element fix(BFormFile): ModelValue not synced up when using reset fix: readonly some refs
1 parent 9f28c00 commit af0a97d

File tree

9 files changed

+78
-59
lines changed

9 files changed

+78
-59
lines changed

packages/bootstrap-vue-next/src/components/BFormFile/BFormFile.vue

+32-32
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
<template>
2-
<div :class="wrapperClasses">
3-
<label v-if="hasLabelSlot || label" :for="computedId" class="form-label" :class="labelClasses">
4-
<slot name="label">
5-
{{ label }}
6-
</slot>
7-
</label>
8-
<input
9-
:id="computedId"
10-
v-bind="$attrs"
11-
ref="input"
12-
type="file"
13-
class="form-control"
14-
:class="computedClasses"
15-
:form="form"
16-
:name="name"
17-
:multiple="multipleBoolean"
18-
:disabled="disabledBoolean"
19-
:capture="computedCapture"
20-
:accept="computedAccept || undefined"
21-
:required="requiredBoolean || undefined"
22-
:aria-required="requiredBoolean || undefined"
23-
:directory="directoryBoolean"
24-
:webkitdirectory="directoryBoolean"
25-
@change="onChange"
26-
@drop="onDrop"
27-
/>
28-
</div>
2+
<label v-if="hasLabelSlot || label" :for="computedId" class="form-label" :class="labelClasses">
3+
<slot name="label">
4+
{{ label }}
5+
</slot>
6+
</label>
7+
<input
8+
:id="computedId"
9+
v-bind="$attrs"
10+
ref="input"
11+
type="file"
12+
class="form-control"
13+
:class="computedClasses"
14+
:form="form"
15+
:name="name"
16+
:multiple="multipleBoolean"
17+
:disabled="disabledBoolean"
18+
:capture="computedCapture"
19+
:accept="computedAccept || undefined"
20+
:required="requiredBoolean || undefined"
21+
:aria-required="requiredBoolean || undefined"
22+
:directory="directoryBoolean"
23+
:webkitdirectory="directoryBoolean"
24+
@change="onChange"
25+
@drop="onDrop"
26+
/>
2927
</template>
3028

3129
<script setup lang="ts">
32-
import {computed, ref, useSlots} from 'vue'
30+
import {computed, ref, useSlots, watch} from 'vue'
3331
import {useFocus, useVModel} from '@vueuse/core'
3432
import type {Booleanish, ClassValue, Size} from '../../types'
3533
import {useBooleanish, useId, useStateClass} from '../../composables'
@@ -62,7 +60,6 @@ const props = withDefaults(
6260
state?: Booleanish | null
6361
modelValue?: File[] | File | null
6462
label?: string
65-
wrapperClasses?: ClassValue
6663
labelClasses?: ClassValue
6764
}>(),
6865
{
@@ -83,7 +80,6 @@ const props = withDefaults(
8380
modelValue: null,
8481
label: '',
8582
labelClasses: undefined,
86-
wrapperClasses: undefined,
8783
}
8884
)
8985
@@ -148,10 +144,14 @@ const onDrop = (e: Event) => {
148144
* Reset the form input
149145
*/
150146
const reset = () => {
151-
if (input.value !== null) {
147+
modelValue.value = null
148+
}
149+
150+
watch(modelValue, (newValue) => {
151+
if (newValue === null && input.value !== null) {
152152
input.value.value = ''
153153
}
154-
}
154+
})
155155
156156
defineExpose({
157157
focus: () => {

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191

9292
<script setup lang="ts">
9393
import {computed, ref, type RendererElement, useSlots} from 'vue'
94-
import {useBooleanish, useId, useModalManager} from '../composables'
94+
import {useBooleanish, useId, useModalManager, useSafeScrollLock} from '../composables'
9595
import {useEventListener, useFocus, useVModel} from '@vueuse/core'
9696
import type {Booleanish, ButtonVariant, ClassValue, ColorVariant, Size} from '../types'
9797
import {BvTriggerableEvent, isEmptySlot} from '../utils'
@@ -160,6 +160,7 @@ const props = withDefaults(
160160
autoFocusButton?: 'ok' | 'cancel' | 'close'
161161
teleportDisabled?: Booleanish
162162
teleportTo?: string | RendererElement | null | undefined
163+
bodyScrolling?: Booleanish
163164
}>(),
164165
{
165166
bodyBgVariant: null,
@@ -210,6 +211,7 @@ const props = withDefaults(
210211
titleTag: 'h5',
211212
teleportDisabled: false,
212213
teleportTo: 'body',
214+
bodyScrolling: false,
213215
}
214216
)
215217
@@ -269,6 +271,7 @@ const okOnlyBoolean = useBooleanish(() => props.okOnly)
269271
const scrollableBoolean = useBooleanish(() => props.scrollable)
270272
const titleSrOnlyBoolean = useBooleanish(() => props.titleSrOnly)
271273
const teleportDisabledBoolean = useBooleanish(() => props.teleportDisabled)
274+
const bodyScrollingBoolean = useBooleanish(() => props.bodyScrolling)
272275
273276
const element = ref<HTMLElement | null>(null)
274277
const okButton = ref<HTMLElement | null>(null)
@@ -277,6 +280,7 @@ const closeButton = ref<HTMLElement | null>(null)
277280
const isActive = ref(modelValueBoolean.value)
278281
const lazyLoadCompleted = ref(false)
279282
283+
useSafeScrollLock(modelValueBoolean, bodyScrollingBoolean)
280284
const {focused: modalFocus} = useFocus(element, {
281285
initialValue: modelValueBoolean.value && props.autoFocusButton === undefined,
282286
})

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
<script setup lang="ts">
6666
import {computed, nextTick, ref, type RendererElement, useSlots} from 'vue'
6767
import {useEventListener, useFocus, useVModel} from '@vueuse/core'
68-
import {useBooleanish, useId} from '../../composables'
68+
import {useBooleanish, useId, useSafeScrollLock} from '../../composables'
6969
import type {Booleanish, ColorVariant} from '../../types'
7070
import {BvTriggerableEvent, isEmptySlot} from '../../utils'
7171
import BOverlay from '../BOverlay/BOverlay.vue'
@@ -162,8 +162,6 @@ const slots = useSlots()
162162
const modelValue = useVModel(props, 'modelValue', emit, {passive: true})
163163
164164
const modelValueBoolean = useBooleanish(modelValue)
165-
// TODO
166-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
167165
const bodyScrollingBoolean = useBooleanish(() => props.bodyScrolling)
168166
const backdropBoolean = useBooleanish(() => props.backdrop)
169167
const noHeaderCloseBoolean = useBooleanish(() => props.noHeaderClose)
@@ -175,6 +173,7 @@ const lazyBoolean = useBooleanish(() => props.lazy)
175173
const teleportDisabledBoolean = useBooleanish(() => props.teleportDisabled)
176174
177175
const computedId = useId(() => props.id, 'offcanvas')
176+
useSafeScrollLock(modelValueBoolean, bodyScrollingBoolean)
178177
179178
const element = ref<HTMLElement | null>(null)
180179

packages/bootstrap-vue-next/src/composables/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export {default as useFormInput} from './useFormInput'
1414
export {default as normalizeOptions} from './useFormSelect'
1515
export {default as useId} from './useId'
1616
export {default as useModalManager} from './useModalManager'
17+
export {default as useSafeScrollLock} from './useSafeScrollLock'
1718
export {default as useStateClass} from './useStateClass'

packages/bootstrap-vue-next/src/composables/useCountdown.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ export default (
3636
interval: MaybeRefOrGetter<number> = ref(1000),
3737
intervalOpts: UseIntervalFnOptions = {}
3838
): CountdownReturn => {
39-
const resolvedLength = toRef(length)
39+
const resolvedLength = readonly(toRef(length))
4040

41-
const resolvedInterval = toRef(interval)
41+
const resolvedInterval = readonly(toRef(interval))
4242

4343
const isPaused = ref(false)
4444

packages/bootstrap-vue-next/src/composables/useFormCheck.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {AriaInvalid, ButtonVariant, Size} from '../types'
2-
import {computed, type MaybeRefOrGetter, toRef} from 'vue'
2+
import {computed, type MaybeRefOrGetter, readonly, toRef} from 'vue'
33
import {resolveAriaInvalid} from '../utils'
44

55
interface ClassesItemsInput {
@@ -11,7 +11,7 @@ interface ClassesItemsInput {
1111
}
1212

1313
const getClasses = (items: MaybeRefOrGetter<ClassesItemsInput>) => {
14-
const resolvedItems = toRef(items)
14+
const resolvedItems = readonly(toRef(items))
1515

1616
return computed(() => ({
1717
'form-check': resolvedItems.value.plain === false && resolvedItems.value.button === false,
@@ -31,7 +31,7 @@ interface InputClassesItemsInput {
3131
}
3232

3333
const getInputClasses = (items: MaybeRefOrGetter<InputClassesItemsInput>) => {
34-
const resolvedItems = toRef(items)
34+
const resolvedItems = readonly(toRef(items))
3535

3636
return computed(() => ({
3737
'form-check-input': resolvedItems.value.plain === false && resolvedItems.value.button === false,
@@ -49,7 +49,7 @@ interface LabelClasesItemsInput {
4949
}
5050

5151
const getLabelClasses = (items: MaybeRefOrGetter<LabelClasesItemsInput>) => {
52-
const resolvedItems = toRef(items)
52+
const resolvedItems = readonly(toRef(items))
5353

5454
return computed(() => ({
5555
'form-check-label': resolvedItems.value.plain === false && resolvedItems.value.button === false,
@@ -70,7 +70,7 @@ interface GroupAttrItemsInput {
7070
}
7171

7272
const getGroupAttr = (items: MaybeRefOrGetter<GroupAttrItemsInput>) => {
73-
const resolvedItems = toRef(items)
73+
const resolvedItems = readonly(toRef(items))
7474

7575
return computed(() => ({
7676
'aria-invalid': resolveAriaInvalid(resolvedItems.value.ariaInvalid, resolvedItems.value.state),
@@ -86,7 +86,7 @@ interface GroupClassesItemsInput {
8686
}
8787

8888
const getGroupClasses = (items: MaybeRefOrGetter<GroupClassesItemsInput>) => {
89-
const resolvedItems = toRef(items)
89+
const resolvedItems = readonly(toRef(items))
9090

9191
return computed(() => ({
9292
'was-validated': resolvedItems.value.validated === true,

packages/bootstrap-vue-next/src/composables/useFormInput.ts

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import type {AriaInvalid, Booleanish, Size} from '../types'
2-
import {
3-
computed,
4-
type defineEmits,
5-
nextTick,
6-
onActivated,
7-
onMounted,
8-
type PropType,
9-
ref,
10-
watch,
11-
} from 'vue'
2+
import {computed, nextTick, onActivated, onMounted, ref, watch} from 'vue'
123
import {useBooleanish, useId} from '.'
134
import {resolveAriaInvalid} from '../utils'
145
import {useFocus} from '@vueuse/core'
@@ -73,8 +64,11 @@ export default (props: Readonly<CommonInputProps>, emit: any) => {
7364
const lazyBoolean = useBooleanish(() => props.lazy)
7465
const lazyFormatterBoolean = useBooleanish(() => props.lazyFormatter)
7566
const numberBoolean = useBooleanish(() => props.number)
67+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
7668
const plaintextBoolean = useBooleanish(() => props.plaintext)
69+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
7770
const readonlyBoolean = useBooleanish(() => props.readonly)
71+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
7872
const requiredBoolean = useBooleanish(() => props.required)
7973
const stateBoolean = useBooleanish(() => props.state)
8074
const trimBoolean = useBooleanish(() => props.trim)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {computed, type MaybeRefOrGetter, onMounted, readonly, ref, toRef, watch} from 'vue'
2+
import {useScrollLock} from '@vueuse/core'
3+
4+
export default (isOpen: MaybeRefOrGetter<boolean>, bodyScroll: MaybeRefOrGetter<boolean>) => {
5+
const modelValue = readonly(toRef(isOpen))
6+
const bodyScrollingValue = readonly(toRef(bodyScroll))
7+
8+
/**
9+
* We use the inverse because bodyScrolling === true means we allow scrolling, while bodyScrolling === false means we disallow
10+
*/
11+
const inverseBodyScrollingValue = computed(() => !bodyScrollingValue.value)
12+
13+
const bodyRef = ref<HTMLBodyElement | null>(null)
14+
15+
onMounted(() => {
16+
bodyRef.value = document.querySelector('body')
17+
})
18+
19+
const isLocked = useScrollLock(bodyRef, modelValue.value && inverseBodyScrollingValue.value)
20+
21+
watch([modelValue, inverseBodyScrollingValue], ([modelVal, bodyVal]) => {
22+
isLocked.value = modelVal && bodyVal
23+
})
24+
}

packages/nuxt/playground/app.vue

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
<template>
2-
<div>
2+
<div style="min-height: 1000vh">
33
<BModal v-model="show"> Nuxt module playground! </BModal>
44
<BButton @click="show = !show"> Click me </BButton>
5-
{{ a }}
65
{{ f }}
76
</div>
87
</template>
98

109
<script setup>
11-
const show = ref(false)
12-
13-
const a = useColorMode()
10+
const show = ref(true)
1411
1512
const f = useBreadcrumb()
1613
</script>

0 commit comments

Comments
 (0)