Skip to content

Commit 70aaf4a

Browse files
committed
fix(CodeTree/Tree): restore item wrapper with presentation role
This partially reverts #4945 as it was breaking focus navigation.
1 parent 019b0b7 commit 70aaf4a

File tree

7 files changed

+305
-294
lines changed

7 files changed

+305
-294
lines changed

playgrounds/nuxt/app/pages/components/tree.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const modelValueWithMappedId = ref<(typeof itemsWithMappedId)[number]>()
8585
:nested="nested"
8686
multiple
8787
v-bind="props"
88+
class="w-full"
8889
/>
8990
</Matrix>
9091

src/runtime/components/Tree.vue

Lines changed: 73 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -233,94 +233,99 @@ const defaultExpanded = computed(() =>
233233
<!-- eslint-disable vue/no-template-shadow -->
234234
<template>
235235
<DefineItemTemplate v-slot="{ item, index, level }">
236-
<TreeItem
237-
v-slot="{ isExpanded, isSelected, isIndeterminate, handleSelect, handleToggle }"
238-
:level="level"
239-
:value="item"
236+
<li
237+
role="presentation"
240238
:class="!!nested && level > 1 ? ui.itemWithChildren({ class: [props.ui?.itemWithChildren, item.ui?.itemWithChildren] }) : ui.item({ class: [props.ui?.item, item.ui?.item] })"
241-
@toggle="item.onToggle"
242-
@select="item.onSelect"
243239
>
244-
<slot
245-
:name="((item.slot ? `${item.slot}-wrapper` : 'item-wrapper') as keyof TreeSlots<T>)"
246-
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
247-
:item="(item as Extract<T[number], { slot: string; }>)"
240+
<TreeItem
241+
v-slot="{ isExpanded, isSelected, isIndeterminate, handleSelect, handleToggle }"
242+
:level="level"
243+
:value="item"
244+
as-child
245+
@toggle="item.onToggle"
246+
@select="item.onSelect"
248247
>
249-
<button
250-
type="button"
251-
:disabled="item.disabled || disabled"
252-
:data-expanded="isExpanded"
253-
:class="ui.link({ class: [props.ui?.link, item.ui?.link, item.class], selected: isSelected, disabled: item.disabled || disabled })"
254-
:style="!nested && level > 1 ? { paddingLeft: flattenedPaddingFormula(level) } : undefined"
248+
<slot
249+
:name="((item.slot ? `${item.slot}-wrapper` : 'item-wrapper') as keyof TreeSlots<T>)"
250+
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
251+
:item="(item as Extract<T[number], { slot: string; }>)"
255252
>
256-
<slot
257-
:name="((item.slot || 'item') as keyof TreeSlots<T>)"
258-
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
259-
:item="(item as Extract<T[number], { slot: string; }>)"
253+
<button
254+
type="button"
255+
:disabled="item.disabled || disabled"
256+
:class="ui.link({ class: [props.ui?.link, item.ui?.link, item.class], selected: isSelected, disabled: item.disabled || disabled })"
257+
:style="!nested && level > 1 ? { paddingLeft: flattenedPaddingFormula(level) } : undefined"
258+
tabindex="0"
260259
>
261260
<slot
262-
:name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof TreeSlots<T>)"
261+
:name="((item.slot || 'item') as keyof TreeSlots<T>)"
263262
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
264263
:item="(item as Extract<T[number], { slot: string; }>)"
265-
>
266-
<UIcon
267-
v-if="item.icon"
268-
:name="item.icon"
269-
:class="ui.linkLeadingIcon({ class: [props.ui?.linkLeadingIcon, item.ui?.linkLeadingIcon] })"
270-
/>
271-
<UIcon
272-
v-else-if="item.children?.length"
273-
:name="isExpanded ? (expandedIcon ?? appConfig.ui.icons.folderOpen) : (collapsedIcon ?? appConfig.ui.icons.folder)"
274-
:class="ui.linkLeadingIcon({ class: [props.ui?.linkLeadingIcon, item.ui?.linkLeadingIcon] })"
275-
/>
276-
</slot>
277-
278-
<span
279-
v-if="getItemLabel(item) || !!slots[(item.slot ? `${item.slot}-label`: 'item-label') as keyof TreeSlots<T>]"
280-
:class="ui.linkLabel({ class: [props.ui?.linkLabel, item.ui?.linkLabel] })"
281264
>
282265
<slot
283-
:name="((item.slot ? `${item.slot}-label`: 'item-label') as keyof TreeSlots<T>)"
284-
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
285-
:item="(item as Extract<T[number], { slot: string; }>)"
286-
>
287-
{{ getItemLabel(item) }}
288-
</slot>
289-
</span>
290-
291-
<span
292-
v-if="item.trailingIcon || item.children?.length || !!slots[(item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof TreeSlots<T>]"
293-
:class="ui.linkTrailing({ class: [props.ui?.linkTrailing, item.ui?.linkTrailing] })"
294-
>
295-
<slot
296-
:name="((item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof TreeSlots<T>)"
266+
:name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof TreeSlots<T>)"
297267
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
298268
:item="(item as Extract<T[number], { slot: string; }>)"
299269
>
300270
<UIcon
301-
v-if="item.trailingIcon"
302-
:name="item.trailingIcon"
303-
:class="ui.linkTrailingIcon({ class: [props.ui?.linkTrailingIcon, item.ui?.linkTrailingIcon] })"
271+
v-if="item.icon"
272+
:name="item.icon"
273+
:class="ui.linkLeadingIcon({ class: [props.ui?.linkLeadingIcon, item.ui?.linkLeadingIcon] })"
304274
/>
305275
<UIcon
306276
v-else-if="item.children?.length"
307-
:name="trailingIcon ?? appConfig.ui.icons.chevronDown"
308-
:class="ui.linkTrailingIcon({ class: [props.ui?.linkTrailingIcon, item.ui?.linkTrailingIcon] })"
277+
:name="isExpanded ? (expandedIcon ?? appConfig.ui.icons.folderOpen) : (collapsedIcon ?? appConfig.ui.icons.folder)"
278+
:class="ui.linkLeadingIcon({ class: [props.ui?.linkLeadingIcon, item.ui?.linkLeadingIcon] })"
309279
/>
310280
</slot>
311-
</span>
312-
</slot>
313-
</button>
314-
</slot>
315281

