Skip to content

Commit 8b8399d

Browse files
teleskop150750productdevbook
authored andcommitted
feat: hooks scoll-area
1 parent 78f2a4d commit 8b8399d

26 files changed

+857
-577
lines changed

packages/vue-primitives/src/accordion/AccordionRoot.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NOOP } from '@vue/shared'
2-
import { type AriaAttributes, computed, type Ref } from 'vue'
2+
import { type AriaAttributes, computed, type MaybeRefOrGetter, type Ref } from 'vue'
33
import { createCollection } from '../collection/index.ts'
44
import { type Direction, useDirection } from '../direction/index.ts'
55
import { createContext, type MutableRefObject, useControllableStateV2, useId } from '../hooks/index.ts'
@@ -106,7 +106,7 @@ export interface UseAccordionRootProps<T extends AccordionType> extends ConvertE
106106
type?: T
107107
disabled?: () => boolean
108108
orientation?: AccordionImplProps['orientation']
109-
dir: () => Direction | undefined
109+
dir?: MaybeRefOrGetter<Direction | undefined>
110110
}
111111

112112
export function useAccordionRoot<T extends AccordionType>(props: UseAccordionRootProps<T>): RadixPrimitiveReturns {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { mergeHooksAttrs, type RadixPrimitiveReturns } from '../shared/index.ts'
2+
import { useScrollAreaContext } from './ScrollAreaRoot.ts'
3+
4+
export function useScrollAreaContent(): RadixPrimitiveReturns {
5+
const context = useScrollAreaContext('ScrollAreaContent')
6+
7+
function setTemplateEl(templateEl: HTMLElement | undefined) {
8+
context.content.value = templateEl
9+
}
10+
11+
return {
12+
attrs(extraAttrs) {
13+
const attrs = {
14+
ref: setTemplateEl,
15+
style: 'min-width: 100%; display: table',
16+
}
17+
18+
if (extraAttrs)
19+
mergeHooksAttrs(attrs, extraAttrs)
20+
21+
return attrs
22+
},
23+
}
24+
}
Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
<script setup lang="ts">
2-
import { useForwardElement } from '@oku-ui/hooks'
3-
import { Primitive } from '@oku-ui/primitive'
4-
import { useScrollAreaContext } from './ScrollAreaRoot.ts'
2+
import { Primitive } from '../primitive/index.ts'
3+
import { normalizeAttrs } from '../shared/mergeProps.ts'
4+
import { useScrollAreaContent } from './ScrollAreaContent.ts'
55
66
defineOptions({
77
name: 'ScrollAreaContent',
8+
inheritAttrs: false,
89
})
910
10-
const context = useScrollAreaContext('ScrollAreaContent')
11-
const forwardedContentRef = useForwardElement(context.content)
11+
const scrollAreaContent = useScrollAreaContent()
1212
</script>
1313

1414
<template>
15-
<Primitive
16-
:ref="forwardedContentRef"
17-
style="min-width: 100%; display: table"
18-
>
15+
<Primitive v-bind="normalizeAttrs(scrollAreaContent.attrs(), $attrs)">
1916
<slot />
2017
</Primitive>
2118
</template>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { RadixPrimitiveReturns } from '../shared/index.ts'
2+
import { computed, type Ref } from 'vue'
3+
import { useScrollAreaContext } from './ScrollAreaRoot.ts'
4+
5+
export function useScrollAreaCorner(): RadixPrimitiveReturns<{ hasCorner: Ref<boolean> }> {
6+
const context = useScrollAreaContext('ScrollAreaCorner')
7+
const hasCorner = computed(() => context.type !== 'scroll' && Boolean(context.scrollbarX.value && context.scrollbarY.value))
8+
9+
return {
10+
hasCorner,
11+
}
12+
}
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
<script setup lang="ts">
2+
import { useScrollAreaCorner } from './ScrollAreaCorner.ts'
23
import ScrollAreaCornerImpl from './ScrollAreaCornerImpl.vue'
3-
import { useScrollAreaContext } from './ScrollAreaRoot.ts'
44
55
defineOptions({
66
name: 'ScrollAreaCorner',
77
})
88
9-
const context = useScrollAreaContext('ScrollAreaCorner')
10-
const hasCorner = () => context.type() !== 'scroll' && Boolean(context.scrollbarX.value && context.scrollbarY.value)
9+
const scrollAreaCorner = useScrollAreaCorner()
1110
</script>
1211

1312
<template>
14-
<ScrollAreaCornerImpl v-if="hasCorner()">
13+
<ScrollAreaCornerImpl v-if="scrollAreaCorner.hasCorner.value">
1514
<slot />
1615
</ScrollAreaCornerImpl>
1716
</template>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useResizeObserver } from '@vueuse/core'
2+
import { computed, type Ref, shallowRef } from 'vue'
3+
import { type ElAttrs, mergeHooksAttrs, type RadixPrimitiveGetAttrs, type RadixPrimitiveReturns } from '../shared/index.ts'
4+
import { useScrollAreaContext } from './ScrollAreaRoot.ts'
5+
6+
export function useScrollAreaCornerImpl(): RadixPrimitiveReturns<{
7+
hasSize: Ref<boolean>
8+
attrs: RadixPrimitiveGetAttrs
9+
}> {
10+
const context = useScrollAreaContext('ScrollAreaCornerImpl')
11+
12+
const width = shallowRef(0)
13+
const height = shallowRef(0)
14+
15+
const hasSize = computed(() => Boolean(width.value && height.value))
16+
17+
useResizeObserver(context.scrollbarX, () => {
18+
const _height = context.scrollbarX.value?.offsetHeight || 0
19+
context.onCornerHeightChange(_height)
20+
height.value = _height
21+
})
22+
23+
useResizeObserver(context.scrollbarY, () => {
24+
const _width = context.scrollbarY.value?.offsetWidth || 0
25+
context.onCornerWidthChange(_width)
26+
width.value = _width
27+
})
28+
29+
return {
30+
hasSize,
31+
attrs(extraAttrs) {
32+
const attrs: ElAttrs = {
33+
style: {
34+
width: `${width.value}px`,
35+
height: `${height.value}px`,
36+
position: 'absolute',
37+
right: context.dir.value === 'ltr' ? 0 : undefined,
38+
left: context.dir.value === 'rtl' ? 0 : undefined,
39+
bottom: 0,
40+
},
41+
}
42+
43+
if (extraAttrs) {
44+
mergeHooksAttrs(attrs, extraAttrs)
45+
}
46+
47+
return attrs
48+
},
49+
}
50+
}

