Skip to content

feat: enhance event handling in suggestion components#117

Merged
gene9831 merged 4 commits intoopentiny:release/v0.2.xfrom
gene9831:fix/on-click-outside
Jun 23, 2025
Merged

feat: enhance event handling in suggestion components#117
gene9831 merged 4 commits intoopentiny:release/v0.2.xfrom
gene9831:fix/on-click-outside

Conversation

@gene9831
Copy link
Copy Markdown
Collaborator

@gene9831 gene9831 commented Jun 17, 2025

  • 优化使用到 onClickOutside API 的代码,使用内置的 ignore 来忽略点击元素
  • 使用 SuggestionPills 配置式时,将 events 下的属性全部迁移到 props,使用 onXXX 来传入事件回调
  • 优化计算 SuggestionPills 最大宽度的逻辑,保证动态添加按钮后,最大宽度更新正确

Summary by CodeRabbit

  • New Features

    • Improved "show more" button logic for suggestion pills, providing more accurate display of overflowing items.
    • Added support for direct event handler props (e.g., onItemClick, onClickOutside) for dropdown menus and suggestion popovers, allowing easier event management.
    • New TypeScript path alias (~) for simpler imports in the codebase.
  • Refactor

    • Streamlined outside click handling for dropdowns, popovers, and action groups by leveraging ignore options and removing manual containment checks.
    • Deprecated legacy event handler objects in favor of direct callback props in multiple components and type definitions.
  • Bug Fixes

    • Enhanced detection of outside clicks for more reliable menu and popover behavior.
  • Documentation

    • Updated demos to reflect new event handling patterns and improved logging for popover events.

- Added `onItemClick`, `onClickOutside`, `onOpen`, and `onClose` event props to `SuggestionPopover` and `DropdownMenu` for improved flexibility.
- Updated `SuggestionPill` actions to utilize new event props, replacing deprecated event handling.
- Refactored `PillButtonWrapper` to support new event props for better integration with suggestion actions.
- Improved handling of click events and visibility toggling in `SuggestionPills` and related components.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 17, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This update refactors event handling across suggestion pills, dropdown menus, and popover components. It introduces explicit event handler props in type definitions, deprecates legacy event objects, and centralizes outside click logic using ignore options. Demo files and internal logic are updated to utilize the new event handler approach, improving consistency and control.

Changes

File(s) Change Summary
packages/components/src/suggestion-pills/index.type.ts, packages/components/src/dropdown-menu/index.type.ts, packages/components/src/suggestion-popover/index.type.ts Refactored event handler types: introduced explicit event handler props interfaces, marked legacy events properties as deprecated, and updated union types for pill actions.
packages/components/src/suggestion-pills/index.vue Overhauled overflow logic for pills: now calculates full width with gaps, updates hidden index logic, and manages visible/floating items based on container width. Introduced new reactive refs and helper functions for accurate layout handling.
docs/demos/suggestion/pills-popper-config.vue Updated to use new SuggestionPillMenuAction type, replaced events with direct callback props, and added manual popover/menu visibility control in event handlers. Improved outside click detection using composedPath().
docs/demos/suggestion/popover-trigger.vue Enhanced event logging: added listeners for open, close, item-click, and click-outside events. Simplified outside click handling.
packages/components/src/suggestion-pills/components/PillButtonWrapper.vue Added support for open and click-outside event handlers on popover and menu actions, binding them conditionally from new event props.
packages/components/src/suggestion-popover/index.vue Centralized open/close event emission logic via a helper function. Refactored outside click handling to use ignore options and unified event emission.
packages/components/src/dropdown-menu/index.vue, packages/components/src/flow-layout-buttons/index.vue, packages/components/src/action-group/ActionGroup.vue Refactored outside click handling: removed manual containment checks, now using ignore options in the handler utility to exclude trigger elements, and directly closing dropdowns/menus.
packages/components/tsconfig.json, packages/components/vite.config.ts Added path alias ~ for the src directory in both TypeScript and Vite configurations, enabling shorthand imports.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PillsComponent
    participant Popover
    participant DropdownMenu

    User->>PillsComponent: Click pill with menu/popover
    PillsComponent->>Popover: (if type is popover) Show, bind onOpen/onClose/onClickOutside
    PillsComponent->>DropdownMenu: (if type is menu) Show, bind onItemClick/onClickOutside
    Popover-->>PillsComponent: onOpen/onClose/onClickOutside callbacks
    DropdownMenu-->>PillsComponent: onItemClick/onClickOutside callbacks
    PillsComponent->>PillsComponent: Update menu visibility, log events
