Navigation View 导航视图

FluereNavigationView + FluereNavigationViewItem(含 ...ItemHeader / ...ItemSeparator)对位 WinUI 3 / Windows App SDK 的 Microsoft.UI.Xaml.Controls.NavigationView:左侧可折叠面板 + 内容区,支持分组、多级子项、选中指示条与内置「设置」项,DisplayMode 状态机与键位逐条抄源码。

<FluereNavigationView v-model:selected-value="selected" label="导航" pane-title="Explorer">
  <FluereNavigationViewItemHeader content="快速访问" />
  <FluereNavigationViewItem value="home" content="主页">
    <template #icon><FluentIconStar24Regular :size="20" /></template>
  </FluereNavigationViewItem>
  <template #content>内容区</template>
</FluereNavigationView>
当前选中:主页。面板宽度按容器宽度自适应(Auto 档阈值 1008 / 641)。
基础形态:分组 + 图标 + 选中指示条 + 设置项

版本与证据口径

项值
规格源 tagwinui3/release/2.5.1(commit ba3a8d5)
关键文件src/controls/dev/NavigationView/{NavigationView.idl,NavigationView.xaml,NavigationView_themeresources.xaml,NavigationView.cpp,NavigationViewItem.cpp,NavigationViewItemPresenter.cpp,NavigationViewItemHeader.cpp,NavigationViewItemSeparator.cpp} + Generated/{NavigationView,NavigationViewItem}.properties.cpp + SplitView/SplitView_themeresources.xaml + Strings/{en-us,zh-CN}/Resources.resw
内建文案开关按钮 / 设置项共 4 条逐字取自上游 Resources.resw(见 packages/ui/src/navigation-view/locale.ts 的对照表)

perf2026 不采纳:上游存在 NavigationBackButton_perf2026.xaml(已挂进 NavigationView.vcxitems),本库沿用 0.5.1 TitleBar 的裁定 —— 不读、不参考、不产生对应分支。

DisplayMode 状态机

paneDisplayMode(对齐 NavigationViewPaneDisplayMode)是配置,displayMode 是运行时算出来的结果,两者不要混。

paneDisplayMode行为
auto(缺省)按容器宽度自适应(下表)
left恒 Expanded(面板 320px)
left-compact恒 Compact(面板 48px 图标轨道)
left-minimal同 Compact(见下「与 WinUI 的差异」第 2 条)

auto 档的判据逐字照抄 NavigationView.cpp:1452-1465(阈值缺省来自 NavigationView.idl:176-180):

容器宽度上游结果本库结果
width >= 1008ExpandedExpanded
0 < width < 641MinimalCompact(降级,见差异表)
其余(含 width === 0)CompactCompact
width === 641CompactCompact(判据是严格小于)
NaNCompactCompact
面板宽度随模式变化:展开 320px、紧凑 48px(图标轨道)。
Auto 档按容器宽度切换:≥ 1008 展开、0 < 宽 < 641 紧凑(上游是 Minimal,本库降级为紧凑)、其余紧凑。
DisplayMode 三档与 Auto 自适应

面板开合语义

isPaneOpen 受 DisplayMode 状态机支配(逐条对齐 NavigationView.cpp:1494-1512):

  • 进入 Expanded:仅当 isPaneVisible 为真且用户没有手动关过才自动开;
  • Expanded → Compact:无条件关;
  • 进入 Minimal:无条件关(本库 Minimal 降级为 Compact,因此「非 Expanded 一律关」);
  • 手动关过是一个全局标志(上游 m_wasForceClosed,NavigationView.h:466)—— 不存在按模式分别记忆;切换 pane-display-mode 会清掉它(:4211)。

开关按钮的点击逻辑所有模式共用一段(:1662-1674):开 → 关并置标志;关 → 开并清标志。isPaneToggleButtonVisible 只控可见性,不控可开合性(:4403-4407)。

面板收起时不是归零,而是 48px 的图标轨道(上游 ListSizeCompact 把 PaneContentGrid.Width 设为 CompactPaneLength,NavigationView.xaml:83),此时只显示图标、文本留在无障碍树里。

折叠态(ListSizeCompact)的对齐规则

收起后的 48px 轨道由三条规则决定,与 paneDisplayMode 无关,只看「紧凑且关闭」(上游 m_isClosedCompact,NavigationViewItem.cpp:253-263):

规则A 级依据
面板标题与 #paneHeader 一并隐藏ListSizeCompact 置 Collapsed(NavigationView.xaml:85)
开关行横向不留内边距,视觉内缩由按钮自身的 Padding = 4,2 提供NavigationViewButtonHolderGridMargin = 0,4(:232)
开关按钮的图标列与导航项同宽(SmallerIconWidth = CompactPaneLength - 8):611-613、NavigationViewItemPresenter.cpp:289

