Skip to content

Commit 6233e32

Browse files
feat: Collection
1 parent cd2f8bb commit 6233e32

File tree

2 files changed

+180
-10
lines changed

2 files changed

+180
-10
lines changed

packages/vue-primitives/src/collection/Collection.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ShallowReactive, type ShallowRef, shallowReactive, watchEffect } from 'vue'
1+
import { type ShallowReactive, type ShallowRef, onBeforeUnmount, onBeforeUpdate, onMounted, shallowReactive } from 'vue'
22
import { createContext } from '../hooks/createContext.ts'
33

44
export const ITEM_DATA_ATTR = 'data-radix-collection-item'
@@ -23,21 +23,52 @@ export function createCollection<ItemElement extends HTMLElement, ItemData = obj
2323
function useCollectionItem(currentElement: ShallowRef<ItemElement | undefined>, attrs: Record<string, unknown> = {}) {
2424
const { itemMap } = useCollectionContext()
2525

26-
watchEffect((onClean) => {
27-
const unrefElement = currentElement.value
26+
let unrefElement: ItemElement | undefined
27+
28+
onMounted(() => {
29+
unrefElement = currentElement.value
2830
if (!unrefElement)
2931
return
3032

3133
itemMap.set(unrefElement, {
3234
ref: unrefElement,
3335
attrs: attrs as ItemData,
3436
})
37+
})
3538

36-
onClean(() => {
37-
itemMap.delete(unrefElement)
39+
onBeforeUpdate(() => {
40+
if (!unrefElement)
41+
return
42+
43+
itemMap.set(unrefElement, {
44+
ref: unrefElement,
45+
attrs: attrs as ItemData,
3846
})
3947
})
4048

49+
onBeforeUnmount(() => {
50+
if (!unrefElement)
51+
return
52+
53+
itemMap.delete(unrefElement)
54+
})
55+
56+
// TODO: watch attrs -> onBeforeUpdate
57+
// watch([currentElement, attrs], (_, __, onClean) => {
58+
// const unrefElement = currentElement.value
59+
// if (!unrefElement)
60+
// return
61+
62+
// itemMap.set(unrefElement, {
63+
// ref: unrefElement,
64+
// attrs: attrs as ItemData,
65+
// })
66+
67+
// onClean(() => {
68+
// itemMap.delete(unrefElement)
69+
// })
70+
// })
71+
4172
return {
4273
itemMap,
4374
}

packages/vue-primitives/src/collection/stories/Collection.stories.tsx

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { defineComponent, shallowRef, watchEffect } from 'vue'
2-
import Toggle from '../../toggle/Toggle.vue'
32
import { ITEM_DATA_ATTR, createCollection } from '../index.ts'
43

54
interface ItemData { disabled: boolean }
@@ -20,6 +19,9 @@ const List = defineComponent({
2019
})
2120

2221
const Item = defineComponent({
22+
// props: {
23+
// disabled: { type: Boolean, default: false },
24+
// },
2325
setup(_, { slots, attrs }) {
2426
const currentElement = shallowRef<HTMLElement>()
2527
Collection.useCollectionItem(currentElement, attrs)
@@ -33,11 +35,14 @@ const Item = defineComponent({
3335
})
3436

3537
const LogItems = defineComponent({
36-
setup() {
38+
props: {
39+
name: { type: String, default: 'items' },
40+
},
41+
setup(props) {
3742
const getItems = useCollection()
3843

3944
watchEffect(() => {
40-
console.warn('Items:', getItems())
45+
console.warn(`${props.name}`, getItems())
4146
})
4247

4348
return () => null
@@ -46,8 +51,6 @@ const LogItems = defineComponent({
4651

4752
export default { title: 'Utilities/Collection' }
4853

49-
export const Styled = () => <Toggle class="root">Toggle</Toggle>
50-
5154
export function Basic() {
5255
return (
5356
<List>
@@ -60,3 +63,139 @@ export function Basic() {
6063
</List>
6164
)
6265
}
66+
67+
export function WithElementInBetween() {
68+
return (
69+
<List>
70+
<div style={{ fontVariant: 'small-caps' }}>Colors</div>
71+
<Item>Red</Item>
72+
<Item {...{ disabled: true }}>Green</Item>
73+
<Item>Blue</Item>
74+
<div style={{ fontVariant: 'small-caps' }}>Words</div>
75+
<Item>Hello</Item>
76+
<Item>World</Item>
77+
<LogItems />
78+
</List>
79+
)
80+
}
81+
82+
const Tomato = () => <Item style={{ color: 'tomato' }}>Tomato</Item>
83+
84+
export function WithWrappedItem() {
85+
return (
86+
<List>
87+
<Item>Red</Item>
88+
<Item {...{ disabled: true }}>Green</Item>
89+
<Item>Blue</Item>
90+
<Tomato />
91+
<LogItems />
92+
</List>
93+
)
94+
}
95+
96+
export function WithFragment() {
97+
const countries = (
98+
<>
99+
<Item>France</Item>
100+
<Item {...{ disabled: true }}>UK</Item>
101+
<Item>Spain</Item>
102+
</>
103+
)
104+
return (
105+
<List>
106+
{countries}
107+
<LogItems />
108+
</List>
109+
)
110+
}
111+
112+
const DynamicInsertionDemo = defineComponent({
113+
setup() {
114+
const hasTomato = shallowRef(false)
115+
116+
function setHasTomato(value: boolean) {
117+
hasTomato.value = value
118+
}
119+
120+
function log() {
121+
console.warn('Items:', Array.from(document.querySelectorAll(`[${ITEM_DATA_ATTR}]`)))
122+
}
123+
124+
return () => (
125+
<>
126+
<button onClick={() => setHasTomato(!hasTomato.value)}>
127+
{hasTomato ? 'Remove' : 'Add'}
128+
{' '}
129+
Tomato
130+
</button>
131+
<button onClick={() => log()} style={{ marginLeft: 10 }}>
132+
Force Update
133+
</button>
134+
135+
<List>
136+
<Item>Red</Item>
137+
{ hasTomato.value && <Tomato />}
138+
<Item {...{ disabled: true }}>
139+
Green
140+
</Item>
141+
<Item>Blue</Item>
142+
<LogItems />
143+
</List>
144+
</>
145+
)
146+
},
147+
})
148+
149+
export function DynamicInsertion() {
150+
return <DynamicInsertionDemo />
151+
}
152+
153+
const WithChangingItemDemo = defineComponent({
154+
setup() {
155+
const isDisabled = shallowRef(false)
156+
157+
function setIsDisabled(value: boolean) {
158+
isDisabled.value = value
159+
}
160+
161+
return () => (
162+
<>
163+
<button onClick={() => setIsDisabled(!isDisabled.value)}>
164+
{isDisabled ? 'Enable' : 'Disable'}
165+
{' '}
166+
Green
167+
</button>
168+
169+
<List>
170+
<Item>Red</Item>
171+
<Item {...{ disabled: isDisabled.value }}>Green</Item>
172+
<Item>Blue</Item>
173+
<LogItems />
174+
</List>
175+
</>
176+
)
177+
},
178+
})
179+
180+
export function WithChangingItem() {
181+
return <WithChangingItemDemo />
182+
}
183+
184+
export function Nested() {
185+
return (
186+
<List>
187+
<Item>1</Item>
188+
<Item>
189+
2
190+
<List>
191+
<Item>2.1</Item>
192+
<Item>2.2</Item>
193+
<Item>2.3</Item>
194+
<LogItems name="items inside 2" />
195+
</List>
196+
</Item>
197+
<Item>3</Item>
198+
<LogItems name="top-level items" />
199+
</List>
200+
)
201+
}

0 commit comments

Comments
 (0)