Skip to content

feat(layout): add layout component#364

Merged
gene9831 merged 12 commits into
opentiny:developfrom
SonyLeo:feat/layout-code
Jun 29, 2026
Merged

feat(layout): add layout component#364
gene9831 merged 12 commits into
opentiny:developfrom
SonyLeo:feat/layout-code

Conversation

@SonyLeo

@SonyLeo SonyLeo commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

背景

文档在线预览 🔗

新增 Layout 布局组件,统一承载页面骨架、可收起侧栏、浮层工作区和主区代理滚动条这几类布局能力。

这个组件的目标不是做一个简单的页面容器,而是提供一套可组合、可控的布局基础能力,覆盖以下几类常见场景:

  • 标准页面布局:header / main / footer / left aside / right aside
  • 带侧栏的工作台布局:支持 dock / drawer
  • 可收起、可调宽侧栏:支持 rail、hidden、slide / overlay
  • 浮层布局:支持拖拽、缩放、受控 / 非受控状态
  • 主区代理滚动条:支持滚动宿主与可视滚动条分离

组件能力

1. 标准布局骨架

Layout 提供以下基础插槽:

  • left-aside
  • header
  • main
  • footer
  • right-aside

内部使用 grid 组织整体结构。普通模式下参与正常文档流;浮层模式下通过 Teleport 挂载到 body,并支持拖拽与缩放。

2. 双侧栏模型

左右侧栏都支持统一配置:

  • mode
  • open / defaultOpen
  • expandedWidth / defaultExpandedWidth
  • minExpandedWidth / maxExpandedWidth
  • collapsedWidth
  • collapseEffect
  • resizable

支持两种展示模式:

  • dock:占据布局空间
  • drawer:覆盖在主区之上

支持两种收起表现:

  • overlay:内容区保持原位
  • slide:内容区跟随侧栏宽度变化

collapsedWidth > 0 时,侧栏关闭后进入 rail 状态;否则进入完全隐藏状态。

3. 受控 / 非受控状态

Layout 的侧栏与浮层状态都同时支持:

  • 受控模式:由外部传入当前值并回写
  • 非受控模式:由组件内部初始化并维护后续状态

侧栏:

  • open / defaultOpen
  • expandedWidth / defaultExpandedWidth

浮层:

  • floatingState / defaultFloatingState

当前实现中,状态收口在 useControllableStatedefault* 只用于非受控初始化,受控判定统一基于显式 prop 是否为 undefined

设计说明

一、状态源集中在 root state

useLayoutRootState 负责管理布局的原始状态与受控/非受控同步,包括:

  • 左右侧栏的 open / width / mode / rail / hidden / canResize
  • 浮层状态的初始化、提交与对外回写

这一层只负责状态解析和状态提交,不负责模板结构。

二、结构编排回收到 Layout.vue

这一版没有继续把渲染层和 drawer 行为拆成额外 composable,而是把结构编排保留在 Layout.vue

  • drawer 互斥打开
  • backdrop 显示与关闭
  • resize 过程态桥接
  • layout class / style 派生
  • 插槽是否渲染判断

原因是这些规则只服务于 Layout 自身,保留在根组件里更直接,也更符合当前场景。

三、浮层交互拆成“结构组件 + 子交互组件”

浮层相关能力拆为:

  • LayoutSurface:负责浮层容器、定位、状态同步与交互编排
  • FloatingDragBar:负责拖拽入口
  • FloatingResizeTriggers:负责缩放入口

其中 drag / resize 在输入层面已经解耦,LayoutSurface 只负责统一编排当前交互状态、提交位置尺寸变化,并向外发出浮层事件。

四、侧栏 resize 采用事件上抛

侧栏宽度拖拽链路为:

  • AsideResizeTrigger 负责指针交互与宽度计算
  • AsideContent 负责向父层透传事件
  • Layout 负责承接宽度变化并更新 panel 状态
  • Layout 再向外发出公共 resize 事件

父层只维护一份 isAsideResizing 过程态,用于禁用过渡和切换交互样式,避免父子之间重复维护 resizing 状态。

五、Layout.AsideToggle 通过内部 context 共享最小状态