packages/vue-primitives/src/scroll-area/ScrollAreaCornerImpl.vue

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,20 @@
11
<script setup lang="ts">
2-
import { Primitive } from '@oku-ui/primitive'
3-
import { useResizeObserver } from '@vueuse/core'
4-
import { shallowRef } from 'vue'
5-
import { useScrollAreaContext } from './ScrollAreaRoot.ts'
2+
import { Primitive } from '../primitive/index.ts'
3+
import { normalizeAttrs } from '../shared/index.ts'
4+
import { useScrollAreaCornerImpl } from './ScrollAreaCornerImpl.ts'
65
76
defineOptions({
87
name: 'ScrollAreaCornerImpl',
8+
inheritAttrs: false,
99
})
1010
11-
const context = useScrollAreaContext('ScrollAreaCornerImpl')
12-
13-
const width = shallowRef(0)
14-
const height = shallowRef(0)
15-
16-
const hasSize = () => Boolean(width.value && height.value)
17-
18-
useResizeObserver(context.scrollbarX, () => {
19-
const _height = context.scrollbarX.value?.offsetHeight || 0
20-
context.onCornerHeightChange(_height)
21-
height.value = _height
22-
})
23-
24-
useResizeObserver(context.scrollbarY, () => {
25-
const _width = context.scrollbarY.value?.offsetWidth || 0
26-
context.onCornerWidthChange(_width)
27-
width.value = _width
28-
})
11+
const scrollAreaCornerImpl = useScrollAreaCornerImpl()
2912
</script>
3013

