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,904 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
|
2
|
+
import AccordionController from "../../app/assets/javascripts/shadcn/controllers/accordion_controller.js"
|
|
3
|
+
import { click, wait, nextFrame, keydown, waitForEvent } from '../helpers/stimulus-test-helper.js'
|
|
4
|
+
|
|
5
|
+
describe("AccordionController", () => {
|
|
6
|
+
let application
|
|
7
|
+
let element
|
|
8
|
+
let controller
|
|
9
|
+
|
|
10
|
+
const createAccordionHTML = (type = "single", collapsible = false, defaultValue = "") => {
|
|
11
|
+
const collapsibleAttr = collapsible ? `data-shadcn--accordion-collapsible-value="true"` : ''
|
|
12
|
+
const defaultAttr = defaultValue ? `data-shadcn--accordion-default-value="${defaultValue}"` : ''
|
|
13
|
+
|
|
14
|
+
return `
|
|
15
|
+
<div data-controller="shadcn--accordion"
|
|
16
|
+
data-shadcn--accordion-type-value="${type}"
|
|
17
|
+
${collapsibleAttr}
|
|
18
|
+
${defaultAttr}>
|
|
19
|
+
<div data-shadcn--accordion-target="item" data-value="item-1" data-state="closed">
|
|
20
|
+
<button data-shadcn--accordion-target="trigger"
|
|
21
|
+
data-action="click->shadcn--accordion#toggle keydown->shadcn--accordion#handleKeydown"
|
|
22
|
+
aria-expanded="false">
|
|
23
|
+
Trigger 1
|
|
24
|
+
</button>
|
|
25
|
+
<div data-shadcn--accordion-target="content" hidden>Content 1</div>
|
|
26
|
+
</div>
|
|
27
|
+
<div data-shadcn--accordion-target="item" data-value="item-2" data-state="closed">
|
|
28
|
+
<button data-shadcn--accordion-target="trigger"
|
|
29
|
+
data-action="click->shadcn--accordion#toggle keydown->shadcn--accordion#handleKeydown"
|
|
30
|
+
aria-expanded="false">
|
|
31
|
+
Trigger 2
|
|
32
|
+
</button>
|
|
33
|
+
<div data-shadcn--accordion-target="content" hidden>Content 2</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div data-shadcn--accordion-target="item" data-value="item-3" data-state="closed">
|
|
36
|
+
<button data-shadcn--accordion-target="trigger"
|
|
37
|
+
data-action="click->shadcn--accordion#toggle keydown->shadcn--accordion#handleKeydown"
|
|
38
|
+
aria-expanded="false">
|
|
39
|
+
Trigger 3
|
|
40
|
+
</button>
|
|
41
|
+
<div data-shadcn--accordion-target="content" hidden>Content 3</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
beforeEach(async () => {
|
|
48
|
+
application = Application.start()
|
|
49
|
+
application.register("shadcn--accordion", AccordionController)
|
|
50
|
+
document.body.innerHTML = createAccordionHTML()
|
|
51
|
+
|
|
52
|
+
await nextFrame()
|
|
53
|
+
|
|
54
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
55
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
if (application) {
|
|
60
|
+
application.stop()
|
|
61
|
+
}
|
|
62
|
+
document.body.innerHTML = ""
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe("value initialization", () => {
|
|
66
|
+
test("initializes with default type value of 'single'", () => {
|
|
67
|
+
expect(controller.typeValue).toBe("single")
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("initializes with default collapsible value of false", () => {
|
|
71
|
+
expect(controller.collapsibleValue).toBe(false)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("initializes with empty default value", () => {
|
|
75
|
+
expect(controller.defaultValue).toBe("")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test("accepts custom type value", async () => {
|
|
79
|
+
application.stop()
|
|
80
|
+
document.body.innerHTML = createAccordionHTML("multiple")
|
|
81
|
+
|
|
82
|
+
application = Application.start()
|
|
83
|
+
application.register("shadcn--accordion", AccordionController)
|
|
84
|
+
await nextFrame()
|
|
85
|
+
|
|
86
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
87
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
88
|
+
|
|
89
|
+
expect(controller.typeValue).toBe("multiple")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("accepts collapsible value", async () => {
|
|
93
|
+
application.stop()
|
|
94
|
+
document.body.innerHTML = createAccordionHTML("single", true)
|
|
95
|
+
|
|
96
|
+
application = Application.start()
|
|
97
|
+
application.register("shadcn--accordion", AccordionController)
|
|
98
|
+
await nextFrame()
|
|
99
|
+
|
|
100
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
101
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
102
|
+
|
|
103
|
+
expect(controller.collapsibleValue).toBe(true)
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe("default values", () => {
|
|
108
|
+
test("expands single item on connect with default value", async () => {
|
|
109
|
+
application.stop()
|
|
110
|
+
document.body.innerHTML = createAccordionHTML("single", false, "item-2")
|
|
111
|
+
|
|
112
|
+
application = Application.start()
|
|
113
|
+
application.register("shadcn--accordion", AccordionController)
|
|
114
|
+
await nextFrame()
|
|
115
|
+
|
|
116
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
117
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
118
|
+
|
|
119
|
+
const item2 = element.querySelector('[data-value="item-2"]')
|
|
120
|
+
expect(item2.dataset.state).toBe("open")
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("expands multiple items on connect with comma-separated default values", async () => {
|
|
124
|
+
application.stop()
|
|
125
|
+
document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, item-3")
|
|
126
|
+
|
|
127
|
+
application = Application.start()
|
|
128
|
+
application.register("shadcn--accordion", AccordionController)
|
|
129
|
+
await nextFrame()
|
|
130
|
+
|
|
131
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
132
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
133
|
+
|
|
134
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
135
|
+
const item3 = element.querySelector('[data-value="item-3"]')
|
|
136
|
+
|
|
137
|
+
expect(item1.dataset.state).toBe("open")
|
|
138
|
+
expect(item3.dataset.state).toBe("open")
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test("handles whitespace in comma-separated default values", async () => {
|
|
142
|
+
application.stop()
|
|
143
|
+
document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, item-2 , item-3")
|
|
144
|
+
|
|
145
|
+
application = Application.start()
|
|
146
|
+
application.register("shadcn--accordion", AccordionController)
|
|
147
|
+
await nextFrame()
|
|
148
|
+
|
|
149
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
150
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
151
|
+
|
|
152
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
153
|
+
const item2 = element.querySelector('[data-value="item-2"]')
|
|
154
|
+
const item3 = element.querySelector('[data-value="item-3"]')
|
|
155
|
+
|
|
156
|
+
expect(item1.dataset.state).toBe("open")
|
|
157
|
+
expect(item2.dataset.state).toBe("open")
|
|
158
|
+
expect(item3.dataset.state).toBe("open")
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test("ignores invalid default values gracefully", async () => {
|
|
162
|
+
application.stop()
|
|
163
|
+
document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, invalid-item, item-3")
|
|
164
|
+
|
|
165
|
+
application = Application.start()
|
|
166
|
+
application.register("shadcn--accordion", AccordionController)
|
|
167
|
+
await nextFrame()
|
|
168
|
+
|
|
169
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
170
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
171
|
+
|
|
172
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
173
|
+
const item3 = element.querySelector('[data-value="item-3"]')
|
|
174
|
+
|
|
175
|
+
// Valid items should still be expanded
|
|
176
|
+
expect(item1.dataset.state).toBe("open")
|
|
177
|
+
expect(item3.dataset.state).toBe("open")
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe("single mode", () => {
|
|
182
|
+
test("expands an item when clicked", () => {
|
|
183
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
184
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
185
|
+
|
|
186
|
+
click(trigger1)
|
|
187
|
+
|
|
188
|
+
expect(item1.dataset.state).toBe("open")
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test("collapses other items when expanding one", async () => {
|
|
192
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
193
|
+
const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
|
|
194
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
195
|
+
const item2 = element.querySelector('[data-value="item-2"]')
|
|
196
|
+
|
|
197
|
+
// Expand first item
|
|
198
|
+
click(trigger1)
|
|
199
|
+
expect(item1.dataset.state).toBe("open")
|
|
200
|
+
|
|
201
|
+
// Expand second item - should collapse first
|
|
202
|
+
click(trigger2)
|
|
203
|
+
await nextFrame()
|
|
204
|
+
|
|
205
|
+
expect(item2.dataset.state).toBe("open")
|
|
206
|
+
expect(item1.dataset.state).toBe("closed")
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test("does not collapse open item when collapsible is false", () => {
|
|
210
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
211
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
212
|
+
|
|
213
|
+
// Expand item
|
|
214
|
+
click(trigger1)
|
|
215
|
+
expect(item1.dataset.state).toBe("open")
|
|
216
|
+
|
|
217
|
+
// Try to collapse - should remain open
|
|
218
|
+
click(trigger1)
|
|
219
|
+
expect(item1.dataset.state).toBe("open")
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test("collapses open item when collapsible is true", async () => {
|
|
223
|
+
application.stop()
|
|
224
|
+
document.body.innerHTML = createAccordionHTML("single", true)
|
|
225
|
+
|
|
226
|
+
application = Application.start()
|
|
227
|
+
application.register("shadcn--accordion", AccordionController)
|
|
228
|
+
await nextFrame()
|
|
229
|
+
|
|
230
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
231
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
232
|
+
|
|
233
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
234
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
235
|
+
|
|
236
|
+
// Expand item
|
|
237
|
+
click(trigger1)
|
|
238
|
+
expect(item1.dataset.state).toBe("open")
|
|
239
|
+
|
|
240
|
+
// Collapse item
|
|
241
|
+
click(trigger1)
|
|
242
|
+
await nextFrame()
|
|
243
|
+
|
|
244
|
+
expect(item1.dataset.state).toBe("closed")
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test("updates aria-expanded on trigger when expanding", () => {
|
|
248
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
249
|
+
|
|
250
|
+
click(trigger1)
|
|
251
|
+
|
|
252
|
+
expect(trigger1.getAttribute("aria-expanded")).toBe("true")
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test("updates aria-expanded on trigger when collapsing", async () => {
|
|
256
|
+
application.stop()
|
|
257
|
+
document.body.innerHTML = createAccordionHTML("single", true)
|
|
258
|
+
|
|
259
|
+
application = Application.start()
|
|
260
|
+
application.register("shadcn--accordion", AccordionController)
|
|
261
|
+
await nextFrame()
|
|
262
|
+
|
|
263
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
264
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
265
|
+
|
|
266
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
267
|
+
|
|
268
|
+
// Expand then collapse
|
|
269
|
+
click(trigger1)
|
|
270
|
+
click(trigger1)
|
|
271
|
+
await nextFrame()
|
|
272
|
+
|
|
273
|
+
expect(trigger1.getAttribute("aria-expanded")).toBe("false")
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test("sets data-state on content when expanding", () => {
|
|
277
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
278
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
279
|
+
|
|
280
|
+
click(trigger1)
|
|
281
|
+
|
|
282
|
+
expect(content1.dataset.state).toBe("open")
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test("removes hidden attribute from content when expanding", () => {
|
|
286
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
287
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
288
|
+
|
|
289
|
+
click(trigger1)
|
|
290
|
+
|
|
291
|
+
expect(content1.hidden).toBe(false)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
describe("multiple mode", () => {
|
|
296
|
+
beforeEach(async () => {
|
|
297
|
+
application.stop()
|
|
298
|
+
document.body.innerHTML = createAccordionHTML("multiple")
|
|
299
|
+
|
|
300
|
+
application = Application.start()
|
|
301
|
+
application.register("shadcn--accordion", AccordionController)
|
|
302
|
+
await nextFrame()
|
|
303
|
+
|
|
304
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
305
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
test("can expand multiple items simultaneously", () => {
|
|
309
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
310
|
+
const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
|
|
311
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
312
|
+
const item2 = element.querySelector('[data-value="item-2"]')
|
|
313
|
+
|
|
314
|
+
click(trigger1)
|
|
315
|
+
click(trigger2)
|
|
316
|
+
|
|
317
|
+
expect(item1.dataset.state).toBe("open")
|
|
318
|
+
expect(item2.dataset.state).toBe("open")
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test("does not collapse other items when expanding", () => {
|
|
322
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
323
|
+
const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
|
|
324
|
+
const trigger3 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[2]
|
|
325
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
326
|
+
const item2 = element.querySelector('[data-value="item-2"]')
|
|
327
|
+
const item3 = element.querySelector('[data-value="item-3"]')
|
|
328
|
+
|
|
329
|
+
click(trigger1)
|
|
330
|
+
click(trigger2)
|
|
331
|
+
click(trigger3)
|
|
332
|
+
|
|
333
|
+
expect(item1.dataset.state).toBe("open")
|
|
334
|
+
expect(item2.dataset.state).toBe("open")
|
|
335
|
+
expect(item3.dataset.state).toBe("open")
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test("can collapse individual items", async () => {
|
|
339
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
340
|
+
const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
|
|
341
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
342
|
+
const item2 = element.querySelector('[data-value="item-2"]')
|
|
343
|
+
|
|
344
|
+
// Expand both
|
|
345
|
+
click(trigger1)
|
|
346
|
+
click(trigger2)
|
|
347
|
+
|
|
348
|
+
// Collapse first
|
|
349
|
+
click(trigger1)
|
|
350
|
+
await nextFrame()
|
|
351
|
+
|
|
352
|
+
expect(item1.dataset.state).toBe("closed")
|
|
353
|
+
expect(item2.dataset.state).toBe("open")
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
test("can expand all items then collapse all", async () => {
|
|
357
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
358
|
+
const items = element.querySelectorAll('[data-shadcn--accordion-target="item"]')
|
|
359
|
+
|
|
360
|
+
// Expand all
|
|
361
|
+
triggers.forEach(trigger => click(trigger))
|
|
362
|
+
items.forEach(item => expect(item.dataset.state).toBe("open"))
|
|
363
|
+
|
|
364
|
+
// Collapse all
|
|
365
|
+
triggers.forEach(trigger => click(trigger))
|
|
366
|
+
await nextFrame()
|
|
367
|
+
|
|
368
|
+
items.forEach(item => expect(item.dataset.state).toBe("closed"))
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
describe("type safety", () => {
|
|
373
|
+
test("findItemByValue returns correct item", () => {
|
|
374
|
+
const item2 = controller.findItemByValue("item-2")
|
|
375
|
+
expect(item2.dataset.value).toBe("item-2")
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test("findItemByValue returns undefined for non-existent item", () => {
|
|
379
|
+
const item = controller.findItemByValue("non-existent")
|
|
380
|
+
expect(item).toBeUndefined()
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
test("handles missing item gracefully in expandItem", () => {
|
|
384
|
+
// Attempt to expand non-existent item should not throw
|
|
385
|
+
expect(() => {
|
|
386
|
+
const fakeItem = document.createElement('div')
|
|
387
|
+
controller.expandItem(fakeItem)
|
|
388
|
+
}).not.toThrow()
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
test("handles missing trigger gracefully", () => {
|
|
392
|
+
const item = document.createElement('div')
|
|
393
|
+
item.setAttribute('data-shadcn--accordion-target', 'item')
|
|
394
|
+
|
|
395
|
+
expect(() => {
|
|
396
|
+
controller.expandItem(item)
|
|
397
|
+
}).not.toThrow()
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
test("handles missing content gracefully", () => {
|
|
401
|
+
const item = document.createElement('div')
|
|
402
|
+
item.setAttribute('data-shadcn--accordion-target', 'item')
|
|
403
|
+
|
|
404
|
+
const trigger = document.createElement('button')
|
|
405
|
+
trigger.setAttribute('data-shadcn--accordion-target', 'trigger')
|
|
406
|
+
item.appendChild(trigger)
|
|
407
|
+
|
|
408
|
+
expect(() => {
|
|
409
|
+
controller.expandItem(item)
|
|
410
|
+
}).not.toThrow()
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
describe("keyboard navigation", () => {
|
|
415
|
+
beforeEach(() => {
|
|
416
|
+
// Focus first trigger
|
|
417
|
+
const firstTrigger = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
418
|
+
firstTrigger.focus()
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
test("ArrowDown moves focus to next trigger", () => {
|
|
422
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
423
|
+
triggers[0].focus()
|
|
424
|
+
|
|
425
|
+
keydown(triggers[0], 'ArrowDown')
|
|
426
|
+
|
|
427
|
+
expect(document.activeElement).toBe(triggers[1])
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
test("ArrowUp moves focus to previous trigger", () => {
|
|
431
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
432
|
+
triggers[1].focus()
|
|
433
|
+
|
|
434
|
+
keydown(triggers[1], 'ArrowUp')
|
|
435
|
+
|
|
436
|
+
expect(document.activeElement).toBe(triggers[0])
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test("ArrowDown wraps to first trigger from last", () => {
|
|
440
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
441
|
+
triggers[2].focus()
|
|
442
|
+
|
|
443
|
+
keydown(triggers[2], 'ArrowDown')
|
|
444
|
+
|
|
445
|
+
expect(document.activeElement).toBe(triggers[0])
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
test("ArrowUp wraps to last trigger from first", () => {
|
|
449
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
450
|
+
triggers[0].focus()
|
|
451
|
+
|
|
452
|
+
keydown(triggers[0], 'ArrowUp')
|
|
453
|
+
|
|
454
|
+
expect(document.activeElement).toBe(triggers[2])
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
test("Home moves focus to first trigger", () => {
|
|
458
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
459
|
+
triggers[2].focus()
|
|
460
|
+
|
|
461
|
+
keydown(triggers[2], 'Home')
|
|
462
|
+
|
|
463
|
+
expect(document.activeElement).toBe(triggers[0])
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
test("End moves focus to last trigger", () => {
|
|
467
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
468
|
+
triggers[0].focus()
|
|
469
|
+
|
|
470
|
+
keydown(triggers[0], 'End')
|
|
471
|
+
|
|
472
|
+
expect(document.activeElement).toBe(triggers[2])
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
test("other keys do not move focus", () => {
|
|
476
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
477
|
+
triggers[0].focus()
|
|
478
|
+
|
|
479
|
+
keydown(triggers[0], 'Tab')
|
|
480
|
+
expect(document.activeElement).toBe(triggers[0])
|
|
481
|
+
|
|
482
|
+
keydown(triggers[0], 'Enter')
|
|
483
|
+
expect(document.activeElement).toBe(triggers[0])
|
|
484
|
+
|
|
485
|
+
keydown(triggers[0], 'Escape')
|
|
486
|
+
expect(document.activeElement).toBe(triggers[0])
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
test("ArrowDown prevents default behavior", () => {
|
|
490
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
491
|
+
triggers[0].focus()
|
|
492
|
+
|
|
493
|
+
let defaultPrevented = false
|
|
494
|
+
const event = new KeyboardEvent('keydown', {
|
|
495
|
+
key: 'ArrowDown',
|
|
496
|
+
bubbles: true,
|
|
497
|
+
cancelable: true
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
// Override preventDefault to track if it was called
|
|
501
|
+
event.preventDefault = () => { defaultPrevented = true }
|
|
502
|
+
triggers[0].dispatchEvent(event)
|
|
503
|
+
|
|
504
|
+
expect(defaultPrevented).toBe(true)
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
test("ArrowUp prevents default behavior", () => {
|
|
508
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
509
|
+
triggers[0].focus()
|
|
510
|
+
|
|
511
|
+
let defaultPrevented = false
|
|
512
|
+
const event = new KeyboardEvent('keydown', {
|
|
513
|
+
key: 'ArrowUp',
|
|
514
|
+
bubbles: true,
|
|
515
|
+
cancelable: true
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// Override preventDefault to track if it was called
|
|
519
|
+
event.preventDefault = () => { defaultPrevented = true }
|
|
520
|
+
triggers[0].dispatchEvent(event)
|
|
521
|
+
|
|
522
|
+
expect(defaultPrevented).toBe(true)
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
test("Home prevents default behavior", () => {
|
|
526
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
527
|
+
triggers[0].focus()
|
|
528
|
+
|
|
529
|
+
let defaultPrevented = false
|
|
530
|
+
const event = new KeyboardEvent('keydown', {
|
|
531
|
+
key: 'Home',
|
|
532
|
+
bubbles: true,
|
|
533
|
+
cancelable: true
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
// Override preventDefault to track if it was called
|
|
537
|
+
event.preventDefault = () => { defaultPrevented = true }
|
|
538
|
+
triggers[0].dispatchEvent(event)
|
|
539
|
+
|
|
540
|
+
expect(defaultPrevented).toBe(true)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test("End prevents default behavior", () => {
|
|
544
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
545
|
+
triggers[0].focus()
|
|
546
|
+
|
|
547
|
+
let defaultPrevented = false
|
|
548
|
+
const event = new KeyboardEvent('keydown', {
|
|
549
|
+
key: 'End',
|
|
550
|
+
bubbles: true,
|
|
551
|
+
cancelable: true
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
// Override preventDefault to track if it was called
|
|
555
|
+
event.preventDefault = () => { defaultPrevented = true }
|
|
556
|
+
triggers[0].dispatchEvent(event)
|
|
557
|
+
|
|
558
|
+
expect(defaultPrevented).toBe(true)
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
test("does nothing when no trigger is focused", () => {
|
|
562
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
563
|
+
|
|
564
|
+
// Create a separate element to focus that's not a trigger
|
|
565
|
+
const outsideElement = document.createElement('button')
|
|
566
|
+
document.body.appendChild(outsideElement)
|
|
567
|
+
outsideElement.focus()
|
|
568
|
+
|
|
569
|
+
const initialActiveElement = document.activeElement
|
|
570
|
+
|
|
571
|
+
// Dispatch keydown on a trigger but it's not focused
|
|
572
|
+
keydown(triggers[0], 'ArrowDown')
|
|
573
|
+
|
|
574
|
+
// Active element should remain the outside element
|
|
575
|
+
expect(document.activeElement).toBe(initialActiveElement)
|
|
576
|
+
expect(document.activeElement).not.toBe(triggers[1])
|
|
577
|
+
|
|
578
|
+
// Cleanup
|
|
579
|
+
document.body.removeChild(outsideElement)
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
describe("event dispatch", () => {
|
|
584
|
+
test("dispatches expand event when item is expanded", async () => {
|
|
585
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
586
|
+
|
|
587
|
+
const eventPromise = waitForEvent(element, 'shadcn--accordion:expand')
|
|
588
|
+
|
|
589
|
+
click(trigger1)
|
|
590
|
+
|
|
591
|
+
const event = await eventPromise
|
|
592
|
+
expect(event.detail.value).toBe("item-1")
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
test("dispatches collapse event when item is collapsed", async () => {
|
|
596
|
+
application.stop()
|
|
597
|
+
document.body.innerHTML = createAccordionHTML("single", true)
|
|
598
|
+
|
|
599
|
+
application = Application.start()
|
|
600
|
+
application.register("shadcn--accordion", AccordionController)
|
|
601
|
+
await nextFrame()
|
|
602
|
+
|
|
603
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
604
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
605
|
+
|
|
606
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
607
|
+
|
|
608
|
+
// Expand first
|
|
609
|
+
click(trigger1)
|
|
610
|
+
|
|
611
|
+
const eventPromise = waitForEvent(element, 'shadcn--accordion:collapse')
|
|
612
|
+
|
|
613
|
+
// Then collapse
|
|
614
|
+
click(trigger1)
|
|
615
|
+
|
|
616
|
+
const event = await eventPromise
|
|
617
|
+
expect(event.detail.value).toBe("item-1")
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
test("dispatches collapse event when another item is expanded in single mode", async () => {
|
|
621
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
622
|
+
const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
|
|
623
|
+
|
|
624
|
+
// Expand first item
|
|
625
|
+
click(trigger1)
|
|
626
|
+
|
|
627
|
+
const eventPromise = waitForEvent(element, 'shadcn--accordion:collapse')
|
|
628
|
+
|
|
629
|
+
// Expand second item (should collapse first)
|
|
630
|
+
click(trigger2)
|
|
631
|
+
|
|
632
|
+
const event = await eventPromise
|
|
633
|
+
expect(event.detail.value).toBe("item-1")
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
test("dispatches events with correct value detail", async () => {
|
|
637
|
+
const trigger3 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[2]
|
|
638
|
+
|
|
639
|
+
const eventPromise = waitForEvent(element, 'shadcn--accordion:expand')
|
|
640
|
+
|
|
641
|
+
click(trigger3)
|
|
642
|
+
|
|
643
|
+
const event = await eventPromise
|
|
644
|
+
expect(event.detail.value).toBe("item-3")
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
test("expand event is dispatched before collapse event in single mode", async () => {
|
|
648
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
649
|
+
const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
|
|
650
|
+
|
|
651
|
+
const events = []
|
|
652
|
+
|
|
653
|
+
element.addEventListener('shadcn--accordion:expand', (e) => {
|
|
654
|
+
events.push({ type: 'expand', value: e.detail.value })
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
element.addEventListener('shadcn--accordion:collapse', (e) => {
|
|
658
|
+
events.push({ type: 'collapse', value: e.detail.value })
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
// Expand first item
|
|
662
|
+
click(trigger1)
|
|
663
|
+
await nextFrame()
|
|
664
|
+
|
|
665
|
+
// Expand second item
|
|
666
|
+
click(trigger2)
|
|
667
|
+
await nextFrame()
|
|
668
|
+
|
|
669
|
+
// Based on the implementation, collapse happens first, then expand
|
|
670
|
+
// This is because collapseItem is called before expandItem in toggle()
|
|
671
|
+
expect(events).toEqual([
|
|
672
|
+
{ type: 'expand', value: 'item-1' },
|
|
673
|
+
{ type: 'collapse', value: 'item-1' },
|
|
674
|
+
{ type: 'expand', value: 'item-2' }
|
|
675
|
+
])
|
|
676
|
+
})
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
describe("animation", () => {
|
|
680
|
+
test("sets height on content during expand animation", () => {
|
|
681
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
682
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
683
|
+
|
|
684
|
+
click(trigger1)
|
|
685
|
+
|
|
686
|
+
// After requestAnimationFrame, height should be set
|
|
687
|
+
expect(content1.style.height).toBeTruthy()
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
test("sets height to 0px initially during collapse", async () => {
|
|
691
|
+
application.stop()
|
|
692
|
+
document.body.innerHTML = createAccordionHTML("single", true)
|
|
693
|
+
|
|
694
|
+
application = Application.start()
|
|
695
|
+
application.register("shadcn--accordion", AccordionController)
|
|
696
|
+
await nextFrame()
|
|
697
|
+
|
|
698
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
699
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
700
|
+
|
|
701
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
702
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
703
|
+
|
|
704
|
+
// Expand first
|
|
705
|
+
click(trigger1)
|
|
706
|
+
await nextFrame()
|
|
707
|
+
|
|
708
|
+
// Collapse
|
|
709
|
+
click(trigger1)
|
|
710
|
+
await nextFrame()
|
|
711
|
+
|
|
712
|
+
expect(content1.style.height).toBe('0px')
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
test("sets hidden attribute after collapse animation", async () => {
|
|
716
|
+
application.stop()
|
|
717
|
+
document.body.innerHTML = createAccordionHTML("single", true)
|
|
718
|
+
|
|
719
|
+
application = Application.start()
|
|
720
|
+
application.register("shadcn--accordion", AccordionController)
|
|
721
|
+
await nextFrame()
|
|
722
|
+
|
|
723
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
724
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
725
|
+
|
|
726
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
727
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
728
|
+
|
|
729
|
+
// Expand first
|
|
730
|
+
click(trigger1)
|
|
731
|
+
await nextFrame()
|
|
732
|
+
|
|
733
|
+
// Collapse
|
|
734
|
+
click(trigger1)
|
|
735
|
+
|
|
736
|
+
// Wait for animation (200ms)
|
|
737
|
+
await wait(250)
|
|
738
|
+
|
|
739
|
+
expect(content1.hidden).toBe(true)
|
|
740
|
+
})
|
|
741
|
+
|
|
742
|
+
test("removes fixed height after expand animation", async () => {
|
|
743
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
744
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
745
|
+
|
|
746
|
+
click(trigger1)
|
|
747
|
+
|
|
748
|
+
// Wait for animation to complete (200ms)
|
|
749
|
+
await wait(250)
|
|
750
|
+
|
|
751
|
+
expect(content1.style.height).toBe('')
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
describe("data-state attributes", () => {
|
|
756
|
+
test("item has data-state='closed' initially", () => {
|
|
757
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
758
|
+
expect(item1.dataset.state).toBe("closed")
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
test("item has data-state='open' when expanded", () => {
|
|
762
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
763
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
764
|
+
|
|
765
|
+
click(trigger1)
|
|
766
|
+
|
|
767
|
+
expect(item1.dataset.state).toBe("open")
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
test("trigger has data-state='open' when item is expanded", () => {
|
|
771
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
772
|
+
|
|
773
|
+
click(trigger1)
|
|
774
|
+
|
|
775
|
+
expect(trigger1.dataset.state).toBe("open")
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
test("content has data-state='open' when item is expanded", () => {
|
|
779
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
780
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
781
|
+
|
|
782
|
+
click(trigger1)
|
|
783
|
+
|
|
784
|
+
expect(content1.dataset.state).toBe("open")
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
test("all elements have data-state='closed' when collapsed in collapsible mode", async () => {
|
|
788
|
+
application.stop()
|
|
789
|
+
document.body.innerHTML = createAccordionHTML("single", true)
|
|
790
|
+
|
|
791
|
+
application = Application.start()
|
|
792
|
+
application.register("shadcn--accordion", AccordionController)
|
|
793
|
+
await nextFrame()
|
|
794
|
+
|
|
795
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
796
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
797
|
+
|
|
798
|
+
const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
|
|
799
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
800
|
+
const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
|
|
801
|
+
|
|
802
|
+
// Expand then collapse
|
|
803
|
+
click(trigger1)
|
|
804
|
+
click(trigger1)
|
|
805
|
+
await nextFrame()
|
|
806
|
+
|
|
807
|
+
expect(item1.dataset.state).toBe("closed")
|
|
808
|
+
expect(trigger1.dataset.state).toBe("closed")
|
|
809
|
+
expect(content1.dataset.state).toBe("closed")
|
|
810
|
+
})
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
describe("integration scenarios", () => {
|
|
814
|
+
test("can toggle between items in single mode", async () => {
|
|
815
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
816
|
+
const items = element.querySelectorAll('[data-shadcn--accordion-target="item"]')
|
|
817
|
+
|
|
818
|
+
// Expand item 1
|
|
819
|
+
click(triggers[0])
|
|
820
|
+
expect(items[0].dataset.state).toBe("open")
|
|
821
|
+
expect(items[1].dataset.state).toBe("closed")
|
|
822
|
+
expect(items[2].dataset.state).toBe("closed")
|
|
823
|
+
|
|
824
|
+
// Expand item 2
|
|
825
|
+
click(triggers[1])
|
|
826
|
+
await nextFrame()
|
|
827
|
+
|
|
828
|
+
expect(items[0].dataset.state).toBe("closed")
|
|
829
|
+
expect(items[1].dataset.state).toBe("open")
|
|
830
|
+
expect(items[2].dataset.state).toBe("closed")
|
|
831
|
+
|
|
832
|
+
// Expand item 3
|
|
833
|
+
click(triggers[2])
|
|
834
|
+
await nextFrame()
|
|
835
|
+
|
|
836
|
+
expect(items[0].dataset.state).toBe("closed")
|
|
837
|
+
expect(items[1].dataset.state).toBe("closed")
|
|
838
|
+
expect(items[2].dataset.state).toBe("open")
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
test("can expand and collapse all items in multiple mode", async () => {
|
|
842
|
+
application.stop()
|
|
843
|
+
document.body.innerHTML = createAccordionHTML("multiple")
|
|
844
|
+
|
|
845
|
+
application = Application.start()
|
|
846
|
+
application.register("shadcn--accordion", AccordionController)
|
|
847
|
+
await nextFrame()
|
|
848
|
+
|
|
849
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
850
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
851
|
+
|
|
852
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
853
|
+
const items = element.querySelectorAll('[data-shadcn--accordion-target="item"]')
|
|
854
|
+
|
|
855
|
+
// Expand all
|
|
856
|
+
triggers.forEach(trigger => click(trigger))
|
|
857
|
+
items.forEach(item => expect(item.dataset.state).toBe("open"))
|
|
858
|
+
|
|
859
|
+
// Collapse all
|
|
860
|
+
triggers.forEach(trigger => click(trigger))
|
|
861
|
+
await nextFrame()
|
|
862
|
+
|
|
863
|
+
items.forEach(item => expect(item.dataset.state).toBe("closed"))
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
test("keyboard navigation works with expanded items", () => {
|
|
867
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
868
|
+
|
|
869
|
+
// Expand first item
|
|
870
|
+
click(triggers[0])
|
|
871
|
+
|
|
872
|
+
// Focus first trigger and navigate
|
|
873
|
+
triggers[0].focus()
|
|
874
|
+
keydown(triggers[0], 'ArrowDown')
|
|
875
|
+
|
|
876
|
+
expect(document.activeElement).toBe(triggers[1])
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
test("can expand default items and navigate with keyboard", async () => {
|
|
880
|
+
application.stop()
|
|
881
|
+
document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, item-3")
|
|
882
|
+
|
|
883
|
+
application = Application.start()
|
|
884
|
+
application.register("shadcn--accordion", AccordionController)
|
|
885
|
+
await nextFrame()
|
|
886
|
+
|
|
887
|
+
element = document.querySelector('[data-controller="shadcn--accordion"]')
|
|
888
|
+
controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
|
|
889
|
+
|
|
890
|
+
const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
|
|
891
|
+
const item1 = element.querySelector('[data-value="item-1"]')
|
|
892
|
+
const item3 = element.querySelector('[data-value="item-3"]')
|
|
893
|
+
|
|
894
|
+
// Verify defaults are expanded
|
|
895
|
+
expect(item1.dataset.state).toBe("open")
|
|
896
|
+
expect(item3.dataset.state).toBe("open")
|
|
897
|
+
|
|
898
|
+
// Navigate with keyboard
|
|
899
|
+
triggers[0].focus()
|
|
900
|
+
keydown(triggers[0], 'End')
|
|
901
|
+
expect(document.activeElement).toBe(triggers[2])
|
|
902
|
+
})
|
|
903
|
+
})
|
|
904
|
+
})
|