Layout.AsideToggle 通过 provide/inject 获取内部上下文,但上下文只暴露最小必要能力:

  • isOpen
  • toggle

它不直接暴露完整 panel 状态,也不允许插槽内容拿到一整套可写控制器。这样可以保持数据流单向:

  • 状态从 Layout 向下传递
  • 交互通过事件或最小 action 向上收口

六、双层结构是有意设计

Layout 采用:

  • 根层 tr-layout
  • 内容层 tr-layout__body

根层负责:

  • 浮层定位
  • 外层 outline / shadow
  • drag bar
  • floating resize triggers

内容层负责:

  • grid 布局
  • 背景
  • overflow hidden
  • drawer/backdrop 的裁切层

这样可以同时满足“浮层外沿交互层需要 overflow: visible”和“内容区需要统一裁切”这两类需求。

实现细节

1. 侧栏背景变量语义化

侧栏背景使用显式变量:

  • --tr-layout-left-aside-bg
  • --tr-layout-right-aside-bg

header / main / footer 的背景变量保持一致的命名粒度。

2. drawer 宽度支持显式变量覆盖

drawer 宽度优先由 --tr-layout-drawer-width 控制;未设置时回退到侧栏展开宽度。

3. floating resize handle 收敛为 7 个方向

当前浮层缩放保留 7 个 handle:

  • s
  • e
  • w
  • ne
  • nw
  • se
  • sw

顶部中间 n handle 被移除,避免与顶部拖拽入口冲突。

4. 代理滚动条采用“滚动宿主外置”模型

Layout.ProxyScrollbar 不直接决定主区谁来滚动,而是通过 scrollTarget 接收真实滚动宿主:

  • ProxyScrollbar 负责滚动条显示、拖拽和同步
  • 使用方负责声明真实滚动元素的尺寸、overflow 和是否隐藏原生滚动条

这种方式比组件内部隐式接管滚动宿主样式更稳,也更符合显式契约。

对外 API

Props

Layout

  • mode
  • leftAside
  • rightAside
  • floatingState
  • defaultFloatingState
  • floatingOptions

Layout.ProxyScrollbar

  • scrollTarget

Layout.AsideToggle

  • side

Events

侧栏事件:

  • aside-open-change
  • left-aside-open-change
  • right-aside-open-change
  • aside-resize-start
  • aside-resize
  • aside-resize-end
  • left-aside-resize-start
  • left-aside-resize
  • left-aside-resize-end
  • right-aside-resize-start
  • right-aside-resize
  • right-aside-resize-end

浮层事件:

  • update:floatingState
  • floating-drag-start
  • floating-drag
  • floating-drag-end
  • floating-resize-start
  • floating-resize
  • floating-resize-end

Slots

Layout

  • left-aside
  • header
  • main
  • footer
  • right-aside

Layout.AsideToggle

  • default

其中:

  • left-aside / right-aside 作为纯内容插槽使用
  • 开关状态通过外部受控 props/events 或 Layout.AsideToggle 的默认插槽 { isOpen } 获取

组件结构

核心文件如下:

  • Layout.vue:布局根组件,负责结构编排、drawer 行为和事件桥接
  • LayoutAsideToggle.vue:侧栏开关组件
  • LayoutProxyScrollbar.vue:主区代理滚动条组件
  • LayoutSurface.vue:浮层外壳与浮层交互编排
  • AsideContent.vue:侧栏结构包装
  • AsideResizeTrigger.vue:侧栏拖宽触发器
  • FloatingDragBar.vue:浮层拖拽入口
  • FloatingResizeTriggers.vue:浮层缩放入口
  • useLayoutRootState.ts:原始状态与受控/非受控同步
  • useLayoutContext.ts:provide/inject 上下文
  • usePointerDragSession.ts:指针拖拽会话抽象

验证

  • pnpm.cmd -F @opentiny/tiny-robot build