3114
<template>
3215
<Primitive
33-
v-if="hasSize()"
34-
:style="{
35-
width: `${width}px`,
36-
height: `${height}px`,
37-
position: 'absolute',
38-
right: context.dir.value === 'ltr' ? 0 : undefined,
39-
left: context.dir.value === 'rtl' ? 0 : undefined,
40-
bottom: 0,
41-
}"
16+
v-if="scrollAreaCornerImpl.hasSize.value"
17+
v-bind="normalizeAttrs(scrollAreaCornerImpl.attrs(), $attrs)"
4218
>
4319
<slot />
4420
</Primitive>

packages/vue-primitives/src/scroll-area/ScrollAreaRoot.ts

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type { Ref } from 'vue'
2-
import type { Direction } from '../direction/index.ts'
3-
import { createContext } from '@oku-ui/hooks'
1+
import { type MaybeRefOrGetter, type Ref, shallowRef } from 'vue'
2+
import { type Direction, useDirection } from '../direction/index.ts'
3+
import { createContext } from '../hooks/index.ts'
4+
import { type ElAttrs, mergeHooksAttrs, type RadixPrimitiveReturns } from '../shared/index.ts'
45

56
type ScrollAreaType = 'auto' | 'always' | 'scroll' | 'hover'
67

@@ -11,12 +12,12 @@ export interface ScrollAreaRootProps {
1112
}
1213