因此折叠态的汉堡图标与每个导航项图标严格同列(都落在 24px 中线上),项背景到面板两侧的间隙也对称(左右各 4px = NavigationViewItemButtonMargin 的横向值)。

这一带曾出现四处偏差(汉堡图标被压成 7.41 × 20、标题仍占位、行背景右侧被裁 3px、齿轮渲染成实心块):前三处由上表的开关行 / 标题 / 分居线规则负责,最后一处是设置项图标的字形选错(应为线性齿轮,而不是只有外轮廓的截断路径)。根因与修法逐条记在 packages/ui/src/navigation-view/navigation-view.vue 的文件头《折叠态的四个 UI 修正》,契约测试见同目录 navigation-view.test.ts 的「折叠态(ListSizeCompact)UI 回归」。

键盘(逐条对齐源码)

触发键前置条件效果
Enter焦点在项上invoke(选中 + 展开 + 抛 itemInvoked)
Space焦点在项上且未按 Alt同上;Alt+Space 明确不处理(让出系统窗口菜单,:2752-2761)
↑ / ↓焦点在项上在可见项之间移动焦点;跨层(进 / 出展开的子项、回到父项)与源码的 XYFocus + 手动聚焦等价
Home / End焦点在项上焦点到列表首 / 末可聚焦项(:2906-2950)
← + Alt面板开着且可轻触关闭关面板(:3136-3145)
Tab—移出面板(roving tabindex:一组里只有一个 Tab 停靠点)

源码里没有的键(负面证据,不要按直觉补):→(NavigationView 对 VirtualKey::Right 零命中)、Esc(关 Minimal 面板是 core SplitView.cpp:576-581 的职责,不在 NavigationView 里)、Ctrl+数字。

按住不放不会连发 invoke:上游用 args.KeyStatus().WasKeyDown 挡(:2724),本库对应 event.repeat。

选择与展开语义

  • 点击有子项的项「既选中也切换展开」(OnNavigationViewItemInvoked:1067 两件事都做,不是二选一)。
  • selects-on-invoked="false":invoke 只展开、不改选中(:1048),适合当分组标题用;反过来用 selectedValue 强行选中它会被回滚(:2281-2292),本库对此回退为「无选中」并给开发期警告。
  • isChildSelected:选中子项时,其整条祖先链都会拿到该状态(UpdateIsChildSelected:2509-2547);本库由父组件在渲染期推导(只读),无需消费方传。
  • 选中指示条下钻:从选中项往下走,只要「自身未选中但后代被选中」且父项已展开,就画在最低可见层(FindLowestLevelContainerToDisplaySelectionIndicator:5562-5593)。
  • 面板关闭时顶层项强制折叠但记住状态(HandleExpansionStateMemory,NavigationViewItem.cpp:269-285):重新展开面板会自动恢复。只对顶层项生效。
当前选中:专辑。选中子项时父项会拿到 is-child-selected,选中指示条下钻到子项。
分组、层级与祖先链选中

设置项与插槽

插槽对应 WinUI说明
默认插槽MenuItems放 Item / ItemHeader / ItemSeparator
#contentContentPresenter窗口内容区
#paneHeaderPaneHeader开关按钮下方(仅展开档显示)
#paneFooterPaneFooter面板底部、设置项上方
#settingsSettingsItem(FooterMenuItems)整块替换内置设置项

内置设置项选中时,selectedValue 取保留哨兵值(导出常量 FLUERE_NAVIGATION_SETTINGS_VALUE),itemInvoked 的 isSettingsInvoked 为 true。消费方不得把菜单项的 value 设成这个字符串(契约测试有断言守这条)。

点任意项都会抛 itemInvoked;设置项的 isSettingsInvoked 为 true。
设置项、PaneFooter 与事件