Summary by CodeRabbit

  • New Features
    • Added new Layout component with left/right aside panels (dock/drawer/rail) including open/close toggles and width resize.
    • Introduced floating mode with draggable and resizable surfaces, emitting floating and aside interaction lifecycle events.
    • Added LayoutProxyScrollbar for rendering a draggable proxy scrollbar tied to a scroll target.
    • Exposed layout components and typing through the main component bundle (including plugin-based installation).
    • Added theme/layout CSS variables for consistent layout sizing and transitions.
  • Bug Fixes
    • Improved drag/resize interaction handling, including safer pointer capture/cleanup, backdrop-based drawer closing, and throttled geometry updates.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds a full layout component system: new public and internal types, state and pointer helpers, floating and aside interaction components, a proxy scrollbar, root layout assembly, and package/style exports.

Changes

Layout component system

Layer / File(s) Summary
Contracts and state
packages/components/src/layout/index.type.ts, packages/components/src/layout/internal.type.ts, packages/components/src/shared/composables/useControllableState.ts, packages/components/src/layout/composables/useLayoutContext.ts, packages/components/src/layout/composables/useLayoutAsideStates.ts, packages/components/src/layout/utils/aside*.ts
Adds the public layout types, internal runtime types, controllable state helper, layout context injection, aside presets, side-aware event emitters, and aside state composition.
Shared utilities and drag session
packages/components/src/layout/utils/number.ts, packages/components/src/layout/utils/cssLength.ts, packages/components/src/layout/utils/slots.ts, packages/components/src/layout/utils/domInteraction.ts, packages/components/src/layout/composables/usePointerDrag.ts, packages/components/src/shared/composables/index.ts
Adds clamp, CSS length parsing, slot-content detection, body interaction locking, pointer-drag session helpers, and the shared composables barrel export.
Floating surface widgets
packages/components/src/layout/utils/surfaceGeometry.ts, packages/components/src/layout/utils/surfaceResize.ts, packages/components/src/layout/components/Floating*.vue, packages/components/src/layout/components/LayoutSurface.vue, packages/components/src/layout/LayoutProxyScrollbar.vue
Adds floating geometry and resize helpers, drag and resize controls, the floating surface controller, and the proxy scrollbar.
Aside shell and root layout
packages/components/src/layout/components/AsideContent.vue, packages/components/src/layout/components/AsideResizeTrigger.vue, packages/components/src/layout/LayoutAsideToggle.vue, packages/components/src/layout/Layout.vue
Adds the aside content, aside resize, and aside toggle components plus the root Layout shell that wires aside state, floating events, and drawer/backdrop rendering.
Bundle exports and styles
packages/components/src/layout/index.ts, packages/components/src/index.ts, packages/components/src/styles/components/index.css, packages/components/src/styles/components/layout.less
Adds the layout component registrations in the package entrypoints and imports the layout stylesheet tokens into the shared component bundle.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Poem

Hop hop, the layout learned to flex and glide,
with drawer panes, drag bars, and scroll thumbs by my side.
(\/)
( •
•)
/ >🥕 I twitched my nose—everything feels neatly tied!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: introducing a new layout component and related layout infrastructure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

✅ Preview build completed successfully!

Click the image above to preview.
Preview will be automatically removed when this PR is closed.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