316-
<ul
317-
v-if="nested && item.children?.length && isExpanded"
318-
role="group"
319-
:class="ui.listWithChildren({ class: [props.ui?.listWithChildren, item.ui?.listWithChildren] })"
320-
>
321-
<ReuseTreeTemplate :items="item.children" :level="level + 1" />
322-
</ul>
323-
</TreeItem>
282+
<span
283+
v-if="getItemLabel(item) || !!slots[(item.slot ? `${item.slot}-label`: 'item-label') as keyof TreeSlots<T>]"
284+
:class="ui.linkLabel({ class: [props.ui?.linkLabel, item.ui?.linkLabel] })"
285+
>
286+
<slot
287+
:name="((item.slot ? `${item.slot}-label`: 'item-label') as keyof TreeSlots<T>)"
288+
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
289+
:item="(item as Extract<T[number], { slot: string; }>)"
290+
>
291+
{{ getItemLabel(item) }}
292+
</slot>
293+
</span>
294+
295+
<span
296+
v-if="item.trailingIcon || item.children?.length || !!slots[(item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof TreeSlots<T>]"
297+
:class="ui.linkTrailing({ class: [props.ui?.linkTrailing, item.ui?.linkTrailing] })"
298+
>
299+
<slot
300+
:name="((item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof TreeSlots<T>)"
301+
v-bind="{ index, level, expanded: isExpanded, selected: isSelected, indeterminate: isIndeterminate, handleSelect, handleToggle }"
302+
:item="(item as Extract<T[number], { slot: string; }>)"
303+
>
304+
<UIcon
305+
v-if="item.trailingIcon"
306+
:name="item.trailingIcon"
307+
:class="ui.linkTrailingIcon({ class: [props.ui?.linkTrailingIcon, item.ui?.linkTrailingIcon] })"
308+
/>
309+
<UIcon
310+
v-else-if="item.children?.length"
311+
:name="trailingIcon ?? appConfig.ui.icons.chevronDown"
312+
:class="ui.linkTrailingIcon({ class: [props.ui?.linkTrailingIcon, item.ui?.linkTrailingIcon] })"
313+
/>
314+
</slot>
315+
</span>
316+
</slot>
317+
</button>
318+
</slot>
319+
320+
<ul
321+
v-if="nested && item.children?.length && isExpanded"
322+
role="group"
323+
:class="ui.listWithChildren({ class: [props.ui?.listWithChildren, item.ui?.listWithChildren] })"
324+
>
325+
<ReuseTreeTemplate :items="item.children" :level="level + 1" />
326+
</ul>
327+
</TreeItem>
328+
</li>
324329
</DefineItemTemplate>
325330

326331
<DefineTreeTemplate v-slot="{ items, level }">