Loading

Possibly related PRs

  • opentiny/tiny-robot#59: Introduces the SuggestionPopover component and event handler props, directly related to this PR's migration from event objects to explicit handler props for suggestion-related components.

Poem

A rabbit hopped through fields of code,
Tidying events where handlers flowed.
With props for clicks and menus bright,
Overflow logic set just right.
Now pills and popovers play in tune—
Refactored neat, beneath the moon!
🐇✨

✨ Finishing Touches
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gene9831 gene9831 marked this pull request as ready for review June 17, 2025 12:34
@gene9831
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 18, 2025

✅ Actions performed

Review triggered.

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (10)
packages/components/vite.config.ts (1)

53-56: Keep alias key consistent with TS paths pattern

tsconfig.json uses the pattern ~/*, whereas Vite is configured with the bare key "~".
The current setup works at runtime, but TypeScript assumes the slash after the tilde (~/foo). To avoid subtle mismatches (e.g. teammates writing import '~foo' which TS will not resolve), align them:

-    alias: {
-      '~': fileURLToPath(new URL('./src', import.meta.url)),
-    },
+    alias: {
+      '~': fileURLToPath(new URL('./src', import.meta.url)),      // for imports like '~/xyz'
+      '~/': fileURLToPath(new URL('./src', import.meta.url)),     // optional: exact match for '~/'
+    },

Alternatively, change the TS pattern to "~": ["src"] if you prefer omitting the slash altogether.

packages/components/src/suggestion-popover/index.type.ts (1)

65-86: Deprecation JSDoc is good – surface it to tooling

You added @deprecated tags, but TypeScript will only warn if the symbol itself is marked /** @deprecated */ export interface ....
Consider duplicating the tag on the interface declaration (not just inside each property) so IDEs flag usage immediately.

packages/components/src/flow-layout-buttons/index.vue (1)

116-122: Redundant ignore target & potential hover edge-case

onClickOutside() already treats the first argument (dropDownRef) as an ignored element.
Passing it again inside ignore is unnecessary.

Additionally, when showMoreTrigger === 'hover', users might move the mouse from moreButton into the dropdown without clicking.
Because ignore only contains moreButtonRef, a mousedown inside the button followed by a mouseup inside the dropdown will close it immediately.
Consider adding moreButtonRef and dropDownRef to ignore only when trigger = 'hover'.

-{ ignore: [dropDownRef, moreButtonRef] },
+{ ignore: [moreButtonRef] },

or compute the array conditionally.

packages/components/src/action-group/ActionGroup.vue (1)

67-73: Good simplification – one small nit

Nice move to onClickOutside with ignore.
Like the previous file, dropDownRef is already excluded implicitly, so ignore: [moreBtnRef] is sufficient.

Not critical, but trimming it avoids confusion.

packages/components/src/dropdown-menu/index.type.ts (2)

30-33: Interface name leaks implementation details

DropdownMenuEventProps duplicates emit names but adds the “on” prefix.
Consider renaming to simply DropdownMenuEvents and dropping the deprecated map sooner; keeping two near-identical interfaces increases surface area without gain.


35-44: Deprecation comments need a removal plan

DropdownMenuEvents is marked @deprecated but still exported. Add a TODO with a targeted removal version to avoid indefinite coexistence.