@SonyLeo SonyLeo changed the title feat(layout-comp): introduce layout components with aside and floating states feat(layout): add layout component Jun 12, 2026
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/index.type.ts
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/composables/createLayoutContext.ts Outdated
Comment thread packages/components/src/layout/LayoutMain.vue Outdated
Comment thread packages/components/src/layout/index.type.ts Outdated
@SonyLeo SonyLeo marked this pull request as ready for review June 16, 2026 07:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/components/src/layout/index.type.ts`:
- Around line 74-82: The LayoutFloatingStateControlProps union type at line
74-82 has both branches requiring a property with type never, making the type
uninhabitable. Remove the never-typed properties from each branch instead: the
first branch should only require floatingState with no defaultFloatingState
property, and the second branch should only have an optional
defaultFloatingState property with no floatingState property. This creates a
proper exclusive union where one branch accepts floatingState and the other
accepts defaultFloatingState.

In `@packages/components/src/layout/LayoutAsideToggle.vue`:
- Around line 37-40: The toggle button in LayoutAsideToggle.vue lacks
accessibility attributes needed for screen readers and users relying on
assistive technology. Add an aria-label attribute to the button element with
class "tr-layout-aside-toggle" to provide an accessible name (especially
important since the default slot may contain only an icon), and add an
aria-pressed or aria-expanded attribute bound to the panel's state to convey
whether the aside panel is currently open or closed. These attributes should be
bound dynamically to reflect the current toggle state.

In `@packages/components/src/layout/utils/surfaceGeometry.ts`:
- Around line 282-288: The `resolveDefaultFloatingRect` function accepts a
`bounds` parameter but ignores it when computing constraints. Currently,
`resolveFloatingConstraints(config)` derives constraints from default viewport
bounds instead of the passed `bounds`, causing the clamped width and height
values to potentially exceed the custom bounds. Modify the function to pass the
`bounds` parameter to `resolveFloatingConstraints` (or update how constraints
are calculated) so that the width and height clamping respects the actual bounds
provided to the function rather than always defaulting to viewport bounds.

In `@packages/components/src/shared/composables/useControllableState.ts`:
- Around line 17-19: The issue is in the useControllableState composable where
internalState is only initialized once with options.defaultValue and never
updated while the component is in controlled mode. When isControlled transitions
from true to false, the resolvedState computed property falls back to the stale
internalState instead of the latest controlled value. Add a watcher that
monitors options.value and synchronizes internalState whenever isControlled is
true, ensuring that when the component transitions to uncontrolled mode,
internalState preserves the last controlled value instead of reverting to the
initial default value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9b176ce3-faf4-4f31-98b8-33f6a4c0c3cc

📥 Commits

Reviewing files that changed from the base of the PR and between 045d26b and 6170efc.

📒 Files selected for processing (31)
  • packages/components/src/index.ts
  • packages/components/src/layout/Layout.vue
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/LayoutProxyScrollbar.vue
  • packages/components/src/layout/components/AsideContent.vue
  • packages/components/src/layout/components/AsideResizeTrigger.vue
  • packages/components/src/layout/components/FloatingResizeTrigger.vue
  • packages/components/src/layout/composables/useLayoutAsideResize.ts
  • packages/components/src/layout/composables/useLayoutContext.ts
  • packages/components/src/layout/composables/useLayoutDrawerActions.ts
  • packages/components/src/layout/composables/useLayoutFloating.ts
  • packages/components/src/layout/composables/useLayoutFloatingDrag.ts
  • packages/components/src/layout/composables/useLayoutFloatingResize.ts
  • packages/components/src/layout/composables/useLayoutProxyScrollbar.ts
  • packages/components/src/layout/composables/useLayoutRenderState.ts
  • packages/components/src/layout/composables/useLayoutRootState.ts
  • packages/components/src/layout/index.ts
  • packages/components/src/layout/index.type.ts
  • packages/components/src/layout/internal.type.ts
  • packages/components/src/layout/utils/asideDefaults.ts
  • packages/components/src/layout/utils/cssLength.ts
  • packages/components/src/layout/utils/domInteraction.ts
  • packages/components/src/layout/utils/emitAsideEvents.ts
  • packages/components/src/layout/utils/math.ts
  • packages/components/src/layout/utils/slots.ts
  • packages/components/src/layout/utils/surfaceGeometry.ts
  • packages/components/src/layout/utils/surfaceResize.ts
  • packages/components/src/shared/composables/index.ts
  • packages/components/src/shared/composables/useControllableState.ts
  • packages/components/src/styles/components/index.css
  • packages/components/src/styles/components/layout.less

Comment thread packages/components/src/layout/index.type.ts
Comment thread packages/components/src/layout/LayoutAsideToggle.vue Outdated
Comment thread packages/components/src/layout/utils/surfaceGeometry.ts Outdated
Comment thread packages/components/src/shared/composables/useControllableState.ts Outdated
Comment thread packages/components/src/layout/index.type.ts
Comment thread packages/components/src/layout/index.type.ts Outdated
Comment thread packages/components/src/layout/index.type.ts Outdated
Comment thread packages/components/src/layout/Layout.vue Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/components/src/layout/composables/useLayoutRootState.ts (1)

113-121: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate resolved floating state by mode to avoid stale state after mode switches.

At Line 114, resolvedFloatingState is not mode-guarded. When mode changes from floating to normal, the last floating state can remain cached and keep resolvedFloating non-empty.

Suggested fix
-  const resolvedFloatingState = computed(() => floatingState.resolvedState.value)
+  const resolvedFloatingState = computed(() =>
+    props.mode === 'floating' ? floatingState.resolvedState.value : undefined,
+  )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/components/src/layout/composables/useLayoutRootState.ts` around
lines 113 - 121, The resolvedFloatingState is not mode-guarded in the
resolvedFloating computed property, causing cached floating state to persist
when the mode switches from floating to normal. Gate the nextFloatingState
assignment by checking the mode first, similar to how nextFloatingOptions is
already guarded - only retrieve floatingState.resolvedState.value when
props.mode === 'floating', otherwise set nextFloatingState to undefined to
prevent stale state from remaining after a mode switch.
packages/components/src/layout/utils/cssLength.ts (1)

