base-ui-vue 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button/ToolbarButton.cjs +6 -0
- package/dist/button/ToolbarButton.js +1 -1
- package/dist/content/ScrollAreaContent.cjs +168 -0
- package/dist/content/ScrollAreaContent.cjs.map +1 -0
- package/dist/content/ScrollAreaContent.js +133 -0
- package/dist/content/ScrollAreaContent.js.map +1 -0
- package/dist/control/SliderControl.js +2 -2
- package/dist/corner/ScrollAreaCorner.cjs +77 -0
- package/dist/corner/ScrollAreaCorner.cjs.map +1 -0
- package/dist/corner/ScrollAreaCorner.js +72 -0
- package/dist/corner/ScrollAreaCorner.js.map +1 -0
- package/dist/decrement/NumberFieldDecrement.cjs +861 -0
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
- package/dist/decrement/NumberFieldDecrement.js +700 -0
- package/dist/decrement/NumberFieldDecrement.js.map +1 -0
- package/dist/fallback/AvatarFallback.cjs +2 -46
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +3 -41
- package/dist/fallback/AvatarFallback.js.map +1 -1
- package/dist/group/NumberFieldGroup.cjs +72 -0
- package/dist/group/NumberFieldGroup.cjs.map +1 -0
- package/dist/group/NumberFieldGroup.js +67 -0
- package/dist/group/NumberFieldGroup.js.map +1 -0
- package/dist/increment/NumberFieldIncrement.cjs +112 -0
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
- package/dist/increment/NumberFieldIncrement.js +107 -0
- package/dist/increment/NumberFieldIncrement.js.map +1 -0
- package/dist/index.cjs +52 -0
- package/dist/index.d.cts +1761 -430
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1761 -430
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index2.cjs +4065 -60
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +3955 -184
- package/dist/index2.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/input/Input.vue +37 -0
- package/src/input/InputDataAttributes.ts +30 -0
- package/src/input/index.ts +4 -0
- package/src/meter/index.ts +16 -0
- package/src/meter/indicator/MeterIndicator.vue +65 -0
- package/src/meter/label/MeterLabel.vue +63 -0
- package/src/meter/root/MeterRoot.vue +131 -0
- package/src/meter/root/MeterRootContext.ts +41 -0
- package/src/meter/track/MeterTrack.vue +46 -0
- package/src/meter/value/MeterValue.vue +85 -0
- package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
- package/src/number-field/group/NumberFieldGroup.vue +47 -0
- package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
- package/src/number-field/index.ts +42 -0
- package/src/number-field/input/NumberFieldInput.vue +455 -0
- package/src/number-field/root/NumberFieldRoot.vue +626 -0
- package/src/number-field/root/NumberFieldRootContext.ts +94 -0
- package/src/number-field/root/useNumberFieldButton.ts +171 -0
- package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
- package/src/number-field/utils/constants.ts +4 -0
- package/src/number-field/utils/getViewportRect.ts +34 -0
- package/src/number-field/utils/parse.ts +248 -0
- package/src/number-field/utils/stateAttributesMapping.ts +9 -0
- package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
- package/src/number-field/utils/types.ts +24 -0
- package/src/number-field/utils/validate.ts +120 -0
- package/src/otp-field/index.ts +22 -0
- package/src/otp-field/input/OtpFieldInput.vue +336 -0
- package/src/otp-field/root/OtpFieldRoot.vue +583 -0
- package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
- package/src/otp-field/utils/otp.ts +135 -0
- package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
- package/src/progress/index.ts +23 -0
- package/src/progress/indicator/ProgressIndicator.vue +74 -0
- package/src/progress/label/ProgressLabel.vue +63 -0
- package/src/progress/root/ProgressRoot.vue +160 -0
- package/src/progress/root/ProgressRootContext.ts +51 -0
- package/src/progress/root/ProgressRootDataAttributes.ts +14 -0
- package/src/progress/root/stateAttributesMapping.ts +18 -0
- package/src/progress/track/ProgressTrack.vue +48 -0
- package/src/progress/value/ProgressValue.vue +92 -0
- package/src/scroll-area/constants.ts +2 -0
- package/src/scroll-area/content/ScrollAreaContent.vue +87 -0
- package/src/scroll-area/corner/ScrollAreaCorner.vue +64 -0
- package/src/scroll-area/index.ts +25 -0
- package/src/scroll-area/root/ScrollAreaRoot.vue +297 -0
- package/src/scroll-area/root/ScrollAreaRootContext.ts +89 -0
- package/src/scroll-area/root/ScrollAreaRootCssVars.ts +4 -0
- package/src/scroll-area/root/ScrollAreaRootDataAttributes.ts +9 -0
- package/src/scroll-area/root/stateAttributes.ts +14 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbar.vue +263 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarContext.ts +20 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarCssVars.ts +4 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarDataAttributes.ts +11 -0
- package/src/scroll-area/thumb/ScrollAreaThumb.vue +120 -0
- package/src/scroll-area/thumb/ScrollAreaThumbDataAttributes.ts +3 -0
- package/src/scroll-area/utils/getOffset.ts +34 -0
- package/src/scroll-area/viewport/ScrollAreaViewport.vue +379 -0
- package/src/scroll-area/viewport/ScrollAreaViewportContext.ts +20 -0
- package/src/scroll-area/viewport/ScrollAreaViewportCssVars.ts +6 -0
- package/src/scroll-area/viewport/ScrollAreaViewportDataAttributes.ts +9 -0
- package/src/utils/detectBrowser.ts +15 -0
- package/src/utils/formatNumber.ts +60 -2
- package/src/utils/scrollEdges.ts +33 -0
- package/src/utils/styles.ts +28 -0
- package/src/utils/useInterval.ts +45 -0
- package/src/utils/usePressAndHold.ts +260 -0
- package/src/utils/useValueChanged.ts +21 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps } from '../../utils/types'
|
|
3
|
+
import type { ProgressRootState } from '../root/ProgressRoot.vue'
|
|
4
|
+
import { computed, useAttrs } from 'vue'
|
|
5
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
6
|
+
import { useProgressRootContext } from '../root/ProgressRootContext'
|
|
7
|
+
import { progressStateAttributesMapping } from '../root/stateAttributesMapping'
|
|
8
|
+
|
|
9
|
+
export interface ProgressValueState extends ProgressRootState {}
|
|
10
|
+
export interface ProgressValueProps extends BaseUIComponentProps<ProgressValueState> {}
|
|
11
|
+
|
|
12
|
+
export interface ProgressValueSlotProps {
|
|
13
|
+
/**
|
|
14
|
+
* Formatted value. `"indeterminate"` when the root value is `null`.
|
|
15
|
+
*/
|
|
16
|
+
formattedValue: string | 'indeterminate'
|
|
17
|
+
/**
|
|
18
|
+
* Raw numeric value, or `null` when indeterminate.
|
|
19
|
+
*/
|
|
20
|
+
value: number | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ProgressValueRenderlessSlotProps extends ProgressValueSlotProps {
|
|
24
|
+
props: Record<string, unknown>
|
|
25
|
+
state: ProgressValueState
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A text label displaying the current value.
|
|
30
|
+
* Renders a `<span>` element.
|
|
31
|
+
*
|
|
32
|
+
* Documentation: [Base UI Vue Progress](https://baseui-vue.com/docs/components/progress)
|
|
33
|
+
*/
|
|
34
|
+
defineOptions({
|
|
35
|
+
name: 'ProgressValue',
|
|
36
|
+
inheritAttrs: false,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const props = withDefaults(defineProps<ProgressValueProps>(), {
|
|
40
|
+
as: 'span',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
defineSlots<{
|
|
44
|
+
default?: (props: ProgressValueSlotProps | ProgressValueRenderlessSlotProps) => unknown
|
|
45
|
+
}>()
|
|
46
|
+
|
|
47
|
+
const attrs = useAttrs()
|
|
48
|
+
const { value, formattedValue, state } = useProgressRootContext()
|
|
49
|
+
|
|
50
|
+
const slotFormattedValue = computed<string | 'indeterminate'>(() =>
|
|
51
|
+
value.value == null ? 'indeterminate' : formattedValue.value,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const defaultDisplay = computed(() =>
|
|
55
|
+
value.value == null ? '' : formattedValue.value,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const valueProps = computed(() => ({
|
|
59
|
+
'aria-hidden': true,
|
|
60
|
+
...attrs,
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
const {
|
|
64
|
+
tag,
|
|
65
|
+
mergedProps,
|
|
66
|
+
renderless,
|
|
67
|
+
} = useRenderElement({
|
|
68
|
+
componentProps: props,
|
|
69
|
+
state,
|
|
70
|
+
props: valueProps,
|
|
71
|
+
defaultTagName: 'span',
|
|
72
|
+
stateAttributesMapping: progressStateAttributesMapping,
|
|
73
|
+
})
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<slot
|
|
78
|
+
v-if="renderless"
|
|
79
|
+
:props="mergedProps"
|
|
80
|
+
:state="state"
|
|
81
|
+
:formatted-value="slotFormattedValue"
|
|
82
|
+
:value="value"
|
|
83
|
+
/>
|
|
84
|
+
<component :is="tag" v-else v-bind="mergedProps">
|
|
85
|
+
<slot
|
|
86
|
+
:formatted-value="slotFormattedValue"
|
|
87
|
+
:value="value"
|
|
88
|
+
>
|
|
89
|
+
{{ defaultDisplay }}
|
|
90
|
+
</slot>
|
|
91
|
+
</component>
|
|
92
|
+
</template>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps } from '../../utils/types'
|
|
3
|
+
import type { ScrollAreaRootState } from '../root/ScrollAreaRootContext'
|
|
4
|
+
import { computed, onBeforeUnmount, onMounted, shallowRef, useAttrs } from 'vue'
|
|
5
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
6
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
7
|
+
import { useScrollAreaRootContext } from '../root/ScrollAreaRootContext'
|
|
8
|
+
import { scrollAreaStateAttributesMapping } from '../root/stateAttributes'
|
|
9
|
+
import { useScrollAreaViewportContext } from '../viewport/ScrollAreaViewportContext'
|
|
10
|
+
|
|
11
|
+
export type ScrollAreaContentState = ScrollAreaRootState
|
|
12
|
+
|
|
13
|
+
export interface ScrollAreaContentProps extends BaseUIComponentProps<ScrollAreaContentState> {}
|
|
14
|
+
|
|
15
|
+
defineOptions({
|
|
16
|
+
name: 'ScrollAreaContent',
|
|
17
|
+
inheritAttrs: false,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(defineProps<ScrollAreaContentProps>(), {
|
|
21
|
+
as: 'div',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const attrs = useAttrs()
|
|
25
|
+
|
|
26
|
+
const contentWrapperRef = shallowRef<HTMLDivElement | null>(null)
|
|
27
|
+
const { computeThumbPosition } = useScrollAreaViewportContext()
|
|
28
|
+
const { viewportState } = useScrollAreaRootContext()
|
|
29
|
+
|
|
30
|
+
let resizeObserver: ResizeObserver | undefined
|
|
31
|
+
|
|
32
|
+
onMounted(() => {
|
|
33
|
+
if (typeof ResizeObserver === 'undefined')
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
let hasInitialized = false
|
|
37
|
+
resizeObserver = new ResizeObserver(() => {
|
|
38
|
+
if (!hasInitialized) {
|
|
39
|
+
hasInitialized = true
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
computeThumbPosition()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
if (contentWrapperRef.value) {
|
|
46
|
+
resizeObserver.observe(contentWrapperRef.value)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
onBeforeUnmount(() => {
|
|
51
|
+
resizeObserver?.disconnect()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const elementProps = computed(() => mergeProps(
|
|
55
|
+
attrs as Record<string, any>,
|
|
56
|
+
{
|
|
57
|
+
role: 'presentation',
|
|
58
|
+
style: { minWidth: 'fit-content' },
|
|
59
|
+
},
|
|
60
|
+
))
|
|
61
|
+
|
|
62
|
+
const {
|
|
63
|
+
tag,
|
|
64
|
+
mergedProps,
|
|
65
|
+
renderless,
|
|
66
|
+
ref: renderRef,
|
|
67
|
+
} = useRenderElement({
|
|
68
|
+
componentProps: props,
|
|
69
|
+
state: viewportState,
|
|
70
|
+
props: elementProps,
|
|
71
|
+
stateAttributesMapping: scrollAreaStateAttributesMapping,
|
|
72
|
+
defaultTagName: 'div',
|
|
73
|
+
ref: contentWrapperRef,
|
|
74
|
+
})
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="viewportState" />
|
|
79
|
+
<component
|
|
80
|
+
:is="tag"
|
|
81
|
+
v-else
|
|
82
|
+
:ref="renderRef"
|
|
83
|
+
v-bind="mergedProps"
|
|
84
|
+
>
|
|
85
|
+
<slot />
|
|
86
|
+
</component>
|
|
87
|
+
</template>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps } from '../../utils/types'
|
|
3
|
+
import { computed, useAttrs } from 'vue'
|
|
4
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
5
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
6
|
+
import { useScrollAreaRootContext } from '../root/ScrollAreaRootContext'
|
|
7
|
+
|
|
8
|
+
export interface ScrollAreaCornerState {}
|
|
9
|
+
|
|
10
|
+
export interface ScrollAreaCornerProps extends BaseUIComponentProps<ScrollAreaCornerState> {}
|
|
11
|
+
|
|
12
|
+
defineOptions({
|
|
13
|
+
name: 'ScrollAreaCorner',
|
|
14
|
+
inheritAttrs: false,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<ScrollAreaCornerProps>(), {
|
|
18
|
+
as: 'div',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const attrs = useAttrs()
|
|
22
|
+
|
|
23
|
+
const { cornerRef, cornerSize, hiddenState } = useScrollAreaRootContext()
|
|
24
|
+
|
|
25
|
+
const state = computed<ScrollAreaCornerState>(() => ({}))
|
|
26
|
+
|
|
27
|
+
const elementProps = computed(() => mergeProps(
|
|
28
|
+
attrs as Record<string, any>,
|
|
29
|
+
{
|
|
30
|
+
style: {
|
|
31
|
+
position: 'absolute',
|
|
32
|
+
bottom: 0,
|
|
33
|
+
insetInlineEnd: 0,
|
|
34
|
+
width: cornerSize.value.width,
|
|
35
|
+
height: cornerSize.value.height,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
))
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
tag,
|
|
42
|
+
mergedProps,
|
|
43
|
+
renderless,
|
|
44
|
+
ref: renderRef,
|
|
45
|
+
} = useRenderElement({
|
|
46
|
+
componentProps: props,
|
|
47
|
+
state,
|
|
48
|
+
props: elementProps,
|
|
49
|
+
defaultTagName: 'div',
|
|
50
|
+
ref: cornerRef,
|
|
51
|
+
})
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<template v-if="!hiddenState.corner">
|
|
56
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
57
|
+
<component
|
|
58
|
+
:is="tag"
|
|
59
|
+
v-else
|
|
60
|
+
:ref="renderRef"
|
|
61
|
+
v-bind="mergedProps"
|
|
62
|
+
/>
|
|
63
|
+
</template>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { default as ScrollAreaContent } from './content/ScrollAreaContent.vue'
|
|
2
|
+
export type { ScrollAreaContentProps, ScrollAreaContentState } from './content/ScrollAreaContent.vue'
|
|
3
|
+
export { default as ScrollAreaCorner } from './corner/ScrollAreaCorner.vue'
|
|
4
|
+
export type { ScrollAreaCornerProps, ScrollAreaCornerState } from './corner/ScrollAreaCorner.vue'
|
|
5
|
+
export { default as ScrollAreaRoot } from './root/ScrollAreaRoot.vue'
|
|
6
|
+
|
|
7
|
+
export type { ScrollAreaRootProps } from './root/ScrollAreaRoot.vue'
|
|
8
|
+
export type { ScrollAreaRootState } from './root/ScrollAreaRootContext'
|
|
9
|
+
export { ScrollAreaRootCssVars } from './root/ScrollAreaRootCssVars'
|
|
10
|
+
export { ScrollAreaRootDataAttributes } from './root/ScrollAreaRootDataAttributes'
|
|
11
|
+
|
|
12
|
+
export { default as ScrollAreaScrollbar } from './scrollbar/ScrollAreaScrollbar.vue'
|
|
13
|
+
export type { ScrollAreaScrollbarProps, ScrollAreaScrollbarState } from './scrollbar/ScrollAreaScrollbar.vue'
|
|
14
|
+
|
|
15
|
+
export { ScrollAreaScrollbarCssVars } from './scrollbar/ScrollAreaScrollbarCssVars'
|
|
16
|
+
export { ScrollAreaScrollbarDataAttributes } from './scrollbar/ScrollAreaScrollbarDataAttributes'
|
|
17
|
+
export { default as ScrollAreaThumb } from './thumb/ScrollAreaThumb.vue'
|
|
18
|
+
export type { ScrollAreaThumbProps, ScrollAreaThumbState } from './thumb/ScrollAreaThumb.vue'
|
|
19
|
+
|
|
20
|
+
export { ScrollAreaThumbDataAttributes } from './thumb/ScrollAreaThumbDataAttributes'
|
|
21
|
+
export { default as ScrollAreaViewport } from './viewport/ScrollAreaViewport.vue'
|
|
22
|
+
export type { ScrollAreaViewportProps, ScrollAreaViewportState } from './viewport/ScrollAreaViewport.vue'
|
|
23
|
+
|
|
24
|
+
export { ScrollAreaViewportCssVars } from './viewport/ScrollAreaViewportCssVars'
|
|
25
|
+
export { ScrollAreaViewportDataAttributes } from './viewport/ScrollAreaViewportDataAttributes'
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps } from '../../utils/types'
|
|
3
|
+
import type { Coords, HiddenState, OverflowEdges, ScrollAreaRootContext, ScrollAreaRootState, Size } from './ScrollAreaRootContext'
|
|
4
|
+
import { computed, onMounted, provide, ref, shallowRef, useAttrs } from 'vue'
|
|
5
|
+
import { useCSPContext } from '../../csp-provider/CSPContext'
|
|
6
|
+
import { contains } from '../../floating-ui-vue/utils/shadowDom'
|
|
7
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
8
|
+
import { styleDisableScrollbar } from '../../utils/styles'
|
|
9
|
+
import { useBaseUiId } from '../../utils/useBaseUiId'
|
|
10
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
11
|
+
import { SCROLL_TIMEOUT } from '../constants'
|
|
12
|
+
import { ScrollAreaScrollbarDataAttributes } from '../scrollbar/ScrollAreaScrollbarDataAttributes'
|
|
13
|
+
import { getOffset } from '../utils/getOffset'
|
|
14
|
+
import { scrollAreaRootContextKey } from './ScrollAreaRootContext'
|
|
15
|
+
import { ScrollAreaRootCssVars } from './ScrollAreaRootCssVars'
|
|
16
|
+
import { scrollAreaStateAttributesMapping } from './stateAttributes'
|
|
17
|
+
|
|
18
|
+
defineOptions({
|
|
19
|
+
name: 'ScrollAreaRoot',
|
|
20
|
+
inheritAttrs: false,
|
|
21
|
+
})
|
|
22
|
+
const props = withDefaults(defineProps<ScrollAreaRootProps>(), {
|
|
23
|
+
as: 'div',
|
|
24
|
+
})
|
|
25
|
+
const DEFAULT_SIZE: Size = { width: 0, height: 0 }
|
|
26
|
+
const DEFAULT_OVERFLOW_EDGES: OverflowEdges = { xStart: false, xEnd: false, yStart: false, yEnd: false }
|
|
27
|
+
const DEFAULT_HIDDEN_STATE: HiddenState = { x: true, y: true, corner: true }
|
|
28
|
+
const DEFAULT_COORDS: Coords = { x: 0, y: 0 }
|
|
29
|
+
|
|
30
|
+
export interface ScrollAreaRootProps extends BaseUIComponentProps<ScrollAreaRootState> {
|
|
31
|
+
overflowEdgeThreshold?:
|
|
32
|
+
| number
|
|
33
|
+
| Partial<{
|
|
34
|
+
xStart: number
|
|
35
|
+
xEnd: number
|
|
36
|
+
yStart: number
|
|
37
|
+
yEnd: number
|
|
38
|
+
}>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const attrs = useAttrs()
|
|
42
|
+
|
|
43
|
+
const overflowEdgeThreshold = computed(() => normalizeOverflowEdgeThreshold(props.overflowEdgeThreshold))
|
|
44
|
+
|
|
45
|
+
const rootId = useBaseUiId()
|
|
46
|
+
const { nonce, disableStyleElements } = useCSPContext()
|
|
47
|
+
|
|
48
|
+
const hovering = ref(false)
|
|
49
|
+
const scrollingX = ref(false)
|
|
50
|
+
const scrollingY = ref(false)
|
|
51
|
+
const touchModality = ref(false)
|
|
52
|
+
const hasMeasuredScrollbar = ref(false)
|
|
53
|
+
const cornerSize = ref<Size>({ ...DEFAULT_SIZE })
|
|
54
|
+
const thumbSize = ref<Size>({ ...DEFAULT_SIZE })
|
|
55
|
+
const overflowEdges = ref<OverflowEdges>({ ...DEFAULT_OVERFLOW_EDGES })
|
|
56
|
+
const hiddenState = ref<HiddenState>({ ...DEFAULT_HIDDEN_STATE })
|
|
57
|
+
|
|
58
|
+
const rootRef = shallowRef<HTMLDivElement | null>(null)
|
|
59
|
+
const viewportRef = shallowRef<HTMLDivElement | null>(null)
|
|
60
|
+
const scrollbarYRef = shallowRef<HTMLDivElement | null>(null)
|
|
61
|
+
const scrollbarXRef = shallowRef<HTMLDivElement | null>(null)
|
|
62
|
+
const thumbYRef = shallowRef<HTMLDivElement | null>(null)
|
|
63
|
+
const thumbXRef = shallowRef<HTMLDivElement | null>(null)
|
|
64
|
+
const cornerRef = shallowRef<HTMLDivElement | null>(null)
|
|
65
|
+
|
|
66
|
+
let scrollYTimeoutId: ReturnType<typeof setTimeout> | undefined
|
|
67
|
+
let scrollXTimeoutId: ReturnType<typeof setTimeout> | undefined
|
|
68
|
+
|
|
69
|
+
// Pointer drag baselines are mutable so move events don't trigger Vue renders.
|
|
70
|
+
const thumbDragging = { current: false }
|
|
71
|
+
const startY = { current: 0 }
|
|
72
|
+
const startX = { current: 0 }
|
|
73
|
+
const startScrollTop = { current: 0 }
|
|
74
|
+
const startScrollLeft = { current: 0 }
|
|
75
|
+
const currentOrientation = { current: 'vertical' as 'vertical' | 'horizontal' }
|
|
76
|
+
const scrollPosition = { current: { ...DEFAULT_COORDS } }
|
|
77
|
+
|
|
78
|
+
function handleScroll(pos: Coords) {
|
|
79
|
+
const offsetX = pos.x - scrollPosition.current.x
|
|
80
|
+
const offsetY = pos.y - scrollPosition.current.y
|
|
81
|
+
scrollPosition.current = pos
|
|
82
|
+
|
|
83
|
+
if (offsetY !== 0) {
|
|
84
|
+
scrollingY.value = true
|
|
85
|
+
clearTimeout(scrollYTimeoutId)
|
|
86
|
+
scrollYTimeoutId = setTimeout(() => {
|
|
87
|
+
scrollingY.value = false
|
|
88
|
+
}, SCROLL_TIMEOUT)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (offsetX !== 0) {
|
|
92
|
+
scrollingX.value = true
|
|
93
|
+
clearTimeout(scrollXTimeoutId)
|
|
94
|
+
scrollXTimeoutId = setTimeout(() => {
|
|
95
|
+
scrollingX.value = false
|
|
96
|
+
}, SCROLL_TIMEOUT)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handlePointerDown(event: PointerEvent) {
|
|
101
|
+
if (event.button !== 0)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
thumbDragging.current = true
|
|
105
|
+
startY.current = event.clientY
|
|
106
|
+
startX.current = event.clientX
|
|
107
|
+
currentOrientation.current = (event.currentTarget as Element).getAttribute(
|
|
108
|
+
ScrollAreaScrollbarDataAttributes.orientation,
|
|
109
|
+
) as 'vertical' | 'horizontal'
|
|
110
|
+
|
|
111
|
+
if (viewportRef.value) {
|
|
112
|
+
startScrollTop.current = viewportRef.value.scrollTop
|
|
113
|
+
startScrollLeft.current = viewportRef.value.scrollLeft
|
|
114
|
+
}
|
|
115
|
+
if (thumbYRef.value && currentOrientation.current === 'vertical') {
|
|
116
|
+
thumbYRef.value.setPointerCapture(event.pointerId)
|
|
117
|
+
}
|
|
118
|
+
if (thumbXRef.value && currentOrientation.current === 'horizontal') {
|
|
119
|
+
thumbXRef.value.setPointerCapture(event.pointerId)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function handlePointerMove(event: PointerEvent) {
|
|
124
|
+
if (!thumbDragging.current)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
const deltaY = event.clientY - startY.current
|
|
128
|
+
const deltaX = event.clientX - startX.current
|
|
129
|
+
|
|
130
|
+
if (viewportRef.value) {
|
|
131
|
+
const scrollableContentHeight = viewportRef.value.scrollHeight
|
|
132
|
+
const viewportHeight = viewportRef.value.clientHeight
|
|
133
|
+
const scrollableContentWidth = viewportRef.value.scrollWidth
|
|
134
|
+
const viewportWidth = viewportRef.value.clientWidth
|
|
135
|
+
|
|
136
|
+
if (thumbYRef.value && scrollbarYRef.value && currentOrientation.current === 'vertical') {
|
|
137
|
+
const scrollbarYOffset = getOffset(scrollbarYRef.value, 'padding', 'y')
|
|
138
|
+
const thumbYOffset = getOffset(thumbYRef.value, 'margin', 'y')
|
|
139
|
+
const thumbHeight = thumbYRef.value.offsetHeight
|
|
140
|
+
const maxThumbOffsetY = scrollbarYRef.value.offsetHeight - thumbHeight - scrollbarYOffset - thumbYOffset
|
|
141
|
+
const scrollRatioY = deltaY / maxThumbOffsetY
|
|
142
|
+
viewportRef.value.scrollTop = startScrollTop.current + scrollRatioY * (scrollableContentHeight - viewportHeight)
|
|
143
|
+
event.preventDefault()
|
|
144
|
+
scrollingY.value = true
|
|
145
|
+
clearTimeout(scrollYTimeoutId)
|
|
146
|
+
scrollYTimeoutId = setTimeout(() => {
|
|
147
|
+
scrollingY.value = false
|
|
148
|
+
}, SCROLL_TIMEOUT)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (thumbXRef.value && scrollbarXRef.value && currentOrientation.current === 'horizontal') {
|
|
152
|
+
const scrollbarXOffset = getOffset(scrollbarXRef.value, 'padding', 'x')
|
|
153
|
+
const thumbXOffset = getOffset(thumbXRef.value, 'margin', 'x')
|
|
154
|
+
const thumbWidth = thumbXRef.value.offsetWidth
|
|
155
|
+
const maxThumbOffsetX = scrollbarXRef.value.offsetWidth - thumbWidth - scrollbarXOffset - thumbXOffset
|
|
156
|
+
const scrollRatioX = deltaX / maxThumbOffsetX
|
|
157
|
+
viewportRef.value.scrollLeft = startScrollLeft.current + scrollRatioX * (scrollableContentWidth - viewportWidth)
|
|
158
|
+
event.preventDefault()
|
|
159
|
+
scrollingX.value = true
|
|
160
|
+
clearTimeout(scrollXTimeoutId)
|
|
161
|
+
scrollXTimeoutId = setTimeout(() => {
|
|
162
|
+
scrollingX.value = false
|
|
163
|
+
}, SCROLL_TIMEOUT)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handlePointerUp(event: PointerEvent) {
|
|
169
|
+
thumbDragging.current = false
|
|
170
|
+
if (thumbYRef.value && currentOrientation.current === 'vertical') {
|
|
171
|
+
thumbYRef.value.releasePointerCapture(event.pointerId)
|
|
172
|
+
}
|
|
173
|
+
if (thumbXRef.value && currentOrientation.current === 'horizontal') {
|
|
174
|
+
thumbXRef.value.releasePointerCapture(event.pointerId)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleTouchModalityChange(event: PointerEvent) {
|
|
179
|
+
touchModality.value = event.pointerType === 'touch'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function handlePointerEnterOrMove(event: PointerEvent) {
|
|
183
|
+
handleTouchModalityChange(event)
|
|
184
|
+
if (event.pointerType !== 'touch') {
|
|
185
|
+
const isTargetRootChild = contains(rootRef.value, event.target as Element)
|
|
186
|
+
hovering.value = isTargetRootChild
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const state = computed<ScrollAreaRootState>(() => ({
|
|
191
|
+
scrolling: scrollingX.value || scrollingY.value,
|
|
192
|
+
hasOverflowX: !hiddenState.value.x,
|
|
193
|
+
hasOverflowY: !hiddenState.value.y,
|
|
194
|
+
overflowXStart: overflowEdges.value.xStart,
|
|
195
|
+
overflowXEnd: overflowEdges.value.xEnd,
|
|
196
|
+
overflowYStart: overflowEdges.value.yStart,
|
|
197
|
+
overflowYEnd: overflowEdges.value.yEnd,
|
|
198
|
+
cornerHidden: hiddenState.value.corner,
|
|
199
|
+
}))
|
|
200
|
+
|
|
201
|
+
const elementProps = computed(() => mergeProps(
|
|
202
|
+
attrs as Record<string, any>,
|
|
203
|
+
{
|
|
204
|
+
role: 'presentation',
|
|
205
|
+
onPointerenter: handlePointerEnterOrMove,
|
|
206
|
+
onPointermove: handlePointerEnterOrMove,
|
|
207
|
+
onPointerdown: handleTouchModalityChange,
|
|
208
|
+
onPointerleave: () => { hovering.value = false },
|
|
209
|
+
style: {
|
|
210
|
+
position: 'relative',
|
|
211
|
+
[ScrollAreaRootCssVars.scrollAreaCornerHeight]: `${cornerSize.value.height}px`,
|
|
212
|
+
[ScrollAreaRootCssVars.scrollAreaCornerWidth]: `${cornerSize.value.width}px`,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
))
|
|
216
|
+
|
|
217
|
+
const {
|
|
218
|
+
tag,
|
|
219
|
+
mergedProps,
|
|
220
|
+
renderless,
|
|
221
|
+
ref: renderRef,
|
|
222
|
+
} = useRenderElement({
|
|
223
|
+
componentProps: props,
|
|
224
|
+
state,
|
|
225
|
+
props: elementProps,
|
|
226
|
+
stateAttributesMapping: scrollAreaStateAttributesMapping,
|
|
227
|
+
defaultTagName: 'div',
|
|
228
|
+
ref: rootRef,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
onMounted(() => {
|
|
232
|
+
styleDisableScrollbar.inject(nonce.value, disableStyleElements.value)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
const contextValue: ScrollAreaRootContext = {
|
|
236
|
+
cornerSize,
|
|
237
|
+
setCornerSize: (size: Size) => { cornerSize.value = size },
|
|
238
|
+
thumbSize,
|
|
239
|
+
setThumbSize: (size: Size) => { thumbSize.value = size },
|
|
240
|
+
hasMeasuredScrollbar,
|
|
241
|
+
setHasMeasuredScrollbar: (value: boolean) => { hasMeasuredScrollbar.value = value },
|
|
242
|
+
touchModality,
|
|
243
|
+
hovering,
|
|
244
|
+
setHovering: (value: boolean) => { hovering.value = value },
|
|
245
|
+
scrollingX,
|
|
246
|
+
setScrollingX: (value: boolean) => { scrollingX.value = value },
|
|
247
|
+
scrollingY,
|
|
248
|
+
setScrollingY: (value: boolean) => { scrollingY.value = value },
|
|
249
|
+
viewportRef,
|
|
250
|
+
rootRef,
|
|
251
|
+
scrollbarYRef,
|
|
252
|
+
scrollbarXRef,
|
|
253
|
+
thumbYRef,
|
|
254
|
+
thumbXRef,
|
|
255
|
+
cornerRef,
|
|
256
|
+
handlePointerDown,
|
|
257
|
+
handlePointerMove,
|
|
258
|
+
handlePointerUp,
|
|
259
|
+
handleScroll,
|
|
260
|
+
rootId,
|
|
261
|
+
hiddenState,
|
|
262
|
+
setHiddenState: (s: HiddenState) => { hiddenState.value = s },
|
|
263
|
+
overflowEdges,
|
|
264
|
+
setOverflowEdges: (e: OverflowEdges) => { overflowEdges.value = e },
|
|
265
|
+
viewportState: state,
|
|
266
|
+
overflowEdgeThreshold,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
provide(scrollAreaRootContextKey, contextValue)
|
|
270
|
+
|
|
271
|
+
function normalizeOverflowEdgeThreshold(
|
|
272
|
+
threshold: ScrollAreaRootProps['overflowEdgeThreshold'],
|
|
273
|
+
) {
|
|
274
|
+
if (typeof threshold === 'number') {
|
|
275
|
+
const value = Math.max(0, threshold)
|
|
276
|
+
return { xStart: value, xEnd: value, yStart: value, yEnd: value }
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
xStart: Math.max(0, threshold?.xStart || 0),
|
|
280
|
+
xEnd: Math.max(0, threshold?.xEnd || 0),
|
|
281
|
+
yStart: Math.max(0, threshold?.yStart || 0),
|
|
282
|
+
yEnd: Math.max(0, threshold?.yEnd || 0),
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
</script>
|
|
286
|
+
|
|
287
|
+
<template>
|
|
288
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
289
|
+
<component
|
|
290
|
+
:is="tag"
|
|
291
|
+
v-else
|
|
292
|
+
:ref="renderRef"
|
|
293
|
+
v-bind="mergedProps"
|
|
294
|
+
>
|
|
295
|
+
<slot />
|
|
296
|
+
</component>
|
|
297
|
+
</template>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ComputedRef, InjectionKey, Ref, ShallowRef } from 'vue'
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
export interface Coords {
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Size {
|
|
10
|
+
width: number
|
|
11
|
+
height: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface HiddenState {
|
|
15
|
+
x: boolean
|
|
16
|
+
y: boolean
|
|
17
|
+
corner: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OverflowEdges {
|
|
21
|
+
xStart: boolean
|
|
22
|
+
xEnd: boolean
|
|
23
|
+
yStart: boolean
|
|
24
|
+
yEnd: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ScrollAreaRootContext {
|
|
28
|
+
cornerSize: Ref<Size>
|
|
29
|
+
setCornerSize: (size: Size) => void
|
|
30
|
+
thumbSize: Ref<Size>
|
|
31
|
+
setThumbSize: (size: Size) => void
|
|
32
|
+
hasMeasuredScrollbar: Ref<boolean>
|
|
33
|
+
setHasMeasuredScrollbar: (value: boolean) => void
|
|
34
|
+
touchModality: Ref<boolean>
|
|
35
|
+
hovering: Ref<boolean>
|
|
36
|
+
setHovering: (value: boolean) => void
|
|
37
|
+
scrollingX: Ref<boolean>
|
|
38
|
+
setScrollingX: (value: boolean) => void
|
|
39
|
+
scrollingY: Ref<boolean>
|
|
40
|
+
setScrollingY: (value: boolean) => void
|
|
41
|
+
viewportRef: ShallowRef<HTMLDivElement | null>
|
|
42
|
+
rootRef: ShallowRef<HTMLDivElement | null>
|
|
43
|
+
scrollbarYRef: ShallowRef<HTMLDivElement | null>
|
|
44
|
+
scrollbarXRef: ShallowRef<HTMLDivElement | null>
|
|
45
|
+
thumbYRef: ShallowRef<HTMLDivElement | null>
|
|
46
|
+
thumbXRef: ShallowRef<HTMLDivElement | null>
|
|
47
|
+
cornerRef: ShallowRef<HTMLDivElement | null>
|
|
48
|
+
handlePointerDown: (event: PointerEvent) => void
|
|
49
|
+
handlePointerMove: (event: PointerEvent) => void
|
|
50
|
+
handlePointerUp: (event: PointerEvent) => void
|
|
51
|
+
handleScroll: (scrollPosition: Coords) => void
|
|
52
|
+
rootId: string | undefined
|
|
53
|
+
hiddenState: Ref<HiddenState>
|
|
54
|
+
setHiddenState: (state: HiddenState) => void
|
|
55
|
+
overflowEdges: Ref<OverflowEdges>
|
|
56
|
+
setOverflowEdges: (edges: OverflowEdges) => void
|
|
57
|
+
viewportState: ComputedRef<ScrollAreaRootState>
|
|
58
|
+
overflowEdgeThreshold: ComputedRef<{
|
|
59
|
+
xStart: number
|
|
60
|
+
xEnd: number
|
|
61
|
+
yStart: number
|
|
62
|
+
yEnd: number
|
|
63
|
+
}>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ScrollAreaRootState {
|
|
67
|
+
scrolling: boolean
|
|
68
|
+
hasOverflowX: boolean
|
|
69
|
+
hasOverflowY: boolean
|
|
70
|
+
overflowXStart: boolean
|
|
71
|
+
overflowXEnd: boolean
|
|
72
|
+
overflowYStart: boolean
|
|
73
|
+
overflowYEnd: boolean
|
|
74
|
+
cornerHidden: boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const scrollAreaRootContextKey = Symbol(
|
|
78
|
+
'ScrollAreaRootContext',
|
|
79
|
+
) as InjectionKey<ScrollAreaRootContext>
|
|
80
|
+
|
|
81
|
+
export function useScrollAreaRootContext(): ScrollAreaRootContext {
|
|
82
|
+
const context = inject(scrollAreaRootContextKey)
|
|
83
|
+
if (context === undefined) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
'Base UI: ScrollAreaRootContext is missing. ScrollArea parts must be placed within <ScrollAreaRoot>.',
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return context
|
|
89
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export enum ScrollAreaRootDataAttributes {
|
|
2
|
+
scrolling = 'data-scrolling',
|
|
3
|
+
hasOverflowX = 'data-has-overflow-x',
|
|
4
|
+
hasOverflowY = 'data-has-overflow-y',
|
|
5
|
+
overflowXStart = 'data-overflow-x-start',
|
|
6
|
+
overflowXEnd = 'data-overflow-x-end',
|
|
7
|
+
overflowYStart = 'data-overflow-y-start',
|
|
8
|
+
overflowYEnd = 'data-overflow-y-end',
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
|
|
2
|
+
import type { ScrollAreaRootState } from './ScrollAreaRootContext'
|
|
3
|
+
import { ScrollAreaRootDataAttributes } from './ScrollAreaRootDataAttributes'
|
|
4
|
+
|
|
5
|
+
export const scrollAreaStateAttributesMapping: StateAttributesMapping<ScrollAreaRootState> = {
|
|
6
|
+
scrolling: value => (value ? { [ScrollAreaRootDataAttributes.scrolling]: '' } : null),
|
|
7
|
+
hasOverflowX: value => (value ? { [ScrollAreaRootDataAttributes.hasOverflowX]: '' } : null),
|
|
8
|
+
hasOverflowY: value => (value ? { [ScrollAreaRootDataAttributes.hasOverflowY]: '' } : null),
|
|
9
|
+
overflowXStart: value => (value ? { [ScrollAreaRootDataAttributes.overflowXStart]: '' } : null),
|
|
10
|
+
overflowXEnd: value => (value ? { [ScrollAreaRootDataAttributes.overflowXEnd]: '' } : null),
|
|
11
|
+
overflowYStart: value => (value ? { [ScrollAreaRootDataAttributes.overflowYStart]: '' } : null),
|
|
12
|
+
overflowYEnd: value => (value ? { [ScrollAreaRootDataAttributes.overflowYEnd]: '' } : null),
|
|
13
|
+
cornerHidden: () => null,
|
|
14
|
+
}
|