src/runtime/components/prose/CodeTree.vue

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -146,50 +146,55 @@ onBeforeUpdate(() => rerenderCount.value++)
146146
<!-- eslint-disable vue/no-template-shadow -->
147147
<template>
148148
<DefineTreeTemplate v-slot="{ items, level }">
149-
<TreeItem
149+
<li
150150
v-for="(item, index) in items"
151151
:key="`${level}-${index}`"
152-
v-slot="{ isExpanded, isSelected }"
153-
:level="level"
154-
:value="item"
152+
role="presentation"
155153
:class="level > 1 ? ui.itemWithChildren({ class: props.ui?.itemWithChildren }) : ui.item({ class: props.ui?.item })"
156154
>
157-
<button
158-
type="button"
159-
:data-expanded="isExpanded"
160-
:class="ui.link({ class: props.ui?.link, active: isSelected })"
155+
<TreeItem
156+
v-slot="{ isExpanded, isSelected }"
157+
:level="level"
158+
:value="item"
159+
as-child
161160
>
162-
<UIcon
163-
v-if="item.children?.length"
164-
:name="isExpanded ? appConfig.ui.icons.folderOpen : appConfig.ui.icons.folder"
165-
:class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon })"
166-
/>
167-
<UCodeIcon
168-
v-else
169-
:filename="item.label"
170-
:class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon })"
171-
/>
172-
173-
<span :class="ui.linkLabel({ class: props.ui?.linkLabel })">
174-
{{ item.label }}
175-
</span>
176-
177-
<span v-if="item.children?.length" :class="ui.linkTrailing({ class: props.ui?.linkTrailing })">
161+
<button
162+
type="button"
163+
:class="ui.link({ class: props.ui?.link, active: isSelected })"
164+
tabindex="0"
165+
>
178166
<UIcon
179-
:name="appConfig.ui.icons.chevronDown"
180-
:class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon })"
167+
v-if="item.children?.length"
168+
:name="isExpanded ? appConfig.ui.icons.folderOpen : appConfig.ui.icons.folder"
169+
:class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon })"
170+
/>
171+
<UCodeIcon
172+
v-else
173+
:filename="item.label"
174+
:class="ui.linkLeadingIcon({ class: props.ui?.linkLeadingIcon })"
181175
/>
182-
</span>
183-
</button>
184176

185-
<ul
186-
v-if="item.children?.length && isExpanded"
187-
role="group"
188-
:class="ui.listWithChildren({ class: props.ui?.listWithChildren })"
189-
>
190-
<ReuseTreeTemplate :items="item.children" :level="level + 1" />
191-
</ul>
192-
</TreeItem>
177+
<span :class="ui.linkLabel({ class: props.ui?.linkLabel })">
178+
{{ item.label }}
179+
</span>
180+
181+
<span v-if="item.children?.length" :class="ui.linkTrailing({ class: props.ui?.linkTrailing })">
182+
<UIcon
183+
:name="appConfig.ui.icons.chevronDown"
184+
:class="ui.linkTrailingIcon({ class: props.ui?.linkTrailingIcon })"
185+
/>
186+
</span>
187+
</button>
188+
189+
<ul
190+
v-if="item.children?.length && isExpanded"
191+
role="group"
192+
:class="ui.listWithChildren({ class: props.ui?.listWithChildren })"
193+
>
194+
<ReuseTreeTemplate :items="item.children" :level="level + 1" />
195+
</ul>
196+
</TreeItem>
197+
</li>
193198
</DefineTreeTemplate>
194199

195200
<div v-bind="$attrs" :class="ui.root({ class: [props.ui?.root, props.class] })">

src/theme/prose/code-tree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default (options: Required<NuxtOptions['ui']>) => ({
1111
linkLeadingIcon: 'size-4 shrink-0',
1212
linkLabel: 'truncate',
1313
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
14-
linkTrailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[expanded=true]:rotate-180',
14+
linkTrailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-expanded:rotate-180',
1515
content: 'overflow-hidden lg:col-span-2 flex flex-col [&>div]:my-0 [&>div]:flex-1 [&>div]:flex [&>div]:flex-col [&>div>div]:border-0 [&>div>pre]:border-b-0 [&>div>pre]:border-s-0 [&>div>pre]:border-e-0 [&>div>pre]:rounded-l-none [&>div>pre]:flex-1 [&>div]:overflow-y-auto'
1616
},
1717
variants: {

src/theme/tree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default (options: Required<ModuleOptions>) => ({
1010
linkLeadingIcon: 'shrink-0 relative',
1111
linkLabel: 'truncate',
1212
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
13-
linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-[expanded=true]:rotate-180'
13+
linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-expanded:rotate-180'
1414
},
1515
variants: {
1616
virtualize: {

0 commit comments

Comments
 (0)