6-21: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Accept zero-valued CSS lengths with units.

At Line 6 and Line 19, values like 0rem / 0% now miss the zero fast-path and incorrectly resolve to fallback instead of 0.

Suggested fix
-const ZERO_LENGTH_RE = /^0(?:\.0+)?$/i
+const ZERO_LENGTH_RE = /^0(?:\.0+)?(?:[a-z%]+)?$/i
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/components/src/layout/utils/cssLength.ts` around lines 6 - 21, The
ZERO_LENGTH_RE regex pattern at the top of the resolveCssLengthToPx function
currently only matches plain zero values like "0" or "0.0" but does not match
zero-valued CSS lengths with units such as "0rem", "0%", or "0px". When these
unit-based values are tested against the regex in the function, the test fails
and the function falls through to other logic instead of correctly returning 0.
Update the ZERO_LENGTH_RE regex pattern to also match optional CSS units (such
as rem, px, %, em, vh, vw, etc.) that may appear after the zero value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/components/src/layout/Layout.vue`:
- Around line 110-117: The computed properties leftDockWidth and rightDockWidth
are calculated unconditionally regardless of whether their corresponding aside
slots actually exist. This causes incorrect resize bounds to be applied even
when the slot is not rendered. Gate leftDockWidth to return a value from
getDockedAsideWidth(drawer.left) only when hasLeftAside is true, and similarly
gate rightDockWidth to return a value from getDockedAsideWidth(drawer.right)
only when hasRightAside is true. Otherwise, both should return 0 or a default
value. Apply this same fix to the other locations mentioned (lines 192-193 and
225-226).

---

Outside diff comments:
In `@packages/components/src/layout/composables/useLayoutRootState.ts`:
- Around line 113-121: The resolvedFloatingState is not mode-guarded in the
resolvedFloating computed property, causing cached floating state to persist
when the mode switches from floating to normal. Gate the nextFloatingState
assignment by checking the mode first, similar to how nextFloatingOptions is
already guarded - only retrieve floatingState.resolvedState.value when
props.mode === 'floating', otherwise set nextFloatingState to undefined to
prevent stale state from remaining after a mode switch.

In `@packages/components/src/layout/utils/cssLength.ts`:
- Around line 6-21: The ZERO_LENGTH_RE regex pattern at the top of the
resolveCssLengthToPx function currently only matches plain zero values like "0"
or "0.0" but does not match zero-valued CSS lengths with units such as "0rem",
"0%", or "0px". When these unit-based values are tested against the regex in the
function, the test fails and the function falls through to other logic instead
of correctly returning 0. Update the ZERO_LENGTH_RE regex pattern to also match
optional CSS units (such as rem, px, %, em, vh, vw, etc.) that may appear after
the zero value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6a8038d9-3cb2-4ff5-ad57-2c28c7897bc1

📥 Commits

Reviewing files that changed from the base of the PR and between 69d167a and bb231bf.