与 WinUI 的差异(有意为之,逐条记录)

  1. 只做左侧面板:PaneDisplayMode 的 Top 不实现 —— 顶部导航是另一套模板(TopNavArea / TopNavGrid / 溢出 Flyout / 手柄导航 BumperNavigation)。连带不实现 ShoulderNavigationEnabled(源码里对非 Top 恒返回 false,:3165-3170,即左侧面板本来就没有手柄导航)、OverflowLabelMode、AutoSuggestBox、返回 / 关闭按钮。
  2. Minimal 档降级为 Compact:上游 Minimal 是「面板浮层化 + 焦点陷阱」(:4588-4596 把 KeyboardNavigationMode 设成 Cycle),浮层焦点管理属 0.5.7 useFocusTrap 的范围。本库在宽度落在 Minimal 区间时渲染为 Compact 形态(面板常驻 48px),不弹浮层。
  3. 子项是「数据型子组件」:三个子组件不会挂载(父组件在渲染期读 vnode props / slots,理由见 collection.ts),因此它们不能 emit,子项插槽不支持作用域参数。
  4. SelectedItem 换成「值标识」:WinUI 用对象引用,Vue 侧用 selectedValue(与 FluereTabView 的选中口径一致)。
  5. 不做子项 flyout:上游「紧凑且关闭时把子项搬进 ChildrenFlyout」(NavigationViewItem.cpp:908-911)。本库紧凑时隐藏 chevron 且不渲染子项,避免出现点了没反应的箭头;展开面板后子项照常内联显示。
  6. 不做「指示条在项之间滑动」的位置动画:上游 PlayIndicatorAnimations(NavigationView.cpp:2185-2235)用 Composition 算前后项位移,600ms / 三段缓动 + 200ms 中心点动画。本库只做当前项指示条淡入淡出(上游 ShowSelectionIndicator 就是直接改 Opacity,NavigationViewItem.cpp:402-408,无过渡),静态形态一致。
  7. chevron 用 CSS 旋转表达两端:上游默认模板里 RotateExpandCollapseChevron 只是 Begin() 两个不存在的 Storyboard(NavigationView.xaml 里 Storyboard 计数 0、无 Rotate/Angle)⇒ 对默认模板是 no-op,真正的旋转由 AnimatedIcon(AnimatedChevronUpDownSmallVisualSource)承担。本库不引入 Lottie,只表达「展开 180° / 收起 0°」的静态两端。

证据缺口(显式标注)

  • chevron 逐帧关键帧:LottieGen 产物给了总时长 433.33ms 与 18 段区间 marker(AnimatedChevronUpDownSmallVisualSource.cpp:104,782,789-825),但完整路径插值在 JSON 源里(不在本快照)。本库只做静态两端 + 单段 50ms 过渡(覆写 --fui-navigation-view-chevron-duration)。
  • ExpandCollapseRotate{Expanded,Collapsed}Storyboard 的时长取不到:NavigationViewItemPresenter.cpp:73-74 查找这两个名字,但默认模板里不存在,因此没有可引用的数值。
  • 系统强调色:AccentFillColorDefaultBrush 的最终值由系统运行时决定,源码树只给别名;本库沿用仓库既有映射 --colorBrandBackground。
  • 高对比度:HC 色板来自系统运行时(themeresources:136-201 全是 SystemColor* 引用),本库不实现 HC 专项,只保留 forced-colors 下的焦点环(0.4.3 原语已处理)。

令牌映射

WinUI 资源最终色(亮 / 暗)本库 token
NavigationViewDefaultPaneBackground ← AcrylicInAppFillColorDefaultBrush#F9F9F9 / #2C2C2C--acrylicInAppFillColorDefault
NavigationViewExpandedPaneBackground ← SolidBackgroundFillColorTransparent全透明--colorTransparentBackground(同观感、非同字面量,见组件注释第 5 条)
NavigationViewContentBackground ← LayerFillColorDefaultBrush#80FFFFFF / #4C3A3A3A--layerFillColorDefault
NavigationViewItemBackground* ← SubtleFillColor{Transparent,Secondary,Tertiary}Brush—--subtleFillColorTransparent / --subtleFillColorSecondary / --subtleFillColorTertiary
NavigationViewItemForeground* ← TextFillColor{Primary,Secondary,Disabled}Brush—--colorNeutralForeground1 / --colorNeutralForeground2 / --colorNeutralForegroundDisabled
NavigationViewSelectionIndicatorForeground ← AccentFillColorDefaultBrush系统强调色--colorBrandBackground
NavigationViewItemSeparatorForeground ← DividerStrokeColorDefaultBrush#0F000000 / #15FFFFFF--dividerStrokeColorDefault
NavigationViewContentGridBorderBrush ← CardStrokeColorDefaultBrush#0F000000 / #19000000--colorNeutralStrokeAlpha(沿用 InfoBar / ContentDialog / TabView 的既有映射)
禁用不透明度 ← ListViewItemDisabledThemeOpacity0.3--fui-navigation-view-disabled-opacity

几何(逐条来自 NavigationView_themeresources.xaml)

