shadcn-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.dockerignore +40 -0
- data/CHANGELOG.md +54 -0
- data/CLAUDE.md +463 -0
- data/PROGRESS.md +485 -0
- data/README.md +1483 -0
- data/Rakefile +29 -0
- data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +13 -0
- data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +46 -0
- data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +111 -0
- data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +27 -0
- data/__tests__/controllers/accordion_controller.test.js +904 -0
- data/__tests__/controllers/calendar_controller.test.js +1370 -0
- data/__tests__/controllers/carousel_controller.test.js +912 -0
- data/__tests__/controllers/checkbox_controller.test.js +454 -0
- data/__tests__/controllers/collapsible_controller.test.js +407 -0
- data/__tests__/controllers/combobox_controller.test.js +966 -0
- data/__tests__/controllers/context_menu_controller.test.js +627 -0
- data/__tests__/controllers/date_picker_controller.test.js +636 -0
- data/__tests__/controllers/dialog_controller.test.js +878 -0
- data/__tests__/controllers/drawer_controller.test.js +995 -0
- data/__tests__/controllers/menubar_controller.test.js +736 -0
- data/__tests__/controllers/navigation_menu_controller.test.js +598 -0
- data/__tests__/controllers/popover_controller.test.js +1007 -0
- data/__tests__/controllers/radio_group_controller.test.js +640 -0
- data/__tests__/controllers/resizable_controller.test.js +680 -0
- data/__tests__/controllers/select_controller.test.js +674 -0
- data/__tests__/controllers/sheet_controller.test.js +986 -0
- data/__tests__/controllers/slider_controller.test.js +1036 -0
- data/__tests__/controllers/switch_controller.test.js +424 -0
- data/__tests__/controllers/tabs_controller.test.js +907 -0
- data/__tests__/controllers/toggle_group_controller.test.js +839 -0
- data/__tests__/controllers/tooltip_controller.test.js +808 -0
- data/__tests__/helpers/stimulus-test-helper.js +203 -0
- data/app/assets/config/manifest.js +1 -0
- data/app/assets/javascripts/shadcn/controllers/accordion_controller.d.ts +53 -0
- data/app/assets/javascripts/shadcn/controllers/accordion_controller.js +140 -0
- data/app/assets/javascripts/shadcn/controllers/avatar_controller.d.ts +22 -0
- data/app/assets/javascripts/shadcn/controllers/avatar_controller.js +26 -0
- data/app/assets/javascripts/shadcn/controllers/calendar_controller.js +592 -0
- data/app/assets/javascripts/shadcn/controllers/carousel_controller.js +263 -0
- data/app/assets/javascripts/shadcn/controllers/checkbox_controller.d.ts +31 -0
- data/app/assets/javascripts/shadcn/controllers/checkbox_controller.js +48 -0
- data/app/assets/javascripts/shadcn/controllers/collapsible_controller.d.ts +43 -0
- data/app/assets/javascripts/shadcn/controllers/collapsible_controller.js +73 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +234 -0
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +141 -0
- data/app/assets/javascripts/shadcn/controllers/command_dialog_controller.js +162 -0
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +202 -0
- data/app/assets/javascripts/shadcn/controllers/date_picker_controller.js +282 -0
- data/app/assets/javascripts/shadcn/controllers/dialog_controller.d.ts +67 -0
- data/app/assets/javascripts/shadcn/controllers/dialog_controller.js +187 -0
- data/app/assets/javascripts/shadcn/controllers/drawer_controller.d.ts +58 -0
- data/app/assets/javascripts/shadcn/controllers/drawer_controller.js +112 -0
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.d.ts +83 -0
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +225 -0
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.d.ts +59 -0
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +143 -0
- data/app/assets/javascripts/shadcn/controllers/input_otp_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/input_otp_controller.js +206 -0
- data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +323 -0
- data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +251 -0
- data/app/assets/javascripts/shadcn/controllers/popover_controller.d.ts +56 -0
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +141 -0
- data/app/assets/javascripts/shadcn/controllers/radio_group_controller.d.ts +47 -0
- data/app/assets/javascripts/shadcn/controllers/radio_group_controller.js +108 -0
- data/app/assets/javascripts/shadcn/controllers/resizable_controller.js +272 -0
- data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.js +74 -0
- data/app/assets/javascripts/shadcn/controllers/select_controller.d.ts +84 -0
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +222 -0
- data/app/assets/javascripts/shadcn/controllers/sheet_controller.d.ts +60 -0
- data/app/assets/javascripts/shadcn/controllers/sheet_controller.js +151 -0
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +148 -0
- data/app/assets/javascripts/shadcn/controllers/slider_controller.d.ts +102 -0
- data/app/assets/javascripts/shadcn/controllers/slider_controller.js +364 -0
- data/app/assets/javascripts/shadcn/controllers/switch_controller.d.ts +46 -0
- data/app/assets/javascripts/shadcn/controllers/switch_controller.js +78 -0
- data/app/assets/javascripts/shadcn/controllers/tabs_controller.d.ts +51 -0
- data/app/assets/javascripts/shadcn/controllers/tabs_controller.js +126 -0
- data/app/assets/javascripts/shadcn/controllers/toast_controller.d.ts +37 -0
- data/app/assets/javascripts/shadcn/controllers/toast_controller.js +58 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_controller.d.ts +27 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_controller.js +42 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.d.ts +44 -0
- data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.js +68 -0
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.d.ts +56 -0
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +117 -0
- data/app/assets/javascripts/shadcn/index.d.ts +74 -0
- data/app/assets/javascripts/shadcn/index.js +133 -0
- data/app/assets/stylesheets/.keep +0 -0
- data/app/assets/stylesheets/shadcn/base.css +445 -0
- data/app/assets/stylesheets/shadcn/components.css +513 -0
- data/app/assets/stylesheets/shadcn/index.css +18 -0
- data/app/assets/stylesheets/shadcn/themes/gray.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/slate.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/stone.css +68 -0
- data/app/assets/stylesheets/shadcn/themes/zinc.css +68 -0
- data/app/components/shadcn/accordion_component.rb +63 -0
- data/app/components/shadcn/accordion_content_component.rb +29 -0
- data/app/components/shadcn/accordion_item_component.rb +40 -0
- data/app/components/shadcn/accordion_trigger_component.rb +49 -0
- data/app/components/shadcn/alert_component.rb +75 -0
- data/app/components/shadcn/alert_description_component.rb +12 -0
- data/app/components/shadcn/alert_dialog_action_component.rb +24 -0
- data/app/components/shadcn/alert_dialog_cancel_component.rb +24 -0
- data/app/components/shadcn/alert_dialog_component.rb +71 -0
- data/app/components/shadcn/alert_dialog_content_component.rb +57 -0
- data/app/components/shadcn/alert_dialog_description_component.rb +12 -0
- data/app/components/shadcn/alert_dialog_footer_component.rb +19 -0
- data/app/components/shadcn/alert_dialog_header_component.rb +19 -0
- data/app/components/shadcn/alert_dialog_title_component.rb +12 -0
- data/app/components/shadcn/alert_title_component.rb +12 -0
- data/app/components/shadcn/aspect_ratio_component.rb +49 -0
- data/app/components/shadcn/avatar_component.rb +107 -0
- data/app/components/shadcn/avatar_fallback_component.rb +17 -0
- data/app/components/shadcn/badge_component.rb +49 -0
- data/app/components/shadcn/base_component.rb +100 -0
- data/app/components/shadcn/breadcrumb_component.rb +70 -0
- data/app/components/shadcn/breadcrumb_item_component.rb +50 -0
- data/app/components/shadcn/button_component.rb +141 -0
- data/app/components/shadcn/button_group_component.rb +69 -0
- data/app/components/shadcn/calendar_component.rb +337 -0
- data/app/components/shadcn/card_action_component.rb +10 -0
- data/app/components/shadcn/card_component.rb +63 -0
- data/app/components/shadcn/card_content_component.rb +19 -0
- data/app/components/shadcn/card_description_component.rb +12 -0
- data/app/components/shadcn/card_footer_component.rb +12 -0
- data/app/components/shadcn/card_header_component.rb +24 -0
- data/app/components/shadcn/card_title_component.rb +18 -0
- data/app/components/shadcn/carousel_component.rb +275 -0
- data/app/components/shadcn/checkbox_component.rb +103 -0
- data/app/components/shadcn/collapsible_component.rb +66 -0
- data/app/components/shadcn/collapsible_content_component.rb +28 -0
- data/app/components/shadcn/combobox_component.rb +322 -0
- data/app/components/shadcn/command_component.rb +52 -0
- data/app/components/shadcn/command_dialog_component.rb +76 -0
- data/app/components/shadcn/command_empty_component.rb +12 -0
- data/app/components/shadcn/command_group_component.rb +34 -0
- data/app/components/shadcn/command_input_component.rb +59 -0
- data/app/components/shadcn/command_item_component.rb +48 -0
- data/app/components/shadcn/command_list_component.rb +38 -0
- data/app/components/shadcn/command_separator_component.rb +12 -0
- data/app/components/shadcn/command_shortcut_component.rb +12 -0
- data/app/components/shadcn/context_menu_component.rb +64 -0
- data/app/components/shadcn/context_menu_content_component.rb +44 -0
- data/app/components/shadcn/context_menu_item_component.rb +63 -0
- data/app/components/shadcn/context_menu_label_component.rb +18 -0
- data/app/components/shadcn/context_menu_separator_component.rb +12 -0
- data/app/components/shadcn/context_menu_shortcut_component.rb +12 -0
- data/app/components/shadcn/date_picker_component.rb +368 -0
- data/app/components/shadcn/dialog_component.rb +77 -0
- data/app/components/shadcn/dialog_content_component.rb +91 -0
- data/app/components/shadcn/dialog_description_component.rb +12 -0
- data/app/components/shadcn/dialog_footer_component.rb +12 -0
- data/app/components/shadcn/dialog_header_component.rb +19 -0
- data/app/components/shadcn/dialog_title_component.rb +12 -0
- data/app/components/shadcn/drawer_component.rb +72 -0
- data/app/components/shadcn/drawer_content_component.rb +76 -0
- data/app/components/shadcn/drawer_description_component.rb +12 -0
- data/app/components/shadcn/drawer_footer_component.rb +12 -0
- data/app/components/shadcn/drawer_header_component.rb +19 -0
- data/app/components/shadcn/drawer_title_component.rb +12 -0
- data/app/components/shadcn/dropdown_menu_component.rb +75 -0
- data/app/components/shadcn/dropdown_menu_content_component.rb +49 -0
- data/app/components/shadcn/dropdown_menu_group_component.rb +10 -0
- data/app/components/shadcn/dropdown_menu_item_component.rb +63 -0
- data/app/components/shadcn/dropdown_menu_label_component.rb +18 -0
- data/app/components/shadcn/dropdown_menu_separator_component.rb +12 -0
- data/app/components/shadcn/dropdown_menu_shortcut_component.rb +12 -0
- data/app/components/shadcn/empty_component.rb +48 -0
- data/app/components/shadcn/empty_content_component.rb +12 -0
- data/app/components/shadcn/empty_description_component.rb +12 -0
- data/app/components/shadcn/empty_header_component.rb +29 -0
- data/app/components/shadcn/empty_media_component.rb +21 -0
- data/app/components/shadcn/empty_title_component.rb +12 -0
- data/app/components/shadcn/field_component.rb +113 -0
- data/app/components/shadcn/hover_card_component.rb +64 -0
- data/app/components/shadcn/hover_card_content_component.rb +36 -0
- data/app/components/shadcn/input_component.rb +108 -0
- data/app/components/shadcn/input_group_component.rb +70 -0
- data/app/components/shadcn/input_otp_component.rb +183 -0
- data/app/components/shadcn/item_actions_component.rb +12 -0
- data/app/components/shadcn/item_component.rb +98 -0
- data/app/components/shadcn/item_content_component.rb +24 -0
- data/app/components/shadcn/item_description_component.rb +12 -0
- data/app/components/shadcn/item_footer_component.rb +12 -0
- data/app/components/shadcn/item_group_component.rb +24 -0
- data/app/components/shadcn/item_header_component.rb +12 -0
- data/app/components/shadcn/item_media_component.rb +22 -0
- data/app/components/shadcn/item_separator_component.rb +12 -0
- data/app/components/shadcn/item_title_component.rb +12 -0
- data/app/components/shadcn/kbd_component.rb +36 -0
- data/app/components/shadcn/label_component.rb +49 -0
- data/app/components/shadcn/menubar_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/menubar_component.rb +56 -0
- data/app/components/shadcn/menubar_content_component.rb +64 -0
- data/app/components/shadcn/menubar_item_component.rb +65 -0
- data/app/components/shadcn/menubar_label_component.rb +27 -0
- data/app/components/shadcn/menubar_menu_component.rb +34 -0
- data/app/components/shadcn/menubar_radio_group_component.rb +42 -0
- data/app/components/shadcn/menubar_radio_item_component.rb +76 -0
- data/app/components/shadcn/menubar_separator_component.rb +22 -0
- data/app/components/shadcn/menubar_shortcut_component.rb +21 -0
- data/app/components/shadcn/menubar_sub_component.rb +38 -0
- data/app/components/shadcn/menubar_sub_content_component.rb +45 -0
- data/app/components/shadcn/menubar_sub_trigger_component.rb +59 -0
- data/app/components/shadcn/menubar_trigger_component.rb +31 -0
- data/app/components/shadcn/native_select_component.rb +150 -0
- data/app/components/shadcn/navigation_menu_component.rb +76 -0
- data/app/components/shadcn/navigation_menu_content_component.rb +30 -0
- data/app/components/shadcn/navigation_menu_item_component.rb +39 -0
- data/app/components/shadcn/navigation_menu_link_component.rb +38 -0
- data/app/components/shadcn/navigation_menu_list_component.rb +29 -0
- data/app/components/shadcn/navigation_menu_trigger_component.rb +59 -0
- data/app/components/shadcn/pagination_component.rb +195 -0
- data/app/components/shadcn/pagination_content_component.rb +47 -0
- data/app/components/shadcn/pagination_ellipsis_component.rb +30 -0
- data/app/components/shadcn/pagination_item_component.rb +53 -0
- data/app/components/shadcn/pagination_next_component.rb +48 -0
- data/app/components/shadcn/pagination_previous_component.rb +48 -0
- data/app/components/shadcn/popover_component.rb +76 -0
- data/app/components/shadcn/popover_content_component.rb +25 -0
- data/app/components/shadcn/progress_component.rb +77 -0
- data/app/components/shadcn/radio_group_component.rb +129 -0
- data/app/components/shadcn/radio_group_item_component.rb +109 -0
- data/app/components/shadcn/resizable_handle_component.rb +98 -0
- data/app/components/shadcn/resizable_panel_component.rb +56 -0
- data/app/components/shadcn/resizable_panel_group_component.rb +94 -0
- data/app/components/shadcn/scroll_area_component.rb +110 -0
- data/app/components/shadcn/select_component.rb +151 -0
- data/app/components/shadcn/select_group_component.rb +32 -0
- data/app/components/shadcn/select_item_component.rb +59 -0
- data/app/components/shadcn/select_separator_component.rb +12 -0
- data/app/components/shadcn/separator_component.rb +54 -0
- data/app/components/shadcn/sheet_component.rb +82 -0
- data/app/components/shadcn/sheet_content_component.rb +95 -0
- data/app/components/shadcn/sheet_description_component.rb +12 -0
- data/app/components/shadcn/sheet_footer_component.rb +12 -0
- data/app/components/shadcn/sheet_header_component.rb +19 -0
- data/app/components/shadcn/sheet_title_component.rb +12 -0
- data/app/components/shadcn/sidebar_component.rb +180 -0
- data/app/components/shadcn/sidebar_content_component.rb +32 -0
- data/app/components/shadcn/sidebar_footer_component.rb +24 -0
- data/app/components/shadcn/sidebar_group_action_component.rb +26 -0
- data/app/components/shadcn/sidebar_group_component.rb +38 -0
- data/app/components/shadcn/sidebar_group_content_component.rb +32 -0
- data/app/components/shadcn/sidebar_group_label_component.rb +25 -0
- data/app/components/shadcn/sidebar_header_component.rb +24 -0
- data/app/components/shadcn/sidebar_inset_component.rb +25 -0
- data/app/components/shadcn/sidebar_menu_action_component.rb +37 -0
- data/app/components/shadcn/sidebar_menu_badge_component.rb +25 -0
- data/app/components/shadcn/sidebar_menu_button_component.rb +52 -0
- data/app/components/shadcn/sidebar_menu_component.rb +32 -0
- data/app/components/shadcn/sidebar_menu_item_component.rb +41 -0
- data/app/components/shadcn/sidebar_menu_skeleton_component.rb +46 -0
- data/app/components/shadcn/sidebar_menu_sub_button_component.rb +43 -0
- data/app/components/shadcn/sidebar_menu_sub_component.rb +33 -0
- data/app/components/shadcn/sidebar_menu_sub_item_component.rb +30 -0
- data/app/components/shadcn/sidebar_provider_component.rb +57 -0
- data/app/components/shadcn/sidebar_rail_component.rb +30 -0
- data/app/components/shadcn/sidebar_separator_component.rb +24 -0
- data/app/components/shadcn/sidebar_trigger_component.rb +51 -0
- data/app/components/shadcn/skeleton_component.rb +29 -0
- data/app/components/shadcn/slider_component.rb +76 -0
- data/app/components/shadcn/spinner_component.rb +67 -0
- data/app/components/shadcn/switch_component.rb +147 -0
- data/app/components/shadcn/table_body_component.rb +16 -0
- data/app/components/shadcn/table_caption_component.rb +12 -0
- data/app/components/shadcn/table_cell_component.rb +12 -0
- data/app/components/shadcn/table_component.rb +57 -0
- data/app/components/shadcn/table_footer_component.rb +16 -0
- data/app/components/shadcn/table_head_component.rb +12 -0
- data/app/components/shadcn/table_header_component.rb +16 -0
- data/app/components/shadcn/table_row_component.rb +40 -0
- data/app/components/shadcn/tabs_component.rb +78 -0
- data/app/components/shadcn/tabs_content_component.rb +32 -0
- data/app/components/shadcn/tabs_list_component.rb +30 -0
- data/app/components/shadcn/tabs_trigger_component.rb +37 -0
- data/app/components/shadcn/textarea_component.rb +84 -0
- data/app/components/shadcn/toast_action_component.rb +18 -0
- data/app/components/shadcn/toast_component.rb +114 -0
- data/app/components/shadcn/toast_description_component.rb +12 -0
- data/app/components/shadcn/toast_title_component.rb +12 -0
- data/app/components/shadcn/toast_viewport_component.rb +12 -0
- data/app/components/shadcn/toggle_component.rb +77 -0
- data/app/components/shadcn/toggle_group_component.rb +96 -0
- data/app/components/shadcn/toggle_group_item_component.rb +62 -0
- data/app/components/shadcn/tooltip_component.rb +89 -0
- data/app/components/shadcn/typography_component.rb +112 -0
- data/babel.config.cjs +5 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/config/importmap.rb +5 -0
- data/fly.toml +26 -0
- data/jest.config.js +19 -0
- data/jest.setup.js +8 -0
- data/lib/generators/shadcn/component/component_generator.rb +188 -0
- data/lib/generators/shadcn/install/install_generator.rb +140 -0
- data/lib/generators/shadcn/install/templates/initializer.rb.tt +35 -0
- data/lib/generators/shadcn/install/templates/shadcn.yml.tt +35 -0
- data/lib/generators/shadcn/theme/theme_generator.rb +128 -0
- data/lib/shadcn/rails/class_merger.rb +228 -0
- data/lib/shadcn/rails/configuration.rb +341 -0
- data/lib/shadcn/rails/engine.rb +59 -0
- data/lib/shadcn/rails/helpers/class_name_helper.rb +35 -0
- data/lib/shadcn/rails/helpers/component_helper.rb +60 -0
- data/lib/shadcn/rails/helpers/pagination_helper.rb +187 -0
- data/lib/shadcn/rails/version.rb +7 -0
- data/lib/shadcn/rails.rb +179 -0
- data/package-lock.json +7415 -0
- data/package.json +68 -0
- data/rollup.config.js +29 -0
- data/sig/shadcn/rails.rbs +6 -0
- metadata +526 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
|
2
|
+
import TooltipController from "../../app/assets/javascripts/shadcn/controllers/tooltip_controller.js"
|
|
3
|
+
import { wait, nextFrame, keydown } from '../helpers/stimulus-test-helper.js'
|
|
4
|
+
|
|
5
|
+
describe("TooltipController", () => {
|
|
6
|
+
let application
|
|
7
|
+
let element
|
|
8
|
+
let controller
|
|
9
|
+
|
|
10
|
+
const createTooltipHTML = (side = "top", align = "center", delay = 200, skipDelay = 300) => {
|
|
11
|
+
return `
|
|
12
|
+
<div data-controller="shadcn--tooltip"
|
|
13
|
+
data-shadcn--tooltip-side-value="${side}"
|
|
14
|
+
data-shadcn--tooltip-align-value="${align}"
|
|
15
|
+
data-shadcn--tooltip-delay-value="${delay}"
|
|
16
|
+
data-shadcn--tooltip-skip-delay-value="${skipDelay}">
|
|
17
|
+
<button data-shadcn--tooltip-target="trigger"
|
|
18
|
+
data-action="mouseenter->shadcn--tooltip#show mouseleave->shadcn--tooltip#hide focus->shadcn--tooltip#show blur->shadcn--tooltip#hide">
|
|
19
|
+
Hover me
|
|
20
|
+
</button>
|
|
21
|
+
<div data-shadcn--tooltip-target="content"
|
|
22
|
+
hidden
|
|
23
|
+
role="tooltip"
|
|
24
|
+
style="position: relative;">
|
|
25
|
+
Tooltip content
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
application = Application.start()
|
|
33
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
34
|
+
document.body.innerHTML = createTooltipHTML()
|
|
35
|
+
|
|
36
|
+
await nextFrame()
|
|
37
|
+
|
|
38
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
39
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
if (application) {
|
|
44
|
+
application.stop()
|
|
45
|
+
}
|
|
46
|
+
document.body.innerHTML = ""
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe("value initialization", () => {
|
|
50
|
+
test("initializes with default side value of 'top'", () => {
|
|
51
|
+
expect(controller.sideValue).toBe("top")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("initializes with default align value of 'center'", () => {
|
|
55
|
+
expect(controller.alignValue).toBe("center")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("initializes with default delay value of 200", () => {
|
|
59
|
+
expect(controller.delayValue).toBe(200)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("initializes with default skipDelay value of 300", () => {
|
|
63
|
+
expect(controller.skipDelayValue).toBe(300)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("accepts custom side value", async () => {
|
|
67
|
+
application.stop()
|
|
68
|
+
document.body.innerHTML = createTooltipHTML("bottom")
|
|
69
|
+
|
|
70
|
+
application = Application.start()
|
|
71
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
72
|
+
await nextFrame()
|
|
73
|
+
|
|
74
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
75
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
76
|
+
|
|
77
|
+
expect(controller.sideValue).toBe("bottom")
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("accepts custom align value", async () => {
|
|
81
|
+
application.stop()
|
|
82
|
+
document.body.innerHTML = createTooltipHTML("top", "start")
|
|
83
|
+
|
|
84
|
+
application = Application.start()
|
|
85
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
86
|
+
await nextFrame()
|
|
87
|
+
|
|
88
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
89
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
90
|
+
|
|
91
|
+
expect(controller.alignValue).toBe("start")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("accepts custom delay value", async () => {
|
|
95
|
+
application.stop()
|
|
96
|
+
document.body.innerHTML = createTooltipHTML("top", "center", 500)
|
|
97
|
+
|
|
98
|
+
application = Application.start()
|
|
99
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
100
|
+
await nextFrame()
|
|
101
|
+
|
|
102
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
103
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
104
|
+
|
|
105
|
+
expect(controller.delayValue).toBe(500)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("accepts custom skipDelay value", async () => {
|
|
109
|
+
application.stop()
|
|
110
|
+
document.body.innerHTML = createTooltipHTML("top", "center", 200, 1000)
|
|
111
|
+
|
|
112
|
+
application = Application.start()
|
|
113
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
114
|
+
await nextFrame()
|
|
115
|
+
|
|
116
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
117
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
118
|
+
|
|
119
|
+
expect(controller.skipDelayValue).toBe(1000)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
describe("connect and disconnect", () => {
|
|
124
|
+
test("initializes timeouts to null on connect", () => {
|
|
125
|
+
expect(controller.showTimeout).toBeNull()
|
|
126
|
+
expect(controller.hideTimeout).toBeNull()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("clears timeouts on disconnect", async () => {
|
|
130
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
131
|
+
|
|
132
|
+
// Start showing tooltip
|
|
133
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
134
|
+
|
|
135
|
+
expect(controller.showTimeout).not.toBeNull()
|
|
136
|
+
|
|
137
|
+
// Disconnect controller
|
|
138
|
+
controller.disconnect()
|
|
139
|
+
|
|
140
|
+
expect(controller.showTimeout).toBeNull()
|
|
141
|
+
expect(controller.hideTimeout).toBeNull()
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe("show and hide on hover", () => {
|
|
146
|
+
test("shows tooltip on mouseenter", async () => {
|
|
147
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
148
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
149
|
+
|
|
150
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
151
|
+
|
|
152
|
+
// Wait for delay
|
|
153
|
+
await wait(250)
|
|
154
|
+
|
|
155
|
+
expect(content.hidden).toBe(false)
|
|
156
|
+
expect(content.dataset.state).toBe("open")
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test("hides tooltip on mouseleave", async () => {
|
|
160
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
161
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
162
|
+
|
|
163
|
+
// Show tooltip
|
|
164
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
165
|
+
await wait(250)
|
|
166
|
+
|
|
167
|
+
expect(content.hidden).toBe(false)
|
|
168
|
+
|
|
169
|
+
// Hide tooltip
|
|
170
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
171
|
+
await wait(150)
|
|
172
|
+
|
|
173
|
+
expect(content.dataset.state).toBe("closed")
|
|
174
|
+
expect(content.hidden).toBe(true)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test("does not show tooltip if mouseleave before delay", async () => {
|
|
178
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
179
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
180
|
+
|
|
181
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
182
|
+
|
|
183
|
+
// Leave before delay completes
|
|
184
|
+
await wait(100)
|
|
185
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
186
|
+
|
|
187
|
+
// Wait past original delay
|
|
188
|
+
await wait(200)
|
|
189
|
+
|
|
190
|
+
expect(content.hidden).toBe(true)
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
describe("delay behavior", () => {
|
|
195
|
+
test("waits for delay before showing tooltip", async () => {
|
|
196
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
197
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
198
|
+
|
|
199
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
200
|
+
|
|
201
|
+
// Tooltip should not be visible before delay
|
|
202
|
+
await wait(100)
|
|
203
|
+
expect(content.hidden).toBe(true)
|
|
204
|
+
|
|
205
|
+
// Should be visible after delay
|
|
206
|
+
await wait(150)
|
|
207
|
+
expect(content.hidden).toBe(false)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test("respects custom delay value", async () => {
|
|
211
|
+
application.stop()
|
|
212
|
+
document.body.innerHTML = createTooltipHTML("top", "center", 500)
|
|
213
|
+
|
|
214
|
+
application = Application.start()
|
|
215
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
216
|
+
await nextFrame()
|
|
217
|
+
|
|
218
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
219
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
220
|
+
|
|
221
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
222
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
223
|
+
|
|
224
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
225
|
+
|
|
226
|
+
// Should not show before custom delay
|
|
227
|
+
await wait(400)
|
|
228
|
+
expect(content.hidden).toBe(true)
|
|
229
|
+
|
|
230
|
+
// Should show after custom delay
|
|
231
|
+
await wait(150)
|
|
232
|
+
expect(content.hidden).toBe(false)
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe("positioning - side", () => {
|
|
237
|
+
test("positions tooltip on top by default", async () => {
|
|
238
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
239
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
240
|
+
|
|
241
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
242
|
+
await wait(250)
|
|
243
|
+
|
|
244
|
+
expect(content.style.bottom).toBe("100%")
|
|
245
|
+
expect(content.style.marginBottom).toBe("8px")
|
|
246
|
+
expect(content.dataset.side).toBe("top")
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test("positions tooltip on bottom", async () => {
|
|
250
|
+
application.stop()
|
|
251
|
+
document.body.innerHTML = createTooltipHTML("bottom")
|
|
252
|
+
|
|
253
|
+
application = Application.start()
|
|
254
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
255
|
+
await nextFrame()
|
|
256
|
+
|
|
257
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
258
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
259
|
+
|
|
260
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
261
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
262
|
+
|
|
263
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
264
|
+
await wait(250)
|
|
265
|
+
|
|
266
|
+
expect(content.style.top).toBe("100%")
|
|
267
|
+
expect(content.style.marginTop).toBe("8px")
|
|
268
|
+
expect(content.dataset.side).toBe("bottom")
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test("positions tooltip on left", async () => {
|
|
272
|
+
application.stop()
|
|
273
|
+
document.body.innerHTML = createTooltipHTML("left")
|
|
274
|
+
|
|
275
|
+
application = Application.start()
|
|
276
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
277
|
+
await nextFrame()
|
|
278
|
+
|
|
279
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
280
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
281
|
+
|
|
282
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
283
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
284
|
+
|
|
285
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
286
|
+
await wait(250)
|
|
287
|
+
|
|
288
|
+
expect(content.style.right).toBe("100%")
|
|
289
|
+
expect(content.style.marginRight).toBe("8px")
|
|
290
|
+
expect(content.style.top).toBe("50%")
|
|
291
|
+
expect(content.style.transform).toBe("translateY(-50%)")
|
|
292
|
+
expect(content.dataset.side).toBe("left")
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test("positions tooltip on right", async () => {
|
|
296
|
+
application.stop()
|
|
297
|
+
document.body.innerHTML = createTooltipHTML("right")
|
|
298
|
+
|
|
299
|
+
application = Application.start()
|
|
300
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
301
|
+
await nextFrame()
|
|
302
|
+
|
|
303
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
304
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
305
|
+
|
|
306
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
307
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
308
|
+
|
|
309
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
310
|
+
await wait(250)
|
|
311
|
+
|
|
312
|
+
expect(content.style.left).toBe("100%")
|
|
313
|
+
expect(content.style.marginLeft).toBe("8px")
|
|
314
|
+
expect(content.style.top).toBe("50%")
|
|
315
|
+
expect(content.style.transform).toBe("translateY(-50%)")
|
|
316
|
+
expect(content.dataset.side).toBe("right")
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
describe("positioning - align", () => {
|
|
321
|
+
test("centers tooltip by default on top/bottom", async () => {
|
|
322
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
323
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
324
|
+
|
|
325
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
326
|
+
await wait(250)
|
|
327
|
+
|
|
328
|
+
expect(content.style.left).toBe("50%")
|
|
329
|
+
expect(content.style.transform).toBe("translateX(-50%)")
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
test("aligns tooltip to start on top", async () => {
|
|
333
|
+
application.stop()
|
|
334
|
+
document.body.innerHTML = createTooltipHTML("top", "start")
|
|
335
|
+
|
|
336
|
+
application = Application.start()
|
|
337
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
338
|
+
await nextFrame()
|
|
339
|
+
|
|
340
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
341
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
342
|
+
|
|
343
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
344
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
345
|
+
|
|
346
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
347
|
+
await wait(250)
|
|
348
|
+
|
|
349
|
+
expect(content.style.left).toBe("0px")
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test("aligns tooltip to end on top", async () => {
|
|
353
|
+
application.stop()
|
|
354
|
+
document.body.innerHTML = createTooltipHTML("top", "end")
|
|
355
|
+
|
|
356
|
+
application = Application.start()
|
|
357
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
358
|
+
await nextFrame()
|
|
359
|
+
|
|
360
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
361
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
362
|
+
|
|
363
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
364
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
365
|
+
|
|
366
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
367
|
+
await wait(250)
|
|
368
|
+
|
|
369
|
+
expect(content.style.right).toBe("0px")
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test("centers tooltip on bottom", async () => {
|
|
373
|
+
application.stop()
|
|
374
|
+
document.body.innerHTML = createTooltipHTML("bottom", "center")
|
|
375
|
+
|
|
376
|
+
application = Application.start()
|
|
377
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
378
|
+
await nextFrame()
|
|
379
|
+
|
|
380
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
381
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
382
|
+
|
|
383
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
384
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
385
|
+
|
|
386
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
387
|
+
await wait(250)
|
|
388
|
+
|
|
389
|
+
expect(content.style.left).toBe("50%")
|
|
390
|
+
expect(content.style.transform).toBe("translateX(-50%)")
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test("does not apply horizontal alignment on left/right sides", async () => {
|
|
394
|
+
application.stop()
|
|
395
|
+
document.body.innerHTML = createTooltipHTML("left", "start")
|
|
396
|
+
|
|
397
|
+
application = Application.start()
|
|
398
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
399
|
+
await nextFrame()
|
|
400
|
+
|
|
401
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
402
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
403
|
+
|
|
404
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
405
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
406
|
+
|
|
407
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
408
|
+
await wait(250)
|
|
409
|
+
|
|
410
|
+
// Left/right sides only set vertical positioning
|
|
411
|
+
expect(content.style.top).toBe("50%")
|
|
412
|
+
expect(content.style.transform).toBe("translateY(-50%)")
|
|
413
|
+
// Should not have left/right alignment
|
|
414
|
+
expect(content.style.left).toBe("")
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
describe("timeout cleanup", () => {
|
|
419
|
+
test("clears show timeout when hide is called", async () => {
|
|
420
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
421
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
422
|
+
|
|
423
|
+
// Start showing
|
|
424
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
425
|
+
expect(controller.showTimeout).not.toBeNull()
|
|
426
|
+
|
|
427
|
+
// Hide before show completes
|
|
428
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
429
|
+
expect(controller.showTimeout).toBeNull()
|
|
430
|
+
|
|
431
|
+
// Advance past original show delay
|
|
432
|
+
await wait(300)
|
|
433
|
+
|
|
434
|
+
// Tooltip should not be shown
|
|
435
|
+
expect(content.hidden).toBe(true)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
test("clears hide timeout when show is called", async () => {
|
|
439
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
440
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
441
|
+
|
|
442
|
+
// Show tooltip
|
|
443
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
444
|
+
await wait(250)
|
|
445
|
+
|
|
446
|
+
// Start hiding
|
|
447
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
448
|
+
expect(controller.hideTimeout).not.toBeNull()
|
|
449
|
+
|
|
450
|
+
// Show again before hide completes
|
|
451
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
452
|
+
expect(controller.hideTimeout).toBeNull()
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
test("no memory leaks when quickly hovering in and out", async () => {
|
|
456
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
457
|
+
|
|
458
|
+
// Rapidly hover in/out
|
|
459
|
+
for (let i = 0; i < 10; i++) {
|
|
460
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
461
|
+
await wait(50)
|
|
462
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
463
|
+
await wait(50)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Only one timeout should be active at most
|
|
467
|
+
const hasTimeout = controller.showTimeout !== null || controller.hideTimeout !== null
|
|
468
|
+
expect(hasTimeout).toBe(true)
|
|
469
|
+
|
|
470
|
+
// Clean up
|
|
471
|
+
await wait(300)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
test("clears all timeouts in clearTimeouts method", () => {
|
|
475
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
476
|
+
|
|
477
|
+
// Create a show timeout
|
|
478
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
479
|
+
expect(controller.showTimeout).not.toBeNull()
|
|
480
|
+
|
|
481
|
+
// Call clearTimeouts
|
|
482
|
+
controller.clearTimeouts()
|
|
483
|
+
|
|
484
|
+
expect(controller.showTimeout).toBeNull()
|
|
485
|
+
expect(controller.hideTimeout).toBeNull()
|
|
486
|
+
})
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
describe("keyboard accessibility", () => {
|
|
490
|
+
test("shows tooltip on focus", async () => {
|
|
491
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
492
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
493
|
+
|
|
494
|
+
trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
|
|
495
|
+
|
|
496
|
+
await wait(250)
|
|
497
|
+
|
|
498
|
+
expect(content.hidden).toBe(false)
|
|
499
|
+
expect(content.dataset.state).toBe("open")
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
test("hides tooltip on blur", async () => {
|
|
503
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
504
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
505
|
+
|
|
506
|
+
// Show tooltip
|
|
507
|
+
trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
|
|
508
|
+
await wait(250)
|
|
509
|
+
|
|
510
|
+
expect(content.hidden).toBe(false)
|
|
511
|
+
|
|
512
|
+
// Hide tooltip
|
|
513
|
+
trigger.dispatchEvent(new FocusEvent('blur', { bubbles: true }))
|
|
514
|
+
await wait(150)
|
|
515
|
+
|
|
516
|
+
expect(content.dataset.state).toBe("closed")
|
|
517
|
+
expect(content.hidden).toBe(true)
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
test("shows tooltip with keyboard navigation", async () => {
|
|
521
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
522
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
523
|
+
|
|
524
|
+
// Tab to focus the button
|
|
525
|
+
trigger.focus()
|
|
526
|
+
trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
|
|
527
|
+
|
|
528
|
+
await wait(250)
|
|
529
|
+
|
|
530
|
+
expect(content.hidden).toBe(false)
|
|
531
|
+
})
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
describe("ARIA attributes", () => {
|
|
535
|
+
test("tooltip has role='tooltip'", () => {
|
|
536
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
537
|
+
expect(content.getAttribute('role')).toBe('tooltip')
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
test("content is hidden initially", () => {
|
|
541
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
542
|
+
expect(content.hidden).toBe(true)
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
test("content data-state changes to open when shown", async () => {
|
|
546
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
547
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
548
|
+
|
|
549
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
550
|
+
await wait(250)
|
|
551
|
+
|
|
552
|
+
expect(content.dataset.state).toBe("open")
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
test("content data-state changes to closed when hidden", async () => {
|
|
556
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
557
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
558
|
+
|
|
559
|
+
// Show
|
|
560
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
561
|
+
await wait(250)
|
|
562
|
+
|
|
563
|
+
// Hide
|
|
564
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
565
|
+
await wait(50)
|
|
566
|
+
|
|
567
|
+
expect(content.dataset.state).toBe("closed")
|
|
568
|
+
})
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
describe("positioning edge cases", () => {
|
|
572
|
+
test("handles missing trigger target gracefully", async () => {
|
|
573
|
+
// Remove trigger
|
|
574
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
575
|
+
trigger.remove()
|
|
576
|
+
|
|
577
|
+
expect(() => {
|
|
578
|
+
controller.show()
|
|
579
|
+
}).not.toThrow()
|
|
580
|
+
|
|
581
|
+
await wait(250)
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
test("handles missing content target gracefully", async () => {
|
|
585
|
+
// Remove content
|
|
586
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
587
|
+
content.remove()
|
|
588
|
+
|
|
589
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
590
|
+
|
|
591
|
+
expect(() => {
|
|
592
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
593
|
+
}).not.toThrow()
|
|
594
|
+
|
|
595
|
+
await wait(250)
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
test("resets positioning styles before positioning", async () => {
|
|
599
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
600
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
601
|
+
|
|
602
|
+
// Show tooltip
|
|
603
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
604
|
+
await wait(250)
|
|
605
|
+
|
|
606
|
+
// Verify position is absolute
|
|
607
|
+
expect(content.style.position).toBe("absolute")
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
test("sets data-side attribute on content", async () => {
|
|
611
|
+
application.stop()
|
|
612
|
+
document.body.innerHTML = createTooltipHTML("bottom")
|
|
613
|
+
|
|
614
|
+
application = Application.start()
|
|
615
|
+
application.register("shadcn--tooltip", TooltipController)
|
|
616
|
+
await nextFrame()
|
|
617
|
+
|
|
618
|
+
element = document.querySelector('[data-controller="shadcn--tooltip"]')
|
|
619
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
|
|
620
|
+
|
|
621
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
622
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
623
|
+
|
|
624
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
625
|
+
await wait(250)
|
|
626
|
+
|
|
627
|
+
expect(content.dataset.side).toBe("bottom")
|
|
628
|
+
})
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
describe("hide animation timing", () => {
|
|
632
|
+
test("sets data-state to closed immediately", async () => {
|
|
633
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
634
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
635
|
+
|
|
636
|
+
// Show tooltip
|
|
637
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
638
|
+
await wait(250)
|
|
639
|
+
|
|
640
|
+
// Hide tooltip
|
|
641
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
642
|
+
await wait(10) // hideTimeout is 0ms but let event loop process
|
|
643
|
+
|
|
644
|
+
expect(content.dataset.state).toBe("closed")
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
test("sets hidden attribute after 100ms delay", async () => {
|
|
648
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
649
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
650
|
+
|
|
651
|
+
// Show tooltip
|
|
652
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
653
|
+
await wait(250)
|
|
654
|
+
|
|
655
|
+
expect(content.hidden).toBe(false)
|
|
656
|
+
|
|
657
|
+
// Hide tooltip
|
|
658
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
659
|
+
await wait(10)
|
|
660
|
+
|
|
661
|
+
// Should not be hidden yet
|
|
662
|
+
expect(content.hidden).toBe(false)
|
|
663
|
+
|
|
664
|
+
// After 100ms animation delay
|
|
665
|
+
await wait(100)
|
|
666
|
+
|
|
667
|
+
expect(content.hidden).toBe(true)
|
|
668
|
+
})
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
describe("integration scenarios", () => {
|
|
672
|
+
test("can show and hide tooltip multiple times", async () => {
|
|
673
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
674
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
675
|
+
|
|
676
|
+
// Show
|
|
677
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
678
|
+
await wait(250)
|
|
679
|
+
expect(content.hidden).toBe(false)
|
|
680
|
+
|
|
681
|
+
// Hide
|
|
682
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
683
|
+
await wait(150)
|
|
684
|
+
expect(content.hidden).toBe(true)
|
|
685
|
+
|
|
686
|
+
// Show again
|
|
687
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
688
|
+
await wait(250)
|
|
689
|
+
expect(content.hidden).toBe(false)
|
|
690
|
+
|
|
691
|
+
// Hide again
|
|
692
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
693
|
+
await wait(150)
|
|
694
|
+
expect(content.hidden).toBe(true)
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
test("switches between hover and focus correctly", async () => {
|
|
698
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
699
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
700
|
+
|
|
701
|
+
// Show on hover
|
|
702
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
703
|
+
await wait(250)
|
|
704
|
+
expect(content.hidden).toBe(false)
|
|
705
|
+
|
|
706
|
+
// Hide on leave
|
|
707
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
708
|
+
await wait(150)
|
|
709
|
+
expect(content.hidden).toBe(true)
|
|
710
|
+
|
|
711
|
+
// Show on focus
|
|
712
|
+
trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
|
|
713
|
+
await wait(250)
|
|
714
|
+
expect(content.hidden).toBe(false)
|
|
715
|
+
|
|
716
|
+
// Hide on blur
|
|
717
|
+
trigger.dispatchEvent(new FocusEvent('blur', { bubbles: true }))
|
|
718
|
+
await wait(150)
|
|
719
|
+
expect(content.hidden).toBe(true)
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
test("repositions tooltip on each show", async () => {
|
|
723
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
724
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
725
|
+
|
|
726
|
+
// Show tooltip
|
|
727
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
728
|
+
await wait(250)
|
|
729
|
+
|
|
730
|
+
const firstPosition = {
|
|
731
|
+
bottom: content.style.bottom,
|
|
732
|
+
left: content.style.left,
|
|
733
|
+
transform: content.style.transform
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Hide
|
|
737
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
738
|
+
await wait(150)
|
|
739
|
+
|
|
740
|
+
// Show again
|
|
741
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
742
|
+
await wait(250)
|
|
743
|
+
|
|
744
|
+
// Position should be recalculated (same values in this case)
|
|
745
|
+
expect(content.style.bottom).toBe(firstPosition.bottom)
|
|
746
|
+
expect(content.style.left).toBe(firstPosition.left)
|
|
747
|
+
expect(content.style.transform).toBe(firstPosition.transform)
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
test("handles rapid hover in and out gracefully", async () => {
|
|
751
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
752
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
753
|
+
|
|
754
|
+
// Rapid hover events
|
|
755
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
756
|
+
await wait(50)
|
|
757
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
758
|
+
await wait(20)
|
|
759
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
760
|
+
await wait(50)
|
|
761
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
762
|
+
await wait(20)
|
|
763
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
764
|
+
|
|
765
|
+
// Complete the final show
|
|
766
|
+
await wait(250)
|
|
767
|
+
|
|
768
|
+
expect(content.hidden).toBe(false)
|
|
769
|
+
expect(content.dataset.state).toBe("open")
|
|
770
|
+
})
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
describe("cleanup on disconnect", () => {
|
|
774
|
+
test("prevents show after disconnect", async () => {
|
|
775
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
776
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
777
|
+
|
|
778
|
+
// Start showing
|
|
779
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
780
|
+
|
|
781
|
+
// Disconnect before delay completes
|
|
782
|
+
controller.disconnect()
|
|
783
|
+
|
|
784
|
+
// Advance past delay
|
|
785
|
+
await wait(300)
|
|
786
|
+
|
|
787
|
+
// Should not show because timeouts were cleared
|
|
788
|
+
expect(content.hidden).toBe(true)
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
test("clears pending hide timeout on disconnect", async () => {
|
|
792
|
+
const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
|
|
793
|
+
const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
|
|
794
|
+
|
|
795
|
+
// Show tooltip
|
|
796
|
+
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
|
|
797
|
+
await wait(250)
|
|
798
|
+
|
|
799
|
+
// Start hiding
|
|
800
|
+
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
|
|
801
|
+
|
|
802
|
+
// Disconnect before hide completes
|
|
803
|
+
controller.disconnect()
|
|
804
|
+
|
|
805
|
+
expect(controller.hideTimeout).toBeNull()
|
|
806
|
+
})
|
|
807
|
+
})
|
|
808
|
+
})
|