📒 Files selected for processing (20)
  • packages/components/src/layout/Layout.vue
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/LayoutProxyScrollbar.vue
  • packages/components/src/layout/components/AsideContent.vue
  • packages/components/src/layout/components/AsideResizeTrigger.vue
  • packages/components/src/layout/components/FloatingDragBar.vue
  • packages/components/src/layout/components/FloatingResizeTriggers.vue
  • packages/components/src/layout/components/LayoutSurface.vue
  • packages/components/src/layout/composables/useLayoutContext.ts
  • packages/components/src/layout/composables/useLayoutRootState.ts
  • packages/components/src/layout/composables/usePointerDragSession.ts
  • packages/components/src/layout/index.type.ts
  • packages/components/src/layout/internal.type.ts
  • packages/components/src/layout/utils/asideEventEmitters.ts
  • packages/components/src/layout/utils/asidePresets.ts
  • packages/components/src/layout/utils/cssLength.ts
  • packages/components/src/layout/utils/layoutElements.ts
  • packages/components/src/layout/utils/number.ts
  • packages/components/src/layout/utils/slots.ts
  • packages/components/src/layout/utils/surfaceGeometry.ts
💤 Files with no reviewable changes (1)
  • packages/components/src/layout/utils/number.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/components/src/layout/utils/asidePresets.ts
  • packages/components/src/layout/utils/layoutElements.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/utils/surfaceGeometry.ts

Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/shared/composables/useControllableState.ts Outdated
Comment thread packages/components/src/shared/composables/useControllableState.ts Outdated
Comment thread packages/components/src/shared/composables/useControllableState.ts Outdated
Comment thread packages/components/src/layout/composables/useLayoutRootState.ts Outdated
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/composables/useLayoutRootState.ts Outdated
Comment thread packages/components/src/layout/composables/useLayoutRootState.ts Outdated
Comment thread packages/components/src/layout/components/LayoutSurface.vue Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/components/src/layout/Layout.vue (1)

62-70: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore keyboard dismissal for drawer overlays.

The backdrop only closes on pointer input. When a drawer is open, Escape should also call closeDrawers() so keyboard users have a reliable dismiss path.

Also applies to: 224-224

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/components/src/layout/Layout.vue` around lines 62 - 70, Restore
keyboard dismissal for drawer overlays by wiring Escape to the existing close
path in Layout.vue. Update the drawer overlay handling so pressing Escape
invokes closeDrawers() for open drawers, using the existing closeDrawers,
leftPanel, and rightPanel logic rather than adding a separate close flow. Ensure
the keyboard handler is attached where drawer overlay events are managed so
keyboard users can dismiss both drawers reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/components/src/layout/components/FloatingDragBar.vue`:
- Around line 30-45: The dragging state in FloatingDragBar.vue is initialized
from useDraggable’s initialValue only once, so it can drift from later prop
updates. Update the FloatingDragBar setup to keep the draggable position in sync
with props.x and props.y by using the position ref returned from useDraggable
and watching those props; when the bar is not actively dragging, assign the
current prop values back into position so the drag anchor stays aligned after
external moves or resizes.

In `@packages/components/src/layout/composables/useLayoutAsideStates.ts`:
- Around line 14-16: The width resolution helper in resolveFiniteNumber still
allows negative numbers to pass through, so collapsedWidth, minExpandedWidth,
maxExpandedWidth, and expandedWidth can end up negative in layout state. Update
resolveFiniteNumber in useLayoutAsideStates to return the fallback for
undefined, non-finite, or any value below zero, so the computed layout CSS
variables and resize bounds are always non-negative.

In `@packages/components/src/layout/Layout.vue`:
- Around line 36-42: The drawer toggle logic in setDrawerOpen and closeDrawers
can emit close events for a sibling drawer even when that aside slot is not
rendered. Update the Layout.vue drawer helpers so they only call
sibling.setOpen(false) when the corresponding aside is actually present, using
hasLeftAside.value and hasRightAside.value to gate the left/right drawer paths,
and keep the existing panel.setOpen(nextOpen) behavior unchanged.
- Around line 23-28: The `rootEl` computed in `Layout.vue` is resolving
`surfaceRef` with `unrefElement`, but `LayoutSurface` is a teleported component
so the component instance does not expose the actual inner DOM node. Update
`LayoutSurface.vue` to create and expose a `surfaceEl` ref via the component’s
expose contract (`LayoutSurfaceExpose`), then change `rootEl` in `Layout.vue` to
read that exposed `surfaceEl` instead of relying on `unrefElement(surfaceRef)`
so consumers get the real layout surface element.