量值出处
项最小高度36:217
图标盒高 / 图标列宽16 / max(0, CompactPaneLength - 8) = 40:219、:611-613
选中指示条3 × 16、圆角 2:220-222
分组标题行高 / 字号40 / 14 SemiBoldNavigationView.xaml:564、:1081-1086
分隔线1px、Margin 0,3,0,4:223、:247
层级缩进Depth() × 31NavigationViewItemBase.h:63
开关按钮40 × 36(MinWidth × MinHeight);紧凑关闭时按钮宽 = CompactPaneLength(48):205-206、NavigationView.cpp:1823-1829
圆角项 OverlayCornerRadius(8)、按钮 ControlCornerRadius(4):447、:283
面板宽Expanded 320 / Compact 48NavigationView.idl:195,198

面板开合动效

面板宽度的开合动画来自 SplitView 模板(NavigationView 的 pane 由内部 SplitView 承载):

方向资源值关键帧缓动
展开SplitViewPaneAnimationOpenDuration200mscubic-bezier(0, 0.35, 0.15, 1)
收起SplitViewPaneAnimationCloseDuration100ms同上

本库把两个时长映射到 --durationNormal / --durationFaster,并在 prefers-reduced-motion 或 motion="none" 下置 0ms —— 关闭动效后静态形态保留(面板仍可开合、指示条仍可见、箭头仍指向正确方向)。

API

FluereNavigationView

Prop类型缺省对应 WinUI
paneDisplayMode'auto' | 'left' | 'left-compact' | 'left-minimal''auto'PaneDisplayMode
isPaneOpenboolean—IsPaneOpen(v-model:is-pane-open)
defaultIsPaneOpenbooleantrueIsPaneOpen 的依赖属性默认值
openPaneLengthnumber令牌 320OpenPaneLength
compactPaneLengthnumber令牌 48CompactPaneLength
compactModeThresholdWidthnumber641CompactModeThresholdWidth
expandedModeThresholdWidthnumber1008ExpandedModeThresholdWidth
isPaneToggleButtonVisiblebooleantrueIsPaneToggleButtonVisible
isPaneVisiblebooleantrueIsPaneVisible
isSettingsVisiblebooleantrueIsSettingsVisible
selectedValuestring | number—SelectedItem(v-model:selected-value)
defaultSelectedValuestring | number——
selectionFollowsFocus'disabled' | 'enabled''disabled'SelectionFollowsFocus
paneTitlestring''PaneTitle
labelstring—AutomationProperties.Name
dir'ltr' | 'rtl''ltr'FlowDirection
motion'auto' | 'none''auto'—
localestring——
事件 / 插槽载荷 / 说明
update:isPaneOpenboolean
update:selectedValuestring | number | undefined
itemInvoked{ value, content, isSettingsInvoked, source: 'pointer' | 'keyboard' | 'focus' }
selectionChanged{ value, previousValue, isSettingsSelected }
displayModeChanged{ displayMode: 'minimal' | 'compact' | 'expanded' }
#content / #paneHeader / #paneFooter / #settings见上文《设置项与插槽》

FluereNavigationViewItem

Prop类型缺省对应 WinUI
valuestring | number必填(Vue 侧的项身份)
contentstring''Content
selectsOnInvokedbooleantrueSelectsOnInvoked
isExpandedboolean—IsExpanded(v-model:is-expanded)
defaultIsExpandedbooleanfalseIsExpanded 的默认值
hasUnrealizedChildrenbooleanfalseHasUnrealizedChildren
isChildSelectedboolean推导IsChildSelected(本库只读)
ariaLabelstring—AutomationProperties.Name
disabledbooleanfalseControl.IsEnabled
插槽说明
默认插槽项文本 / 富内容(优先于 content)
#icon图标(建议 20px)
#children下级项(递归,可继续嵌 #children)

FluereNavigationViewItemHeader / FluereNavigationViewItemSeparator

组件Props对应 WinUI
FluereNavigationViewItemHeadercontentNavigationViewItemHeader
FluereNavigationViewItemSeparator—NavigationViewItemSeparator

两者都不可聚焦、对 AT 隐藏(上游 IsTabStop=False + AccessibilityView=Raw,NavigationView.xaml:505-509、:574-576)。

浏览器实测

样式契约测试读的是 SFC / CSS 源码文本(jsdom 不解析 var(),见 style-spec §7.1), 因此「面板样式有没有真的生效」必须在真实浏览器里量计算样式 —— 尤其是本组件的行视觉写在 非 scoped 的 navigation-view.css 里(同一套行由父组件与递归列表组件分别渲染, 两个 data-v-* 不同,各写一份 scoped 块必然漂移)。实测结论见组件注释与 docs/todo.md 的 0.5.4 记录。

v0.5.0-alpha.1