1314
export interface ScrollAreaContext {
14-
type: () => ScrollAreaType
15+
type: ScrollAreaType
1516
dir: Ref<Direction>
1617
scrollHideDelay: number
1718
scrollArea: Ref<HTMLElement | undefined>
1819
viewport: Ref<HTMLElement | undefined>
19-
content: Ref<HTMLDivElement | undefined>
20+
content: Ref<HTMLElement | undefined>
2021
scrollbarX: Ref<HTMLElement | undefined>
2122
scrollbarXEnabled: Ref<boolean>
2223
onScrollbarXEnabledChange: (rendered: boolean) => void
@@ -28,3 +29,68 @@ export interface ScrollAreaContext {
2829
}
2930

3031
export const [provideScrollAreaContext, useScrollAreaContext] = createContext<ScrollAreaContext>('ScrollArea')
32+
33+
export interface UseScrollAreaRootProps {
34+
el: Ref<HTMLElement | undefined>
35+
type?: ScrollAreaType
36+
dir?: MaybeRefOrGetter<Direction | undefined>
37+
scrollHideDelay: number
38+
}
39+
40+
export function useScrollAreaRoot(props: UseScrollAreaRootProps): RadixPrimitiveReturns {
41+
const viewport = shallowRef<HTMLElement>()
42+
const content = shallowRef<HTMLDivElement>()
43+
const scrollbarX = shallowRef<HTMLElement>()
44+
const scrollbarY = shallowRef<HTMLElement>()
45+
const cornerWidth = shallowRef(0)
46+
const cornerHeight = shallowRef(0)
47+
const scrollbarXEnabled = shallowRef(false)
48+
const scrollbarYEnabled = shallowRef(false)
49+
50+
const direction = useDirection(props.dir)
51+
52+
provideScrollAreaContext({
53+
type: props.type ?? 'hover',
54+
dir: direction,
55+
scrollHideDelay: props.scrollHideDelay,
56+
scrollArea: props.el,
57+
viewport,
58+
content,
59+
scrollbarX,
60+
scrollbarXEnabled,
61+
onScrollbarXEnabledChange(rendered) {
62+
scrollbarXEnabled.value = rendered
63+
},
64+
scrollbarY,
65+
scrollbarYEnabled,
66+
onScrollbarYEnabledChange(rendered) {
67+
scrollbarYEnabled.value = rendered
68+
},
69+
onCornerWidthChange(width) {
70+
cornerWidth.value = width
71+
},
72+
onCornerHeightChange(height) {
73+
cornerHeight.value = height
74+
},
75+
})
76+
77+
return {
78+
attrs(extraAttrs) {
79+
const attrs: ElAttrs = {
80+
dir: direction.value,
81+
style: {
82+
'position': 'relative',
83+
// Pass corner sizes as CSS vars to reduce re-renders of context consumers
84+
'--radix-scroll-area-corner-width': `${cornerWidth.value}px`,
85+
'--radix-scroll-area-corner-height': `${cornerHeight.value}px`,
86+
},
87+
}
88+
89+
if (extraAttrs) {
90+
mergeHooksAttrs(attrs, extraAttrs)
91+
}
92+
93+
return attrs
94+
},
95+
}
96+
}

packages/vue-primitives/src/scroll-area/ScrollAreaRoot.vue

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,37 @@
22
import { useForwardElement } from '@oku-ui/hooks'
33
import { Primitive } from '@oku-ui/primitive'
44
import { shallowRef } from 'vue'
5-
import { useDirection } from '../direction/index.ts'
6-
import { useForwardElement } from '../hooks/index.ts'
75
import { Primitive } from '../primitive/index.ts'
8-
import { provideScrollAreaContext, type ScrollAreaRootProps } from './ScrollAreaRoot.ts'
6+
import { normalizeAttrs } from '../shared/mergeProps.ts'
7+
import { type ScrollAreaRootProps, useScrollAreaRoot } from './ScrollAreaRoot.ts'
98
109
defineOptions({
1110
name: 'ScrollAreaRoot',
11+
inheritAttrs: false,
1212
})
1313
1414
const props = withDefaults(defineProps<ScrollAreaRootProps>(), {
1515
type: 'hover',
1616
scrollHideDelay: 600,
1717
})
1818
19-
const scrollArea = shallowRef<HTMLElement>()
20-
const forwardElement = useForwardElement(scrollArea)
21-
const viewport = shallowRef<HTMLElement>()
22-
const content = shallowRef<HTMLDivElement>()
23-
const scrollbarX = shallowRef<HTMLElement>()
24-
const scrollbarY = shallowRef<HTMLElement>()
25-
const cornerWidth = shallowRef(0)
26-
const cornerHeight = shallowRef(0)
27-
const scrollbarXEnabled = shallowRef(false)
28-
const scrollbarYEnabled = shallowRef(false)
19+
const el = shallowRef<HTMLElement>()
20+
function setEl(value: HTMLElement | undefined) {
21+
el.value = value
22+
}
2923
30-
const direction = useDirection(() => props.dir)
31-
32-
provideScrollAreaContext({
33-
type() {
34-
return props.type
24+
const scrollAreaRoot = useScrollAreaRoot({
25+
el,
26+
type: props.type,
27+
dir() {
28+
return props.dir
3529
},
36-
dir: direction,
3730
scrollHideDelay: props.scrollHideDelay,
38-
scrollArea,
39-
viewport,
40-
content,
41-
scrollbarX,
42-
scrollbarXEnabled,
43-
onScrollbarXEnabledChange(rendered) {
44-
scrollbarXEnabled.value = rendered
45-
},
46-
scrollbarY,
47-
scrollbarYEnabled,
48-
onScrollbarYEnabledChange(rendered) {
49-
scrollbarYEnabled.value = rendered
50-
},
51-
onCornerWidthChange(width) {
52-
cornerWidth.value = width
53-
},
54-
onCornerHeightChange(height) {
55-
cornerHeight.value = height
56-
},
5731
})
5832
</script>
5933

6034
<template>
61-
<Primitive
62-
:ref="forwardElement"
63-
:dir="direction"
64-
:style="{
65-
'position': 'relative',
66-
// Pass corner sizes as CSS vars to reduce re-renders of context consumers
67-
'--radix-scroll-area-corner-width': `${cornerWidth}px`,
68-
'--radix-scroll-area-corner-height': `${cornerHeight}px`,
69-
}"
70-
>
35+
<Primitive v-bind="normalizeAttrs(scrollAreaRoot.attrs([{ ref: setEl }]), $attrs)">
7136
<slot />
7237
</Primitive>
7338
</template>

0 commit comments

Comments
 (0)