packages/components/src/suggestion-pills/index.type.ts (1)

20-29: Consider making slots symmetrical

PopoverAction offers a slots field whereas MenuAction does not. If custom slots might be useful for menu actions later, adding the field now keeps the interfaces parallel and avoids a breaking change later.

packages/components/src/suggestion-popover/index.vue (2)

150-168: emitClickTriggerEvents emits reversed semantics

When you close via outside-click you first set show.value = false and then call emitClickTriggerEvents().
Inside the helper you emit 'open' when show.value is truthy, 'close' otherwise.
This is correct, but less obvious than emitting explicitly. Consider renaming to emitVisibilityEvent and adding a short comment to avoid mis-reading the logic.


176-190: Duplicate close emission paths

handleClose emits 'close' directly, whereas handleToggleShow / handleItemClick route through emitClickTriggerEvents().
For consistency (and for potential centralisation of side-effects) call the helper here as well.

-  show.value = false
-  emit('close')
+  show.value = false
+  emitClickTriggerEvents()
docs/demos/suggestion/pills-popper-config.vue (1)

128-129: Guard against showAllRef being undefined

event.composedPath().includes(showAllRef.value?.$el) is safe, but it might be clearer (and faster) to exit early when the ref is unset.

-if (event.composedPath().includes(showAllRef.value?.$el)) {
+const switchEl = showAllRef.value?.$el
+if (switchEl && event.composedPath().includes(switchEl)) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc900fd and eff0a80.

📒 Files selected for processing (13)
  • docs/demos/suggestion/pills-popper-config.vue (3 hunks)
  • docs/demos/suggestion/popover-trigger.vue (2 hunks)
  • packages/components/src/action-group/ActionGroup.vue (1 hunks)
  • packages/components/src/dropdown-menu/index.type.ts (1 hunks)
  • packages/components/src/dropdown-menu/index.vue (1 hunks)
  • packages/components/src/flow-layout-buttons/index.vue (1 hunks)
  • packages/components/src/suggestion-pills/components/PillButtonWrapper.vue (2 hunks)
  • packages/components/src/suggestion-pills/index.type.ts (1 hunks)
  • packages/components/src/suggestion-pills/index.vue (2 hunks)
  • packages/components/src/suggestion-popover/index.type.ts (1 hunks)
  • packages/components/src/suggestion-popover/index.vue (3 hunks)
  • packages/components/tsconfig.json (1 hunks)
  • packages/components/vite.config.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
packages/components/src/dropdown-menu/index.vue (1)
Learnt from: gene9831
PR: opentiny/tiny-robot#59
File: packages/components/src/suggestion-popover/index.vue:0-0
Timestamp: 2025-05-27T03:45:56.392Z
Learning: In Vue components using VueUse's onClickOutside, calling stopPropagation() in the onClickOutside handler can prevent subsequent click event handlers on the same element from being triggered. This means that clicking a trigger element while a popover is open can successfully close the popover without immediately reopening it, even when the onClickOutside only listens to the popover element and not the trigger element.
docs/demos/suggestion/popover-trigger.vue (1)
Learnt from: gene9831
PR: opentiny/tiny-robot#59
File: packages/components/src/suggestion-popover/index.vue:131-133
Timestamp: 2025-05-27T03:35:11.008Z
Learning: In the SuggestionPopover component (packages/components/src/suggestion-popover/index.vue), the click handler can be bound unconditionally because the `show` computed property has a custom setter that prevents state mutations when `props.trigger === 'manual'`. This design centralizes trigger mode logic in the computed property rather than requiring conditional checks in event handlers.
packages/components/src/suggestion-popover/index.vue (2)
Learnt from: gene9831
PR: opentiny/tiny-robot#59
File: packages/components/src/suggestion-popover/index.vue:131-133
Timestamp: 2025-05-27T03:35:11.008Z
Learning: In the SuggestionPopover component (packages/components/src/suggestion-popover/index.vue), the click handler can be bound unconditionally because the `show` computed property has a custom setter that prevents state mutations when `props.trigger === 'manual'`. This design centralizes trigger mode logic in the computed property rather than requiring conditional checks in event handlers.
Learnt from: gene9831
PR: opentiny/tiny-robot#59
File: packages/components/src/suggestion-popover/index.vue:0-0
Timestamp: 2025-05-27T03:45:56.392Z
Learning: In Vue components using VueUse's onClickOutside, calling stopPropagation() in the onClickOutside handler can prevent subsequent click event handlers on the same element from being triggered. This means that clicking a trigger element while a popover is open can successfully close the popover without immediately reopening it, even when the onClickOutside only listens to the popover element and not the trigger element.
🔇 Additional comments (5)
docs/demos/suggestion/popover-trigger.vue (1)

3-10: Demo looks good – showcases the new events clearly

The extra console logs are helpful for users exploring the API.

packages/components/src/dropdown-menu/index.vue (1)

51-58: ```shell
#!/bin/bash

Show props definitions and comments in dropdown-menu index.type.ts

sed -n '1,200p' packages/components/src/dropdown-menu/index.type.ts


</details>
<details>
<summary>packages/components/src/suggestion-pills/index.type.ts (1)</summary>

`10-18`: **Good split between popover and menu actions**

Separating `SuggestionPillPopoverAction` and `SuggestionPillMenuAction` makes the union easier to narrow and improves IntelliSense.  

LGTM.

</details>
<details>
<summary>packages/components/src/suggestion-pills/index.vue (2)</summary>

`16-17`: **Good use of defineModel for v-model support!**

The implementation correctly uses Vue 3.3+ defineModel macro for cleaner two-way binding.

---

`33-38`: **Fix inverted logic in floatingItems computed property.**

The floating items should be displayed when overflow exists and `showAll` is true.

Apply this diff to fix the logic:

```diff
 const floatingItems = computed(() => {
-  if (!hasShowMoreBtn.value || !showAll.value) {
+  if (!hasShowMoreBtn.value || showAll.value) {
     return []
   }
   return props.items?.slice(hiddenIndex.value) || []
 })

Likely an incorrect or invalid review comment.

Comment thread packages/components/tsconfig.json
Comment thread packages/components/src/suggestion-popover/index.type.ts
Comment thread docs/demos/suggestion/pills-popper-config.vue
Comment thread packages/components/src/suggestion-pills/index.vue Outdated
Comment thread packages/components/src/suggestion-pills/index.vue
Comment thread packages/components/src/suggestion-pills/index.vue
Comment thread packages/components/src/suggestion-pills/index.vue
Comment thread packages/components/src/suggestion-pills/index.vue
gene9831 added 3 commits June 19, 2025 09:43
…components

- Changed item IDs in `pills-popper-config.vue` to start from 2 for better uniqueness.
- Refactored visibility logic in `index.vue` to simplify conditions for showing static and floating items based on button visibility and state.
- Removed unnecessary gap calculation function and replaced it with a fixed gap value for consistency.
…ity handling

- Introduced a button in `pills-popper-config.vue` to allow users to dynamically add items to the suggestion list.
- Implemented a `closeAllPopper` function to streamline the visibility management of suggestion items.
- Enhanced click handling to ensure proper visibility toggling when interacting with the new button.
- Updated gap calculation in `index.vue` to dynamically retrieve the row gap from the container's computed style instead of using a fixed value.
- Added a check to ensure the container reference is valid before performing width calculations on item elements.
@gene9831 gene9831 merged commit 02739ac into opentiny:release/v0.2.x Jun 23, 2025
@gene9831 gene9831 deleted the fix/on-click-outside branch June 23, 2025 06:56
gene9831 added a commit to gene9831/tiny-robot that referenced this pull request Jul 3, 2025
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