---

Outside diff comments:
In `@packages/components/src/layout/Layout.vue`:
- Around line 62-70: Restore keyboard dismissal for drawer overlays by wiring
Escape to the existing close path in Layout.vue. Update the drawer overlay
handling so pressing Escape invokes closeDrawers() for open drawers, using the
existing closeDrawers, leftPanel, and rightPanel logic rather than adding a
separate close flow. Ensure the keyboard handler is attached where drawer
overlay events are managed so keyboard users can dismiss both drawers reliably.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bddd32c9-f04a-44b2-b49c-6d029567f93b

📥 Commits

Reviewing files that changed from the base of the PR and between bb231bf and e273236.

📒 Files selected for processing (16)
  • packages/components/src/layout/Layout.vue
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/LayoutProxyScrollbar.vue
  • packages/components/src/layout/components/AsideContent.vue
  • packages/components/src/layout/components/AsideResizeTrigger.vue
  • packages/components/src/layout/components/FloatingDragBar.vue
  • packages/components/src/layout/components/FloatingResizeTrigger.vue
  • packages/components/src/layout/components/FloatingResizeTriggers.vue
  • packages/components/src/layout/components/LayoutSurface.vue
  • packages/components/src/layout/composables/useLayoutAsideStates.ts
  • packages/components/src/layout/composables/useLayoutContext.ts
  • packages/components/src/layout/composables/usePointerDrag.ts
  • packages/components/src/layout/index.type.ts
  • packages/components/src/layout/internal.type.ts
  • packages/components/src/layout/utils/surfaceGeometry.ts
  • packages/components/src/shared/composables/useControllableState.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/components/AsideResizeTrigger.vue
  • packages/components/src/layout/index.type.ts

Comment thread packages/components/src/layout/components/FloatingDragBar.vue
Comment thread packages/components/src/layout/composables/useLayoutAsideStates.ts Outdated
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/Layout.vue Outdated
@opentiny opentiny deleted a comment from coderabbitai Bot Jun 25, 2026
Comment thread packages/components/src/layout/composables/usePointerDrag.ts
Comment thread packages/components/src/layout/composables/useLayoutAsideStates.ts Outdated
Comment thread packages/components/src/layout/utils/asideEventEmitters.ts
Comment thread packages/components/src/layout/utils/domInteraction.ts Outdated
@gene9831

Copy link
Copy Markdown
Collaborator

@coderabbitai review latest commit

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

@gene9831 Reviewing the latest commit now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/components/src/layout/components/AsideResizeTrigger.vue (1)

103-114: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't let minWidth override the geometry cap.

When the layout is narrower than props.minWidth + props.oppositeDockWidth + --tr-layout-main-min-width, effectiveMax is forced back up to props.minWidth, so dragging can still squeeze the main area below its configured minimum. Cap the upper bound to the actual available width and handle the available < minWidth case explicitly.

