@hypoth-ui/cli 0.0.1 → 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +19 -115
- package/dist/{add-PDBC4JTE.js → add-V5PW73GC.js} +29 -17
- package/dist/{chunk-5LTQ2XVL.js → chunk-27CLUUVC.js} +0 -2
- package/dist/{chunk-YPKFYE45.js → chunk-NWIRSZUQ.js} +6 -13
- package/dist/{chunk-GJ6JOQ3Q.js → chunk-PBK72SJJ.js} +1 -1
- package/dist/{diff-BQEXG7HU.js → diff-776UATCA.js} +2 -2
- package/dist/index.js +5 -5
- package/dist/{init-7AZXYAPJ.js → init-GDU2PW7K.js} +10 -13
- package/dist/{list-X6ZLM2NQ.js → list-XDP5I537.js} +3 -3
- package/package.json +16 -12
- package/registry/components.json +1820 -206
- package/templates/accordion/index.tsx +266 -0
- package/templates/accordion/wc/accordion-content.ts +113 -0
- package/templates/accordion/wc/accordion-item.ts +111 -0
- package/templates/accordion/wc/accordion-trigger.ts +105 -0
- package/templates/accordion/wc/accordion.ts +213 -0
- package/templates/accordion/wc/index.ts +12 -0
- package/templates/alert/index.tsx +177 -0
- package/templates/alert/wc/alert.ts +167 -0
- package/templates/alert/wc/index.ts +1 -0
- package/templates/alert-dialog/index.tsx +360 -0
- package/templates/alert-dialog/wc/alert-dialog-action.ts +43 -0
- package/templates/alert-dialog/wc/alert-dialog-cancel.ts +43 -0
- package/templates/alert-dialog/wc/alert-dialog-content.ts +42 -0
- package/templates/alert-dialog/wc/alert-dialog-description.ts +34 -0
- package/templates/alert-dialog/wc/alert-dialog-footer.ts +25 -0
- package/templates/alert-dialog/wc/alert-dialog-header.ts +25 -0
- package/templates/alert-dialog/wc/alert-dialog-title.ts +34 -0
- package/templates/alert-dialog/wc/alert-dialog-trigger.ts +46 -0
- package/templates/alert-dialog/wc/alert-dialog.ts +302 -0
- package/templates/alert-dialog/wc/index.ts +13 -0
- package/templates/aspect-ratio/index.tsx +50 -0
- package/templates/aspect-ratio/wc/aspect-ratio.ts +78 -0
- package/templates/aspect-ratio/wc/index.ts +5 -0
- package/templates/avatar/avatar-group.tsx +88 -0
- package/templates/avatar/avatar.tsx +124 -0
- package/templates/avatar/index.tsx +33 -0
- package/templates/avatar/wc/avatar-group.ts +112 -0
- package/templates/avatar/wc/avatar.ts +184 -0
- package/templates/avatar/wc/index.ts +5 -0
- package/templates/badge/index.tsx +140 -0
- package/templates/badge/wc/badge.ts +119 -0
- package/templates/badge/wc/index.ts +9 -0
- package/templates/breadcrumb/index.tsx +157 -0
- package/templates/breadcrumb/wc/breadcrumb-item.ts +30 -0
- package/templates/breadcrumb/wc/breadcrumb-link.ts +70 -0
- package/templates/breadcrumb/wc/breadcrumb-list.ts +30 -0
- package/templates/breadcrumb/wc/breadcrumb-page.ts +32 -0
- package/templates/breadcrumb/wc/breadcrumb-separator.ts +31 -0
- package/templates/breadcrumb/wc/breadcrumb.ts +55 -0
- package/templates/breadcrumb/wc/index.ts +10 -0
- package/templates/button/button.tsx +119 -0
- package/templates/button/index.ts +1 -0
- package/templates/button/wc/button.ts +169 -0
- package/templates/calendar/index.tsx +149 -0
- package/templates/calendar/wc/calendar.ts +316 -0
- package/templates/calendar/wc/index.ts +4 -0
- package/templates/card/index.tsx +108 -0
- package/templates/card/wc/card-content.ts +25 -0
- package/templates/card/wc/card-footer.ts +25 -0
- package/templates/card/wc/card-header.ts +25 -0
- package/templates/card/wc/card.ts +43 -0
- package/templates/card/wc/index.ts +8 -0
- package/templates/checkbox/checkbox.tsx +85 -0
- package/templates/checkbox/wc/checkbox.ts +247 -0
- package/templates/collapsible/index.tsx +172 -0
- package/templates/collapsible/wc/collapsible-content.ts +97 -0
- package/templates/collapsible/wc/collapsible-trigger.ts +39 -0
- package/templates/collapsible/wc/collapsible.ts +143 -0
- package/templates/collapsible/wc/index.ts +7 -0
- package/templates/combobox/combobox-content.tsx +141 -0
- package/templates/combobox/combobox-context.ts +36 -0
- package/templates/combobox/combobox-empty.tsx +38 -0
- package/templates/combobox/combobox-input.tsx +159 -0
- package/templates/combobox/combobox-loading.tsx +38 -0
- package/templates/combobox/combobox-option.tsx +99 -0
- package/templates/combobox/combobox-root.tsx +207 -0
- package/templates/combobox/combobox-tag.tsx +62 -0
- package/templates/combobox/index.ts +62 -0
- package/templates/combobox/wc/combobox-content.ts +97 -0
- package/templates/combobox/wc/combobox-input.ts +134 -0
- package/templates/combobox/wc/combobox-option.ts +111 -0
- package/templates/combobox/wc/combobox-tag.ts +103 -0
- package/templates/combobox/wc/combobox.ts +981 -0
- package/templates/combobox/wc/index.ts +5 -0
- package/templates/command/index.tsx +279 -0
- package/templates/command/wc/command-empty.ts +24 -0
- package/templates/command/wc/command-group.ts +60 -0
- package/templates/command/wc/command-input.ts +136 -0
- package/templates/command/wc/command-item.ts +78 -0
- package/templates/command/wc/command-list.ts +103 -0
- package/templates/command/wc/command-loading.ts +24 -0
- package/templates/command/wc/command-separator.ts +23 -0
- package/templates/command/wc/command.ts +176 -0
- package/templates/context-menu/index.tsx +262 -0
- package/templates/context-menu/wc/context-menu-content.ts +41 -0
- package/templates/context-menu/wc/context-menu-item.ts +83 -0
- package/templates/context-menu/wc/context-menu-label.ts +30 -0
- package/templates/context-menu/wc/context-menu-separator.ts +28 -0
- package/templates/context-menu/wc/context-menu.ts +324 -0
- package/templates/context-menu/wc/index.ts +9 -0
- package/templates/data-table/index.tsx +263 -0
- package/templates/data-table/wc/data-table.ts +405 -0
- package/templates/data-table/wc/index.ts +10 -0
- package/templates/date-picker/date-picker-calendar.tsx +352 -0
- package/templates/date-picker/date-picker-content.tsx +121 -0
- package/templates/date-picker/date-picker-context.ts +46 -0
- package/templates/date-picker/date-picker-root.tsx +201 -0
- package/templates/date-picker/date-picker-trigger.tsx +95 -0
- package/templates/date-picker/index.ts +44 -0
- package/templates/date-picker/wc/date-picker-calendar.ts +457 -0
- package/templates/date-picker/wc/date-picker.ts +592 -0
- package/templates/date-picker/wc/date-utils.ts +467 -0
- package/templates/date-picker/wc/index.ts +3 -0
- package/templates/dialog/dialog-close.tsx +57 -0
- package/templates/dialog/dialog-content.tsx +106 -0
- package/templates/dialog/dialog-context.ts +24 -0
- package/templates/dialog/dialog-description.tsx +51 -0
- package/templates/dialog/dialog-root.tsx +104 -0
- package/templates/dialog/dialog-title.tsx +38 -0
- package/templates/dialog/dialog-trigger.tsx +94 -0
- package/templates/dialog/index.ts +52 -0
- package/templates/dialog/wc/dialog-content.ts +59 -0
- package/templates/dialog/wc/dialog-description.ts +58 -0
- package/templates/dialog/wc/dialog-title.ts +56 -0
- package/templates/dialog/wc/dialog.ts +411 -0
- package/templates/drawer/index.tsx +263 -0
- package/templates/drawer/wc/drawer-content.ts +150 -0
- package/templates/drawer/wc/drawer-description.ts +34 -0
- package/templates/drawer/wc/drawer-footer.ts +25 -0
- package/templates/drawer/wc/drawer-header.ts +25 -0
- package/templates/drawer/wc/drawer-title.ts +34 -0
- package/templates/drawer/wc/drawer.ts +348 -0
- package/templates/drawer/wc/index.ts +10 -0
- package/templates/dropdown-menu/index.tsx +454 -0
- package/templates/dropdown-menu/wc/dropdown-menu-checkbox-item.ts +93 -0
- package/templates/dropdown-menu/wc/dropdown-menu-content.ts +43 -0
- package/templates/dropdown-menu/wc/dropdown-menu-item.ts +85 -0
- package/templates/dropdown-menu/wc/dropdown-menu-label.ts +31 -0
- package/templates/dropdown-menu/wc/dropdown-menu-radio-group.ts +80 -0
- package/templates/dropdown-menu/wc/dropdown-menu-radio-item.ts +101 -0
- package/templates/dropdown-menu/wc/dropdown-menu-separator.ts +28 -0
- package/templates/dropdown-menu/wc/dropdown-menu.ts +358 -0
- package/templates/dropdown-menu/wc/index.ts +12 -0
- package/templates/field/field-description.tsx +39 -0
- package/templates/field/field-error.tsx +37 -0
- package/templates/field/field.tsx +46 -0
- package/templates/field/index.ts +4 -0
- package/templates/field/label.tsx +40 -0
- package/templates/field/wc/field-description.ts +42 -0
- package/templates/field/wc/field-error.ts +46 -0
- package/templates/field/wc/field.ts +210 -0
- package/templates/field/wc/label.ts +54 -0
- package/templates/file-upload/file-upload-context.ts +26 -0
- package/templates/file-upload/file-upload-dropzone.tsx +111 -0
- package/templates/file-upload/file-upload-input.tsx +86 -0
- package/templates/file-upload/file-upload-item.tsx +105 -0
- package/templates/file-upload/file-upload-root.tsx +115 -0
- package/templates/file-upload/index.ts +50 -0
- package/templates/file-upload/wc/file-upload.ts +380 -0
- package/templates/file-upload/wc/index.ts +1 -0
- package/templates/hover-card/index.tsx +203 -0
- package/templates/hover-card/wc/hover-card-content.ts +50 -0
- package/templates/hover-card/wc/hover-card.ts +382 -0
- package/templates/hover-card/wc/index.ts +6 -0
- package/templates/icon/icon.tsx +76 -0
- package/templates/icon/wc/icon-adapter.ts +108 -0
- package/templates/icon/wc/icon.ts +161 -0
- package/templates/input/input.tsx +130 -0
- package/templates/input/wc/input.ts +216 -0
- package/templates/layout/app-shell.tsx +177 -0
- package/templates/layout/box.tsx +53 -0
- package/templates/layout/center.tsx +42 -0
- package/templates/layout/container.tsx +43 -0
- package/templates/layout/flow.tsx +83 -0
- package/templates/layout/grid.tsx +79 -0
- package/templates/layout/index.ts +33 -0
- package/templates/layout/inline.tsx +16 -0
- package/templates/layout/page.tsx +43 -0
- package/templates/layout/section.tsx +39 -0
- package/templates/layout/spacer.tsx +30 -0
- package/templates/layout/split.tsx +47 -0
- package/templates/layout/stack.tsx +16 -0
- package/templates/layout/wc/app-shell.ts +58 -0
- package/templates/layout/wc/box.ts +117 -0
- package/templates/layout/wc/center.ts +78 -0
- package/templates/layout/wc/container.ts +77 -0
- package/templates/layout/wc/flow.ts +149 -0
- package/templates/layout/wc/footer.ts +57 -0
- package/templates/layout/wc/grid.ts +142 -0
- package/templates/layout/wc/header.ts +57 -0
- package/templates/layout/wc/index.ts +41 -0
- package/templates/layout/wc/main.ts +46 -0
- package/templates/layout/wc/page.ts +81 -0
- package/templates/layout/wc/section.ts +65 -0
- package/templates/layout/wc/spacer.ts +77 -0
- package/templates/layout/wc/split.ts +94 -0
- package/templates/layout/wc/wrap.ts +93 -0
- package/templates/layout/wrap.tsx +46 -0
- package/templates/link/link.tsx +109 -0
- package/templates/link/wc/link.ts +124 -0
- package/templates/list/index.tsx +55 -0
- package/templates/list/list-item.tsx +117 -0
- package/templates/list/list.tsx +115 -0
- package/templates/list/wc/index.ts +5 -0
- package/templates/list/wc/list-item.ts +127 -0
- package/templates/list/wc/list.ts +114 -0
- package/templates/menu/index.ts +49 -0
- package/templates/menu/menu-content.tsx +109 -0
- package/templates/menu/menu-context.ts +17 -0
- package/templates/menu/menu-item.tsx +108 -0
- package/templates/menu/menu-label.tsx +32 -0
- package/templates/menu/menu-root.tsx +108 -0
- package/templates/menu/menu-separator.tsx +24 -0
- package/templates/menu/menu-trigger.tsx +104 -0
- package/templates/menu/wc/menu-content.ts +67 -0
- package/templates/menu/wc/menu-item.ts +109 -0
- package/templates/menu/wc/menu.ts +449 -0
- package/templates/navigation-menu/index.tsx +328 -0
- package/templates/navigation-menu/wc/index.ts +12 -0
- package/templates/navigation-menu/wc/navigation-menu-content.ts +30 -0
- package/templates/navigation-menu/wc/navigation-menu-indicator.ts +30 -0
- package/templates/navigation-menu/wc/navigation-menu-item.ts +60 -0
- package/templates/navigation-menu/wc/navigation-menu-link.ts +97 -0
- package/templates/navigation-menu/wc/navigation-menu-list.ts +30 -0
- package/templates/navigation-menu/wc/navigation-menu-trigger.ts +110 -0
- package/templates/navigation-menu/wc/navigation-menu-viewport.ts +85 -0
- package/templates/navigation-menu/wc/navigation-menu.ts +272 -0
- package/templates/number-input/index.ts +46 -0
- package/templates/number-input/number-input-context.ts +38 -0
- package/templates/number-input/number-input-decrement.tsx +53 -0
- package/templates/number-input/number-input-field.tsx +93 -0
- package/templates/number-input/number-input-increment.tsx +53 -0
- package/templates/number-input/number-input-root.tsx +137 -0
- package/templates/number-input/wc/index.ts +1 -0
- package/templates/number-input/wc/number-input.ts +283 -0
- package/templates/pagination/index.tsx +198 -0
- package/templates/pagination/wc/index.ts +11 -0
- package/templates/pagination/wc/pagination-content.ts +30 -0
- package/templates/pagination/wc/pagination-ellipsis.ts +28 -0
- package/templates/pagination/wc/pagination-item.ts +30 -0
- package/templates/pagination/wc/pagination-link.ts +76 -0
- package/templates/pagination/wc/pagination-next.ts +69 -0
- package/templates/pagination/wc/pagination-previous.ts +69 -0
- package/templates/pagination/wc/pagination.ts +156 -0
- package/templates/pin-input/index.ts +39 -0
- package/templates/pin-input/pin-input-context.ts +30 -0
- package/templates/pin-input/pin-input-field.tsx +186 -0
- package/templates/pin-input/pin-input-root.tsx +120 -0
- package/templates/pin-input/wc/index.ts +1 -0
- package/templates/pin-input/wc/pin-input.ts +259 -0
- package/templates/popover/popover.tsx +121 -0
- package/templates/popover/wc/popover-content.ts +66 -0
- package/templates/popover/wc/popover.ts +343 -0
- package/templates/progress/index.tsx +117 -0
- package/templates/progress/wc/index.ts +4 -0
- package/templates/progress/wc/progress.ts +174 -0
- package/templates/radio/radio.tsx +43 -0
- package/templates/radio/wc/radio-group.ts +261 -0
- package/templates/radio/wc/radio.ts +145 -0
- package/templates/scroll-area/index.tsx +144 -0
- package/templates/scroll-area/wc/index.ts +8 -0
- package/templates/scroll-area/wc/scroll-area-scrollbar.ts +143 -0
- package/templates/scroll-area/wc/scroll-area-thumb.ts +225 -0
- package/templates/scroll-area/wc/scroll-area-viewport.ts +120 -0
- package/templates/scroll-area/wc/scroll-area.ts +63 -0
- package/templates/select/index.ts +57 -0
- package/templates/select/select-content.tsx +243 -0
- package/templates/select/select-context.ts +30 -0
- package/templates/select/select-group.tsx +53 -0
- package/templates/select/select-label.tsx +34 -0
- package/templates/select/select-option.tsx +97 -0
- package/templates/select/select-root.tsx +153 -0
- package/templates/select/select-separator.tsx +27 -0
- package/templates/select/select-trigger.tsx +112 -0
- package/templates/select/select-value.tsx +48 -0
- package/templates/select/wc/index.ts +6 -0
- package/templates/select/wc/select-content.ts +89 -0
- package/templates/select/wc/select-group.ts +82 -0
- package/templates/select/wc/select-label.ts +49 -0
- package/templates/select/wc/select-option.ts +111 -0
- package/templates/select/wc/select-trigger.ts +101 -0
- package/templates/select/wc/select.ts +840 -0
- package/templates/separator/index.tsx +49 -0
- package/templates/separator/wc/index.ts +5 -0
- package/templates/separator/wc/separator.ts +60 -0
- package/templates/sheet/index.tsx +291 -0
- package/templates/sheet/wc/index.ts +12 -0
- package/templates/sheet/wc/sheet-close.ts +43 -0
- package/templates/sheet/wc/sheet-content.ts +47 -0
- package/templates/sheet/wc/sheet-description.ts +34 -0
- package/templates/sheet/wc/sheet-footer.ts +25 -0
- package/templates/sheet/wc/sheet-header.ts +25 -0
- package/templates/sheet/wc/sheet-overlay.ts +23 -0
- package/templates/sheet/wc/sheet-title.ts +34 -0
- package/templates/sheet/wc/sheet.ts +336 -0
- package/templates/skeleton/index.tsx +131 -0
- package/templates/skeleton/wc/index.ts +10 -0
- package/templates/skeleton/wc/skeleton.ts +107 -0
- package/templates/slider/index.ts +41 -0
- package/templates/slider/slider-context.ts +36 -0
- package/templates/slider/slider-range.tsx +59 -0
- package/templates/slider/slider-root.tsx +166 -0
- package/templates/slider/slider-thumb.tsx +213 -0
- package/templates/slider/slider-track.tsx +113 -0
- package/templates/slider/wc/index.ts +1 -0
- package/templates/slider/wc/slider.ts +465 -0
- package/templates/spinner/spinner.tsx +64 -0
- package/templates/spinner/wc/spinner.ts +70 -0
- package/templates/stepper/index.tsx +230 -0
- package/templates/stepper/wc/index.ts +12 -0
- package/templates/stepper/wc/stepper-content.ts +30 -0
- package/templates/stepper/wc/stepper-description.ts +25 -0
- package/templates/stepper/wc/stepper-indicator.ts +30 -0
- package/templates/stepper/wc/stepper-item.ts +55 -0
- package/templates/stepper/wc/stepper-separator.ts +29 -0
- package/templates/stepper/wc/stepper-title.ts +25 -0
- package/templates/stepper/wc/stepper-trigger.ts +67 -0
- package/templates/stepper/wc/stepper.ts +164 -0
- package/templates/switch/switch.tsx +90 -0
- package/templates/switch/wc/switch.ts +228 -0
- package/templates/table/body.tsx +21 -0
- package/templates/table/cell.tsx +44 -0
- package/templates/table/head.tsx +112 -0
- package/templates/table/header.tsx +21 -0
- package/templates/table/index.tsx +93 -0
- package/templates/table/root.tsx +82 -0
- package/templates/table/row.tsx +36 -0
- package/templates/table/wc/index.ts +9 -0
- package/templates/table/wc/table-body.ts +32 -0
- package/templates/table/wc/table-cell.ts +58 -0
- package/templates/table/wc/table-head.ts +129 -0
- package/templates/table/wc/table-header.ts +32 -0
- package/templates/table/wc/table-row.ts +50 -0
- package/templates/table/wc/table.ts +93 -0
- package/templates/tabs/index.tsx +222 -0
- package/templates/tabs/wc/index.ts +8 -0
- package/templates/tabs/wc/tabs-content.ts +82 -0
- package/templates/tabs/wc/tabs-list.ts +56 -0
- package/templates/tabs/wc/tabs-trigger.ts +136 -0
- package/templates/tabs/wc/tabs.ts +202 -0
- package/templates/tag/index.tsx +186 -0
- package/templates/tag/wc/index.ts +4 -0
- package/templates/tag/wc/tag.ts +166 -0
- package/templates/text/text.tsx +100 -0
- package/templates/text/wc/text.ts +94 -0
- package/templates/textarea/textarea.tsx +134 -0
- package/templates/textarea/wc/textarea.ts +280 -0
- package/templates/time-picker/index.ts +42 -0
- package/templates/time-picker/time-picker-context.ts +28 -0
- package/templates/time-picker/time-picker-root.tsx +113 -0
- package/templates/time-picker/time-picker-segment.tsx +91 -0
- package/templates/time-picker/wc/index.ts +1 -0
- package/templates/time-picker/wc/time-picker.ts +221 -0
- package/templates/toast/index.tsx +71 -0
- package/templates/toast/provider.tsx +228 -0
- package/templates/toast/toast.tsx +142 -0
- package/templates/toast/use-toast.ts +89 -0
- package/templates/toast/wc/index.ts +15 -0
- package/templates/toast/wc/toast-controller.ts +282 -0
- package/templates/toast/wc/toast-provider.ts +161 -0
- package/templates/toast/wc/toast.ts +165 -0
- package/templates/tooltip/tooltip.tsx +62 -0
- package/templates/tooltip/wc/tooltip-content.ts +64 -0
- package/templates/tooltip/wc/tooltip.ts +289 -0
- package/templates/tree/index.tsx +60 -0
- package/templates/tree/tree-item.tsx +131 -0
- package/templates/tree/tree.tsx +138 -0
- package/templates/tree/wc/index.ts +11 -0
- package/templates/tree/wc/tree-item.ts +273 -0
- package/templates/tree/wc/tree-utils.ts +143 -0
- package/templates/tree/wc/tree.ts +139 -0
- package/templates/visually-hidden/visually-hidden.tsx +45 -0
- package/templates/visually-hidden/wc/visually-hidden.ts +64 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DialogBehavior,
|
|
3
|
+
type Presence,
|
|
4
|
+
createDialogBehavior,
|
|
5
|
+
createPresence,
|
|
6
|
+
prefersReducedMotion,
|
|
7
|
+
} from "@hypoth-ui/primitives-dom";
|
|
8
|
+
import { html } from "lit";
|
|
9
|
+
import { property, state } from "lit/decorators.js";
|
|
10
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
11
|
+
import { StandardEvents, emitEvent } from "../../events/emit.js";
|
|
12
|
+
import { define } from "../../registry/define.js";
|
|
13
|
+
import { devWarn, hasRequiredChild, Warnings } from "../../utils/dev-warnings.js";
|
|
14
|
+
|
|
15
|
+
// Import child components to ensure they're registered
|
|
16
|
+
import type { DsDialogContent } from "./dialog-content.js";
|
|
17
|
+
import "./dialog-content.js";
|
|
18
|
+
import "./dialog-title.js";
|
|
19
|
+
import "./dialog-description.js";
|
|
20
|
+
|
|
21
|
+
export type DialogRole = "dialog" | "alertdialog";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Modal dialog component with accessibility and focus management.
|
|
25
|
+
*
|
|
26
|
+
* Provides a modal overlay with backdrop, focus trap, and dismissal handling.
|
|
27
|
+
* Follows WAI-ARIA Dialog (Modal) pattern.
|
|
28
|
+
*
|
|
29
|
+
* @element ds-dialog
|
|
30
|
+
*
|
|
31
|
+
* @slot trigger - Button or element that opens the dialog
|
|
32
|
+
* @slot - Dialog content (ds-dialog-content)
|
|
33
|
+
*
|
|
34
|
+
* @fires ds:open-change - Fired when open state changes (detail: { open, reason })
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```html
|
|
38
|
+
* <ds-dialog>
|
|
39
|
+
* <button slot="trigger">Open Dialog</button>
|
|
40
|
+
* <ds-dialog-content>
|
|
41
|
+
* <ds-dialog-title>Confirm Action</ds-dialog-title>
|
|
42
|
+
* <ds-dialog-description>Are you sure?</ds-dialog-description>
|
|
43
|
+
* <button>Yes</button>
|
|
44
|
+
* <button>No</button>
|
|
45
|
+
* </ds-dialog-content>
|
|
46
|
+
* </ds-dialog>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export class DsDialog extends DSElement {
|
|
50
|
+
/** Whether the dialog is open */
|
|
51
|
+
@property({ type: Boolean, reflect: true })
|
|
52
|
+
open = false;
|
|
53
|
+
|
|
54
|
+
/** Whether Escape key closes the dialog */
|
|
55
|
+
@property({ type: Boolean, attribute: "close-on-escape" })
|
|
56
|
+
closeOnEscape = true;
|
|
57
|
+
|
|
58
|
+
/** Whether clicking the backdrop closes the dialog */
|
|
59
|
+
@property({ type: Boolean, attribute: "close-on-backdrop" })
|
|
60
|
+
closeOnBackdrop = true;
|
|
61
|
+
|
|
62
|
+
/** Whether to animate open/close transitions */
|
|
63
|
+
@property({ type: Boolean })
|
|
64
|
+
animated = true;
|
|
65
|
+
|
|
66
|
+
/** Dialog role - stored internally, read from attribute */
|
|
67
|
+
@state()
|
|
68
|
+
private dialogRole: DialogRole = "dialog";
|
|
69
|
+
|
|
70
|
+
/** Whether the dialog is closing (for animation) */
|
|
71
|
+
@state()
|
|
72
|
+
private isClosing = false;
|
|
73
|
+
|
|
74
|
+
private dialogBehavior: DialogBehavior | null = null;
|
|
75
|
+
private presence: Presence | null = null;
|
|
76
|
+
private attributeObserver: MutationObserver | null = null;
|
|
77
|
+
private backdropElement: HTMLElement | null = null;
|
|
78
|
+
|
|
79
|
+
override connectedCallback(): void {
|
|
80
|
+
super.connectedCallback();
|
|
81
|
+
|
|
82
|
+
// Listen for trigger clicks via event delegation
|
|
83
|
+
this.addEventListener("click", this.handleTriggerClick);
|
|
84
|
+
|
|
85
|
+
// Watch for role attribute changes (don't reflect back, just read)
|
|
86
|
+
this.attributeObserver = new MutationObserver(() => {
|
|
87
|
+
const roleAttr = this.getAttribute("role");
|
|
88
|
+
if (roleAttr === "alertdialog" || roleAttr === "dialog") {
|
|
89
|
+
this.dialogRole = roleAttr;
|
|
90
|
+
}
|
|
91
|
+
// Remove the role from ds-dialog itself (it shouldn't have it)
|
|
92
|
+
if (roleAttr) {
|
|
93
|
+
this.removeAttribute("role");
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
this.attributeObserver.observe(this, {
|
|
97
|
+
attributes: true,
|
|
98
|
+
attributeFilter: ["role"],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Read initial role attribute
|
|
102
|
+
const initialRole = this.getAttribute("role");
|
|
103
|
+
if (initialRole === "alertdialog" || initialRole === "dialog") {
|
|
104
|
+
this.dialogRole = initialRole;
|
|
105
|
+
this.removeAttribute("role");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Initialize dialog behavior
|
|
109
|
+
this.initDialogBehavior();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override disconnectedCallback(): void {
|
|
113
|
+
super.disconnectedCallback();
|
|
114
|
+
this.removeEventListener("click", this.handleTriggerClick);
|
|
115
|
+
this.attributeObserver?.disconnect();
|
|
116
|
+
this.attributeObserver = null;
|
|
117
|
+
this.cleanup();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Initializes the dialog behavior primitive.
|
|
122
|
+
*/
|
|
123
|
+
private initDialogBehavior(): void {
|
|
124
|
+
this.dialogBehavior = createDialogBehavior({
|
|
125
|
+
defaultOpen: this.open,
|
|
126
|
+
role: this.dialogRole,
|
|
127
|
+
closeOnEscape: this.closeOnEscape,
|
|
128
|
+
closeOnOutsideClick: this.closeOnBackdrop,
|
|
129
|
+
onOpenChange: (open) => {
|
|
130
|
+
// Sync state when behavior changes (e.g., from escape or outside click)
|
|
131
|
+
if (!open && this.open) {
|
|
132
|
+
this.handleBehaviorClose();
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Set trigger element if one exists
|
|
138
|
+
const trigger = this.querySelector('[slot="trigger"]') as HTMLElement | null;
|
|
139
|
+
if (trigger) {
|
|
140
|
+
this.dialogBehavior.setTriggerElement(trigger);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Handles close triggered by the behavior (escape/outside click).
|
|
146
|
+
*/
|
|
147
|
+
private handleBehaviorClose(reason: "escape" | "outside-click" = "escape"): void {
|
|
148
|
+
// Emit cancelable open-change event before closing
|
|
149
|
+
const openChangeEvent = emitEvent(this, StandardEvents.OPEN_CHANGE, {
|
|
150
|
+
detail: { open: false, reason },
|
|
151
|
+
cancelable: true,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (openChangeEvent.defaultPrevented) {
|
|
155
|
+
// Re-open the behavior since we're preventing close
|
|
156
|
+
this.dialogBehavior?.open();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const content = this.querySelector("ds-dialog-content") as DsDialogContent | null;
|
|
161
|
+
|
|
162
|
+
// If animated, use presence for exit animation
|
|
163
|
+
if (this.animated && content && !prefersReducedMotion()) {
|
|
164
|
+
this.isClosing = true;
|
|
165
|
+
this._closeReason = reason;
|
|
166
|
+
|
|
167
|
+
// Create presence for exit animation
|
|
168
|
+
this.presence = createPresence({
|
|
169
|
+
onExitComplete: () => {
|
|
170
|
+
this.completeClose();
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
this.presence.hide(content);
|
|
174
|
+
} else {
|
|
175
|
+
// No animation - close immediately
|
|
176
|
+
this.open = false;
|
|
177
|
+
this.isClosing = false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Tracks the reason for closing (for animation completion) */
|
|
182
|
+
private _closeReason: "escape" | "outside-click" | "trigger" | "programmatic" = "programmatic";
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Opens the dialog.
|
|
186
|
+
*/
|
|
187
|
+
public show(): void {
|
|
188
|
+
if (this.open) return;
|
|
189
|
+
|
|
190
|
+
// Store trigger element for focus return
|
|
191
|
+
const trigger = this.querySelector('[slot="trigger"]') as HTMLElement | null;
|
|
192
|
+
if (trigger) {
|
|
193
|
+
this.dialogBehavior?.setTriggerElement(trigger);
|
|
194
|
+
} else if (document.activeElement instanceof HTMLElement) {
|
|
195
|
+
this.dialogBehavior?.setTriggerElement(document.activeElement);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.open = true;
|
|
199
|
+
this.dialogBehavior?.open();
|
|
200
|
+
emitEvent(this, StandardEvents.OPEN_CHANGE, {
|
|
201
|
+
detail: { open: true, reason: "trigger" },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Closes the dialog.
|
|
207
|
+
* @param reason - The reason for closing (default: "programmatic")
|
|
208
|
+
*/
|
|
209
|
+
public close(reason: "escape" | "outside-click" | "trigger" | "programmatic" = "programmatic"): void {
|
|
210
|
+
if (!this.open) return;
|
|
211
|
+
|
|
212
|
+
// Emit cancelable open-change event before closing
|
|
213
|
+
const openChangeEvent = emitEvent(this, StandardEvents.OPEN_CHANGE, {
|
|
214
|
+
detail: { open: false, reason },
|
|
215
|
+
cancelable: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (openChangeEvent.defaultPrevented) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const content = this.querySelector("ds-dialog-content") as DsDialogContent | null;
|
|
223
|
+
|
|
224
|
+
// Close the behavior (this deactivates focus trap and dismiss layer)
|
|
225
|
+
this.dialogBehavior?.close();
|
|
226
|
+
|
|
227
|
+
// If animated, use presence for exit animation
|
|
228
|
+
if (this.animated && content && !prefersReducedMotion()) {
|
|
229
|
+
this.isClosing = true;
|
|
230
|
+
this._closeReason = reason;
|
|
231
|
+
|
|
232
|
+
// Create presence for exit animation
|
|
233
|
+
this.presence = createPresence({
|
|
234
|
+
onExitComplete: () => {
|
|
235
|
+
this.completeClose();
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
this.presence.hide(content);
|
|
239
|
+
} else {
|
|
240
|
+
// No animation - close immediately
|
|
241
|
+
this.open = false;
|
|
242
|
+
this.isClosing = false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Completes the close after exit animation.
|
|
248
|
+
* Note: The open-change event was already emitted before animation started.
|
|
249
|
+
*/
|
|
250
|
+
private completeClose(): void {
|
|
251
|
+
this.presence?.destroy();
|
|
252
|
+
this.presence = null;
|
|
253
|
+
this.open = false;
|
|
254
|
+
this.isClosing = false;
|
|
255
|
+
// Event was already emitted before animation started
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private handleTriggerClick = (event: Event): void => {
|
|
259
|
+
const target = event.target as HTMLElement;
|
|
260
|
+
const trigger = target.closest('[slot="trigger"]');
|
|
261
|
+
|
|
262
|
+
if (trigger && this.contains(trigger)) {
|
|
263
|
+
// Store trigger before opening
|
|
264
|
+
this.dialogBehavior?.setTriggerElement(trigger as HTMLElement);
|
|
265
|
+
this.show();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
private handleBackdropClick = (event: MouseEvent): void => {
|
|
270
|
+
// Only close if clicking directly on backdrop, not on dialog content
|
|
271
|
+
const content = this.querySelector("ds-dialog-content");
|
|
272
|
+
const target = event.target as Node;
|
|
273
|
+
|
|
274
|
+
// If click is inside content, don't close
|
|
275
|
+
if (content?.contains(target)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Click was on backdrop (outside content)
|
|
280
|
+
if (this.closeOnBackdrop) {
|
|
281
|
+
this.close("outside-click");
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
private cleanup(): void {
|
|
286
|
+
this.dialogBehavior?.destroy();
|
|
287
|
+
this.dialogBehavior = null;
|
|
288
|
+
this.presence?.destroy();
|
|
289
|
+
this.presence = null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
override async updated(changedProperties: Map<string, unknown>): Promise<void> {
|
|
293
|
+
super.updated(changedProperties);
|
|
294
|
+
|
|
295
|
+
if (changedProperties.has("open")) {
|
|
296
|
+
const content = this.querySelector("ds-dialog-content") as DsDialogContent | null;
|
|
297
|
+
|
|
298
|
+
if (this.open) {
|
|
299
|
+
// Wait for the next microtask to ensure DOM is committed
|
|
300
|
+
await this.updateComplete;
|
|
301
|
+
|
|
302
|
+
// Set data-state to open for entry animation
|
|
303
|
+
if (content) {
|
|
304
|
+
content.dataState = "open";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Set content element on behavior (activates focus trap and dismiss layer)
|
|
308
|
+
this.dialogBehavior?.setContentElement(content);
|
|
309
|
+
this.updateContentAccessibility();
|
|
310
|
+
|
|
311
|
+
// Attach backdrop click handler
|
|
312
|
+
this.backdropElement = this.querySelector(".ds-dialog__backdrop");
|
|
313
|
+
this.backdropElement?.addEventListener("click", this.handleBackdropClick);
|
|
314
|
+
} else {
|
|
315
|
+
// Clean up backdrop listener
|
|
316
|
+
this.backdropElement?.removeEventListener("click", this.handleBackdropClick);
|
|
317
|
+
this.backdropElement = null;
|
|
318
|
+
|
|
319
|
+
// Clear content element on behavior
|
|
320
|
+
this.dialogBehavior?.setContentElement(null);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Update role on content when dialogRole changes
|
|
325
|
+
if (changedProperties.has("dialogRole") && this.open) {
|
|
326
|
+
this.updateContentAccessibility();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Sync behavior options when they change
|
|
330
|
+
if (changedProperties.has("closeOnEscape") || changedProperties.has("closeOnBackdrop")) {
|
|
331
|
+
// Re-create behavior with new options
|
|
332
|
+
const wasOpen = this.dialogBehavior?.state.open;
|
|
333
|
+
this.dialogBehavior?.destroy();
|
|
334
|
+
this.initDialogBehavior();
|
|
335
|
+
if (wasOpen) {
|
|
336
|
+
this.dialogBehavior?.open();
|
|
337
|
+
const content = this.querySelector("ds-dialog-content") as HTMLElement | null;
|
|
338
|
+
this.dialogBehavior?.setContentElement(content);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Updates accessibility attributes on dialog content.
|
|
345
|
+
*/
|
|
346
|
+
private updateContentAccessibility(): void {
|
|
347
|
+
const content = this.querySelector("ds-dialog-content");
|
|
348
|
+
if (!content || !this.dialogBehavior) return;
|
|
349
|
+
|
|
350
|
+
// Get props from behavior
|
|
351
|
+
const contentProps = this.dialogBehavior.getContentProps();
|
|
352
|
+
|
|
353
|
+
// Apply content props
|
|
354
|
+
content.setAttribute("role", this.dialogRole);
|
|
355
|
+
content.setAttribute("aria-modal", contentProps["aria-modal"]);
|
|
356
|
+
|
|
357
|
+
// Dev warning: Check for required dialog title
|
|
358
|
+
if (!hasRequiredChild(this, "ds-dialog-title") && !this.getAttribute("aria-label")) {
|
|
359
|
+
devWarn(Warnings.dialogMissingTitle("ds-dialog"));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Connect title via aria-labelledby
|
|
363
|
+
const title = this.querySelector("ds-dialog-title");
|
|
364
|
+
if (title) {
|
|
365
|
+
const titleProps = this.dialogBehavior.getTitleProps();
|
|
366
|
+
if (!title.id) {
|
|
367
|
+
title.id = titleProps.id;
|
|
368
|
+
}
|
|
369
|
+
content.setAttribute("aria-labelledby", title.id);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Connect description via aria-describedby
|
|
373
|
+
const description = this.querySelector("ds-dialog-description");
|
|
374
|
+
if (description) {
|
|
375
|
+
const descProps = this.dialogBehavior.getDescriptionProps();
|
|
376
|
+
if (!description.id) {
|
|
377
|
+
description.id = descProps.id;
|
|
378
|
+
}
|
|
379
|
+
content.setAttribute("aria-describedby", description.id);
|
|
380
|
+
this.dialogBehavior.setHasDescription(true);
|
|
381
|
+
} else {
|
|
382
|
+
// Dev warning: Title without description
|
|
383
|
+
if (title) {
|
|
384
|
+
devWarn(Warnings.dialogMissingDescription("ds-dialog"));
|
|
385
|
+
}
|
|
386
|
+
this.dialogBehavior.setHasDescription(false);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
override render() {
|
|
391
|
+
// Always render backdrop, hide when not open
|
|
392
|
+
return html`
|
|
393
|
+
<slot name="trigger"></slot>
|
|
394
|
+
<div
|
|
395
|
+
class="ds-dialog__backdrop"
|
|
396
|
+
?hidden=${!this.open}
|
|
397
|
+
?data-closing=${this.isClosing}
|
|
398
|
+
>
|
|
399
|
+
<slot></slot>
|
|
400
|
+
</div>
|
|
401
|
+
`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
define("ds-dialog", DsDialog);
|
|
406
|
+
|
|
407
|
+
declare global {
|
|
408
|
+
interface HTMLElementTagNameMap {
|
|
409
|
+
"ds-dialog": DsDialog;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drawer compound component exports.
|
|
3
|
+
*
|
|
4
|
+
* Drawer is a mobile-optimized slide-in panel with swipe gesture support.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { Drawer } from "@/components/ui";
|
|
9
|
+
*
|
|
10
|
+
* <Drawer.Root>
|
|
11
|
+
* <Drawer.Trigger>
|
|
12
|
+
* <button>Open Menu</button>
|
|
13
|
+
* </Drawer.Trigger>
|
|
14
|
+
* <Drawer.Content>
|
|
15
|
+
* <Drawer.Header>
|
|
16
|
+
* <Drawer.Title>Navigation</Drawer.Title>
|
|
17
|
+
* </Drawer.Header>
|
|
18
|
+
* <nav>Menu items...</nav>
|
|
19
|
+
* </Drawer.Content>
|
|
20
|
+
* </Drawer.Root>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
type HTMLAttributes,
|
|
26
|
+
type ReactNode,
|
|
27
|
+
createElement,
|
|
28
|
+
forwardRef,
|
|
29
|
+
useCallback,
|
|
30
|
+
useEffect,
|
|
31
|
+
useRef,
|
|
32
|
+
useState,
|
|
33
|
+
} from "react";
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Types
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
export type DrawerSide = "top" | "right" | "bottom" | "left";
|
|
40
|
+
|
|
41
|
+
export interface DrawerRootProps extends HTMLAttributes<HTMLElement> {
|
|
42
|
+
/** Content */
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
/** Controlled open state */
|
|
45
|
+
open?: boolean;
|
|
46
|
+
/** Default open state (uncontrolled) */
|
|
47
|
+
defaultOpen?: boolean;
|
|
48
|
+
/** Called when open state changes */
|
|
49
|
+
onOpenChange?: (open: boolean) => void;
|
|
50
|
+
/** Side of the screen the drawer appears from */
|
|
51
|
+
side?: DrawerSide;
|
|
52
|
+
/** Whether swipe-to-dismiss is enabled */
|
|
53
|
+
swipeDismiss?: boolean;
|
|
54
|
+
/** Whether Escape key closes the drawer */
|
|
55
|
+
closeOnEscape?: boolean;
|
|
56
|
+
/** Whether clicking the overlay closes the drawer */
|
|
57
|
+
closeOnOverlay?: boolean;
|
|
58
|
+
/** Whether to animate open/close transitions */
|
|
59
|
+
animated?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DrawerTriggerProps extends HTMLAttributes<HTMLElement> {
|
|
63
|
+
/** Trigger content (typically a button) */
|
|
64
|
+
children?: ReactNode;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface DrawerContentProps extends HTMLAttributes<HTMLElement> {
|
|
68
|
+
/** Content */
|
|
69
|
+
children?: ReactNode;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface DrawerHeaderProps extends HTMLAttributes<HTMLElement> {
|
|
73
|
+
/** Header content */
|
|
74
|
+
children?: ReactNode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface DrawerFooterProps extends HTMLAttributes<HTMLElement> {
|
|
78
|
+
/** Footer content */
|
|
79
|
+
children?: ReactNode;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface DrawerTitleProps extends HTMLAttributes<HTMLElement> {
|
|
83
|
+
/** Title content */
|
|
84
|
+
children?: ReactNode;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DrawerDescriptionProps extends HTMLAttributes<HTMLElement> {
|
|
88
|
+
/** Description content */
|
|
89
|
+
children?: ReactNode;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Components
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Drawer root component.
|
|
98
|
+
*/
|
|
99
|
+
const DrawerRoot = forwardRef<HTMLElement, DrawerRootProps>(function DrawerRoot(
|
|
100
|
+
{
|
|
101
|
+
children,
|
|
102
|
+
className,
|
|
103
|
+
open: controlledOpen,
|
|
104
|
+
defaultOpen = false,
|
|
105
|
+
onOpenChange,
|
|
106
|
+
side = "bottom",
|
|
107
|
+
swipeDismiss = true,
|
|
108
|
+
closeOnEscape = true,
|
|
109
|
+
closeOnOverlay = true,
|
|
110
|
+
animated = true,
|
|
111
|
+
...props
|
|
112
|
+
},
|
|
113
|
+
ref
|
|
114
|
+
) {
|
|
115
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
116
|
+
const isControlled = controlledOpen !== undefined;
|
|
117
|
+
const open = isControlled ? controlledOpen : internalOpen;
|
|
118
|
+
const elementRef = useRef<HTMLElement>(null);
|
|
119
|
+
|
|
120
|
+
// Combine refs
|
|
121
|
+
const combinedRef = (node: HTMLElement | null) => {
|
|
122
|
+
(elementRef as React.MutableRefObject<HTMLElement | null>).current = node;
|
|
123
|
+
if (typeof ref === "function") {
|
|
124
|
+
ref(node);
|
|
125
|
+
} else if (ref) {
|
|
126
|
+
(ref as React.MutableRefObject<HTMLElement | null>).current = node;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleOpenChange = useCallback(
|
|
131
|
+
(event: Event) => {
|
|
132
|
+
const customEvent = event as CustomEvent;
|
|
133
|
+
const isOpen = customEvent.type === "ds:open";
|
|
134
|
+
|
|
135
|
+
if (!isControlled) {
|
|
136
|
+
setInternalOpen(isOpen);
|
|
137
|
+
}
|
|
138
|
+
onOpenChange?.(isOpen);
|
|
139
|
+
},
|
|
140
|
+
[isControlled, onOpenChange]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Attach event listeners
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const element = elementRef.current;
|
|
146
|
+
if (!element) return;
|
|
147
|
+
|
|
148
|
+
element.addEventListener("ds:open", handleOpenChange);
|
|
149
|
+
element.addEventListener("ds:close", handleOpenChange);
|
|
150
|
+
|
|
151
|
+
return () => {
|
|
152
|
+
element.removeEventListener("ds:open", handleOpenChange);
|
|
153
|
+
element.removeEventListener("ds:close", handleOpenChange);
|
|
154
|
+
};
|
|
155
|
+
}, [handleOpenChange]);
|
|
156
|
+
|
|
157
|
+
return createElement(
|
|
158
|
+
"ds-drawer",
|
|
159
|
+
{
|
|
160
|
+
ref: combinedRef,
|
|
161
|
+
class: className,
|
|
162
|
+
open: open || undefined,
|
|
163
|
+
side,
|
|
164
|
+
"swipe-dismiss": swipeDismiss,
|
|
165
|
+
"close-on-escape": closeOnEscape,
|
|
166
|
+
"close-on-overlay": closeOnOverlay,
|
|
167
|
+
animated: animated || undefined,
|
|
168
|
+
...props,
|
|
169
|
+
},
|
|
170
|
+
children
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
DrawerRoot.displayName = "Drawer.Root";
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Drawer trigger component.
|
|
177
|
+
*/
|
|
178
|
+
const DrawerTrigger = forwardRef<HTMLElement, DrawerTriggerProps>(function DrawerTrigger(
|
|
179
|
+
{ children, className, ...props },
|
|
180
|
+
ref
|
|
181
|
+
) {
|
|
182
|
+
return createElement("div", { ref, className, slot: "trigger", ...props }, children);
|
|
183
|
+
});
|
|
184
|
+
DrawerTrigger.displayName = "Drawer.Trigger";
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Drawer content component.
|
|
188
|
+
*/
|
|
189
|
+
const DrawerContent = forwardRef<HTMLElement, DrawerContentProps>(function DrawerContent(
|
|
190
|
+
{ children, className, ...props },
|
|
191
|
+
ref
|
|
192
|
+
) {
|
|
193
|
+
return createElement("ds-drawer-content", { ref, class: className, ...props }, children);
|
|
194
|
+
});
|
|
195
|
+
DrawerContent.displayName = "Drawer.Content";
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Drawer header component.
|
|
199
|
+
*/
|
|
200
|
+
const DrawerHeader = forwardRef<HTMLElement, DrawerHeaderProps>(function DrawerHeader(
|
|
201
|
+
{ children, className, ...props },
|
|
202
|
+
ref
|
|
203
|
+
) {
|
|
204
|
+
return createElement("ds-drawer-header", { ref, class: className, ...props }, children);
|
|
205
|
+
});
|
|
206
|
+
DrawerHeader.displayName = "Drawer.Header";
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Drawer footer component.
|
|
210
|
+
*/
|
|
211
|
+
const DrawerFooter = forwardRef<HTMLElement, DrawerFooterProps>(function DrawerFooter(
|
|
212
|
+
{ children, className, ...props },
|
|
213
|
+
ref
|
|
214
|
+
) {
|
|
215
|
+
return createElement("ds-drawer-footer", { ref, class: className, ...props }, children);
|
|
216
|
+
});
|
|
217
|
+
DrawerFooter.displayName = "Drawer.Footer";
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Drawer title component.
|
|
221
|
+
*/
|
|
222
|
+
const DrawerTitle = forwardRef<HTMLElement, DrawerTitleProps>(function DrawerTitle(
|
|
223
|
+
{ children, className, ...props },
|
|
224
|
+
ref
|
|
225
|
+
) {
|
|
226
|
+
return createElement("ds-drawer-title", { ref, class: className, ...props }, children);
|
|
227
|
+
});
|
|
228
|
+
DrawerTitle.displayName = "Drawer.Title";
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Drawer description component.
|
|
232
|
+
*/
|
|
233
|
+
const DrawerDescription = forwardRef<HTMLElement, DrawerDescriptionProps>(
|
|
234
|
+
function DrawerDescription({ children, className, ...props }, ref) {
|
|
235
|
+
return createElement("ds-drawer-description", { ref, class: className, ...props }, children);
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
DrawerDescription.displayName = "Drawer.Description";
|
|
239
|
+
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// Compound Component
|
|
242
|
+
// ============================================================================
|
|
243
|
+
|
|
244
|
+
export const Drawer = {
|
|
245
|
+
Root: DrawerRoot,
|
|
246
|
+
Trigger: DrawerTrigger,
|
|
247
|
+
Content: DrawerContent,
|
|
248
|
+
Header: DrawerHeader,
|
|
249
|
+
Footer: DrawerFooter,
|
|
250
|
+
Title: DrawerTitle,
|
|
251
|
+
Description: DrawerDescription,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Also export individual components
|
|
255
|
+
export {
|
|
256
|
+
DrawerRoot,
|
|
257
|
+
DrawerTrigger,
|
|
258
|
+
DrawerContent,
|
|
259
|
+
DrawerHeader,
|
|
260
|
+
DrawerFooter,
|
|
261
|
+
DrawerTitle,
|
|
262
|
+
DrawerDescription,
|
|
263
|
+
};
|