Suggested fix
 function resolveResizeBounds(layoutEl: HTMLElement, asideEl: HTMLElement): ResizeBounds {
   const rootRect = layoutEl.getBoundingClientRect()
   const mainMinWidthValue = getComputedStyle(layoutEl).getPropertyValue('--tr-layout-main-min-width').trim()
   const mainMinWidth = resolveCssLengthToPx(mainMinWidthValue, 320)
   const startWidth = asideEl.getBoundingClientRect().width
-  const maxAvailableWidth = rootRect.width - mainMinWidth - props.oppositeDockWidth
+  const maxAvailableWidth = Math.max(0, rootRect.width - mainMinWidth - props.oppositeDockWidth)
+  const effectiveMax = Math.min(props.maxWidth, maxAvailableWidth)
+  const minWidth = Math.min(props.minWidth, effectiveMax)

   return {
     startWidth,
-    minWidth: props.minWidth,
-    effectiveMax: Math.max(props.minWidth, Math.min(props.maxWidth, maxAvailableWidth)),
+    minWidth,
+    effectiveMax,
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/components/src/layout/components/AsideResizeTrigger.vue` around
lines 103 - 114, In resolveResizeBounds, the current effectiveMax calculation
lets props.minWidth override the actual geometry limit, which can still shrink
the main area too far. Update the ResizeBounds logic in AsideResizeTrigger.vue
so the upper bound is capped by the real available width first, then handle the
case where maxAvailableWidth is below props.minWidth explicitly instead of
forcing it back up. Keep the fix localized to resolveResizeBounds and preserve
the existing minWidth/maxWidth behavior where the layout can actually support
it.
♻️ Duplicate comments (1)
packages/components/src/layout/composables/usePointerDrag.ts (1)

52-53: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Only treat explicit cancellation sentinels as aborted starts.

T can legally be 0 or '', but !context drops those valid drag contexts.

Proposed fix
-    if (!context) {
+    if (context === false || context === null || context === undefined) {
       return
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/components/src/layout/composables/usePointerDrag.ts` around lines 52
- 53, The early return in usePointerDrag is treating any falsy drag context as
missing, which incorrectly rejects valid values like 0 or ''. Update the
start/cancel check to only abort when the context is the explicit cancellation
sentinel (for example a null/undefined-style value used by the drag flow), and
keep valid falsy contexts flowing through the drag start logic in
usePointerDrag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/components/src/layout/composables/usePointerDrag.ts`:
- Around line 61-66: In usePointerDrag, the setPointerCapture error path
currently calls finishDrag('manual') and then rethrows, which turns a
recoverable pointer-capture failure into an uncaught handler error. Update the
try/catch around handleEl.setPointerCapture in usePointerDrag so the catch only
performs the cleanup via finishDrag('manual') and does not rethrow the caught
error.

---

Outside diff comments:
In `@packages/components/src/layout/components/AsideResizeTrigger.vue`:
- Around line 103-114: In resolveResizeBounds, the current effectiveMax
calculation lets props.minWidth override the actual geometry limit, which can
still shrink the main area too far. Update the ResizeBounds logic in
AsideResizeTrigger.vue so the upper bound is capped by the real available width
first, then handle the case where maxAvailableWidth is below props.minWidth
explicitly instead of forcing it back up. Keep the fix localized to
resolveResizeBounds and preserve the existing minWidth/maxWidth behavior where
the layout can actually support it.

---

Duplicate comments:
In `@packages/components/src/layout/composables/usePointerDrag.ts`:
- Around line 52-53: The early return in usePointerDrag is treating any falsy
drag context as missing, which incorrectly rejects valid values like 0 or ''.
Update the start/cancel check to only abort when the context is the explicit
cancellation sentinel (for example a null/undefined-style value used by the drag
flow), and keep valid falsy contexts flowing through the drag start logic in
usePointerDrag.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c318a503-2c3b-4904-be3f-70f7a2825567

📥 Commits

Reviewing files that changed from the base of the PR and between e273236 and 3c6f2ef.

📒 Files selected for processing (11)
  • packages/components/src/layout/Layout.vue
  • packages/components/src/layout/LayoutProxyScrollbar.vue
  • packages/components/src/layout/components/AsideContent.vue
  • packages/components/src/layout/components/AsideResizeTrigger.vue
  • packages/components/src/layout/components/FloatingResizeTrigger.vue
  • packages/components/src/layout/components/LayoutSurface.vue
  • packages/components/src/layout/composables/useLayoutAsideStates.ts
  • packages/components/src/layout/composables/usePointerDrag.ts
  • packages/components/src/layout/internal.type.ts
  • packages/components/src/layout/utils/asideEventEmitters.ts
  • packages/components/src/layout/utils/domInteraction.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/components/src/layout/components/FloatingResizeTrigger.vue
  • packages/components/src/layout/Layout.vue
  • packages/components/src/layout/components/LayoutSurface.vue
  • packages/components/src/layout/LayoutProxyScrollbar.vue

Comment thread packages/components/src/layout/composables/usePointerDrag.ts
Comment thread packages/components/src/layout/utils/slots.ts
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/LayoutProxyScrollbar.vue
Comment thread packages/components/src/layout/LayoutProxyScrollbar.vue
@gene9831 gene9831 merged commit 79556a0 into opentiny:develop Jun 29, 2026
4 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🧹 Preview Cleaned Up

The preview deployment has been removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants