@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,261 @@
|
|
|
1
|
+
import { type RovingFocus, createRovingFocus } from "@hypoth-ui/primitives-dom";
|
|
2
|
+
import { html } from "lit";
|
|
3
|
+
import type { PropertyValues } from "lit";
|
|
4
|
+
import { property } from "lit/decorators.js";
|
|
5
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
6
|
+
import { FormAssociatedMixin } from "../../base/form-associated.js";
|
|
7
|
+
import type { ValidationFlags } from "../../base/form-associated.js";
|
|
8
|
+
import { StandardEvents, emitEvent } from "../../events/emit.js";
|
|
9
|
+
import { define } from "../../registry/define.js";
|
|
10
|
+
import type { DsRadio } from "./radio.js";
|
|
11
|
+
|
|
12
|
+
// Import radio component
|
|
13
|
+
import "./radio.js";
|
|
14
|
+
|
|
15
|
+
export type RadioOrientation = "horizontal" | "vertical";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Container for radio button group with roving tabindex and native form participation.
|
|
19
|
+
*
|
|
20
|
+
* Uses ElementInternals for form association - the selected value is submitted with the form
|
|
21
|
+
* and the group participates in constraint validation.
|
|
22
|
+
*
|
|
23
|
+
* @element ds-radio-group
|
|
24
|
+
* @fires ds:change - Fired when selection changes with { value }
|
|
25
|
+
* @fires ds:invalid - Fired when customValidation is true and validation fails
|
|
26
|
+
*
|
|
27
|
+
* @slot - ds-radio children
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```html
|
|
31
|
+
* <form>
|
|
32
|
+
* <ds-field>
|
|
33
|
+
* <ds-label>Size</ds-label>
|
|
34
|
+
* <ds-radio-group name="size" required>
|
|
35
|
+
* <ds-radio value="sm">Small</ds-radio>
|
|
36
|
+
* <ds-radio value="md">Medium</ds-radio>
|
|
37
|
+
* <ds-radio value="lg">Large</ds-radio>
|
|
38
|
+
* </ds-radio-group>
|
|
39
|
+
* <ds-field-error></ds-field-error>
|
|
40
|
+
* </ds-field>
|
|
41
|
+
* <button type="submit">Submit</button>
|
|
42
|
+
* </form>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export class DsRadioGroup extends FormAssociatedMixin(DSElement) {
|
|
46
|
+
/** Layout and navigation axis */
|
|
47
|
+
@property({ type: String, reflect: true })
|
|
48
|
+
orientation: RadioOrientation = "vertical";
|
|
49
|
+
|
|
50
|
+
/** Default value for form reset */
|
|
51
|
+
private _defaultValue = "";
|
|
52
|
+
|
|
53
|
+
private rovingFocus: RovingFocus | null = null;
|
|
54
|
+
private childObserver: MutationObserver | null = null;
|
|
55
|
+
|
|
56
|
+
override connectedCallback(): void {
|
|
57
|
+
// Store default value for form reset
|
|
58
|
+
this._defaultValue = this.value;
|
|
59
|
+
|
|
60
|
+
super.connectedCallback();
|
|
61
|
+
|
|
62
|
+
// Set role on the group
|
|
63
|
+
this.setAttribute("role", "radiogroup");
|
|
64
|
+
|
|
65
|
+
// Observe child changes
|
|
66
|
+
this.childObserver = new MutationObserver(() => {
|
|
67
|
+
this.setupRadios();
|
|
68
|
+
});
|
|
69
|
+
this.childObserver.observe(this, { childList: true, subtree: true });
|
|
70
|
+
|
|
71
|
+
// Listen for radio selection events
|
|
72
|
+
this.addEventListener("ds:radio-select", this.handleRadioSelect as EventListener);
|
|
73
|
+
|
|
74
|
+
// Setup after first render
|
|
75
|
+
this.updateComplete.then(() => {
|
|
76
|
+
this.setupRadios();
|
|
77
|
+
this.setupRovingFocus();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override disconnectedCallback(): void {
|
|
82
|
+
super.disconnectedCallback();
|
|
83
|
+
this.childObserver?.disconnect();
|
|
84
|
+
this.childObserver = null;
|
|
85
|
+
this.rovingFocus?.destroy();
|
|
86
|
+
this.rovingFocus = null;
|
|
87
|
+
this.removeEventListener("ds:radio-select", this.handleRadioSelect as EventListener);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sets up the radio items with correct checked state and disabled state.
|
|
92
|
+
*/
|
|
93
|
+
private setupRadios(): void {
|
|
94
|
+
const radios = this.getRadioItems();
|
|
95
|
+
|
|
96
|
+
radios.forEach((radio) => {
|
|
97
|
+
// Set checked state based on group value
|
|
98
|
+
radio.setChecked(radio.value === this.value);
|
|
99
|
+
|
|
100
|
+
// Propagate disabled state from group
|
|
101
|
+
if (this.disabled) {
|
|
102
|
+
radio.disabled = true;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Update roving focus tabindex
|
|
107
|
+
this.updateTabIndices();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Updates tabindex values for roving focus.
|
|
112
|
+
*/
|
|
113
|
+
private updateTabIndices(): void {
|
|
114
|
+
const radios = this.getRadioItems();
|
|
115
|
+
const selectedIndex = radios.findIndex((r) => r.value === this.value);
|
|
116
|
+
const focusIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
|
117
|
+
|
|
118
|
+
radios.forEach((radio, index) => {
|
|
119
|
+
radio.setTabIndex(index === focusIndex ? 0 : -1);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sets up roving focus for keyboard navigation.
|
|
125
|
+
*/
|
|
126
|
+
private setupRovingFocus(): void {
|
|
127
|
+
this.rovingFocus?.destroy();
|
|
128
|
+
|
|
129
|
+
const direction = this.orientation === "horizontal" ? "horizontal" : "vertical";
|
|
130
|
+
|
|
131
|
+
this.rovingFocus = createRovingFocus({
|
|
132
|
+
container: this,
|
|
133
|
+
selector: "[role='radio']",
|
|
134
|
+
direction,
|
|
135
|
+
loop: true,
|
|
136
|
+
skipDisabled: true,
|
|
137
|
+
onFocus: (element, _index) => {
|
|
138
|
+
// Find the parent ds-radio and select it (selection follows focus)
|
|
139
|
+
const radio = element.closest("ds-radio") as DsRadio | null;
|
|
140
|
+
if (radio && !radio.disabled && !this.disabled) {
|
|
141
|
+
this.selectValue(radio.value);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets all radio items in the group.
|
|
149
|
+
*/
|
|
150
|
+
private getRadioItems(): DsRadio[] {
|
|
151
|
+
return Array.from(this.querySelectorAll("ds-radio")) as DsRadio[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Handles radio selection from click or keyboard.
|
|
156
|
+
*/
|
|
157
|
+
private handleRadioSelect = (event: CustomEvent<{ value: string }>): void => {
|
|
158
|
+
if (this.disabled) return;
|
|
159
|
+
|
|
160
|
+
const { value } = event.detail;
|
|
161
|
+
this.selectValue(value);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Selects a value and updates state.
|
|
166
|
+
*/
|
|
167
|
+
private selectValue(value: string): void {
|
|
168
|
+
if (value === this.value) return;
|
|
169
|
+
|
|
170
|
+
this.value = value;
|
|
171
|
+
this.setupRadios();
|
|
172
|
+
|
|
173
|
+
emitEvent(this, StandardEvents.CHANGE, {
|
|
174
|
+
detail: { value: this.value },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
override updated(changedProperties: Map<string, unknown>): void {
|
|
179
|
+
super.updated(changedProperties);
|
|
180
|
+
|
|
181
|
+
if (changedProperties.has("value")) {
|
|
182
|
+
this.setupRadios();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (changedProperties.has("orientation")) {
|
|
186
|
+
this.setupRovingFocus();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (changedProperties.has("disabled")) {
|
|
190
|
+
this.setupRadios();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Form association implementation
|
|
195
|
+
|
|
196
|
+
protected getFormValue(): string | null {
|
|
197
|
+
return this.value || null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected getValidationAnchor(): HTMLElement | undefined {
|
|
201
|
+
// Use the first radio control as validation anchor
|
|
202
|
+
return this.querySelector("ds-radio [role='radio']") as HTMLElement | undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
protected getValidationFlags(): ValidationFlags {
|
|
206
|
+
if (this.required && !this.value) {
|
|
207
|
+
return { valueMissing: true };
|
|
208
|
+
}
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected getValidationMessage(flags: ValidationFlags): string {
|
|
213
|
+
if (flags.valueMissing) {
|
|
214
|
+
return "Please select an option";
|
|
215
|
+
}
|
|
216
|
+
return "";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
protected shouldUpdateFormValue(changedProperties: PropertyValues): boolean {
|
|
220
|
+
return changedProperties.has("value");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
protected shouldUpdateValidity(changedProperties: PropertyValues): boolean {
|
|
224
|
+
return changedProperties.has("value");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
protected onFormReset(): void {
|
|
228
|
+
this.value = this._defaultValue;
|
|
229
|
+
this.setupRadios();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
protected onFormStateRestore(
|
|
233
|
+
state: string | File | FormData | null,
|
|
234
|
+
_mode: "restore" | "autocomplete"
|
|
235
|
+
): void {
|
|
236
|
+
if (typeof state === "string") {
|
|
237
|
+
this.value = state;
|
|
238
|
+
this.setupRadios();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
override render() {
|
|
243
|
+
return html`
|
|
244
|
+
<div
|
|
245
|
+
class="ds-radio-group"
|
|
246
|
+
part="container"
|
|
247
|
+
data-orientation=${this.orientation}
|
|
248
|
+
>
|
|
249
|
+
<slot></slot>
|
|
250
|
+
</div>
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
define("ds-radio-group", DsRadioGroup);
|
|
256
|
+
|
|
257
|
+
declare global {
|
|
258
|
+
interface HTMLElementTagNameMap {
|
|
259
|
+
"ds-radio-group": DsRadioGroup;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
4
|
+
import { define } from "../../registry/define.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Individual radio button within a group.
|
|
8
|
+
*
|
|
9
|
+
* @element ds-radio
|
|
10
|
+
*
|
|
11
|
+
* @slot - Radio label
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```html
|
|
15
|
+
* <ds-radio-group name="size">
|
|
16
|
+
* <ds-radio value="sm">Small</ds-radio>
|
|
17
|
+
* <ds-radio value="md">Medium</ds-radio>
|
|
18
|
+
* <ds-radio value="lg">Large</ds-radio>
|
|
19
|
+
* </ds-radio-group>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class DsRadio extends DSElement {
|
|
23
|
+
/** Radio value */
|
|
24
|
+
@property({ type: String, reflect: true })
|
|
25
|
+
value = "";
|
|
26
|
+
|
|
27
|
+
/** Selected state (managed by group) */
|
|
28
|
+
@property({ type: Boolean, reflect: true })
|
|
29
|
+
checked = false;
|
|
30
|
+
|
|
31
|
+
/** Disabled state */
|
|
32
|
+
@property({ type: Boolean, reflect: true })
|
|
33
|
+
disabled = false;
|
|
34
|
+
|
|
35
|
+
/** Label text captured from slot */
|
|
36
|
+
@state()
|
|
37
|
+
private labelText = "";
|
|
38
|
+
|
|
39
|
+
/** Unique ID for label association */
|
|
40
|
+
@state()
|
|
41
|
+
private labelId = "";
|
|
42
|
+
|
|
43
|
+
/** Whether this radio is focusable (managed by group) */
|
|
44
|
+
@state()
|
|
45
|
+
private _tabIndexValue: number | undefined = undefined;
|
|
46
|
+
|
|
47
|
+
get tabIndexValue(): number {
|
|
48
|
+
return this._tabIndexValue ?? -1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override connectedCallback(): void {
|
|
52
|
+
// Capture label text before Lit renders
|
|
53
|
+
this.labelText = this.textContent?.trim() ?? "";
|
|
54
|
+
// Generate unique ID for label
|
|
55
|
+
this.labelId = `radio-label-${crypto.randomUUID().slice(0, 8)}`;
|
|
56
|
+
super.connectedCallback();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sets the checked state (called by RadioGroup).
|
|
61
|
+
*/
|
|
62
|
+
setChecked(checked: boolean): void {
|
|
63
|
+
this.checked = checked;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sets the tabindex (called by RadioGroup for roving focus).
|
|
68
|
+
*/
|
|
69
|
+
setTabIndex(index: number): void {
|
|
70
|
+
this._tabIndexValue = index;
|
|
71
|
+
this.requestUpdate();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Focus the control element.
|
|
76
|
+
*/
|
|
77
|
+
focusControl(): void {
|
|
78
|
+
const control = this.querySelector("[role='radio']") as HTMLElement | null;
|
|
79
|
+
control?.focus();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private handleClick = (event: Event): void => {
|
|
83
|
+
if (this.disabled) return;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
|
|
86
|
+
// Dispatch custom event for parent to handle
|
|
87
|
+
this.dispatchEvent(
|
|
88
|
+
new CustomEvent("ds:radio-select", {
|
|
89
|
+
detail: { value: this.value },
|
|
90
|
+
bubbles: true,
|
|
91
|
+
composed: true,
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
private handleKeyDown = (event: KeyboardEvent): void => {
|
|
97
|
+
// Space key selects the radio
|
|
98
|
+
if (event.key === " " && !this.disabled) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
this.dispatchEvent(
|
|
101
|
+
new CustomEvent("ds:radio-select", {
|
|
102
|
+
detail: { value: this.value },
|
|
103
|
+
bubbles: true,
|
|
104
|
+
composed: true,
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
override render() {
|
|
111
|
+
return html`
|
|
112
|
+
<div
|
|
113
|
+
class="ds-radio"
|
|
114
|
+
part="container"
|
|
115
|
+
@click=${this.handleClick}
|
|
116
|
+
>
|
|
117
|
+
<div
|
|
118
|
+
role="radio"
|
|
119
|
+
part="control"
|
|
120
|
+
class="ds-radio__control"
|
|
121
|
+
.tabIndex=${this.tabIndexValue}
|
|
122
|
+
aria-checked=${this.checked ? "true" : "false"}
|
|
123
|
+
aria-disabled=${this.disabled ? "true" : "false"}
|
|
124
|
+
aria-labelledby=${this.labelId}
|
|
125
|
+
@keydown=${this.handleKeyDown}
|
|
126
|
+
>
|
|
127
|
+
<span class="ds-radio__indicator" part="indicator">
|
|
128
|
+
${this.checked ? html`<span class="ds-radio__dot"></span>` : null}
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
<span id=${this.labelId} class="ds-radio__label" part="label">
|
|
132
|
+
${this.labelText}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
define("ds-radio", DsRadio);
|
|
140
|
+
|
|
141
|
+
declare global {
|
|
142
|
+
interface HTMLElementTagNameMap {
|
|
143
|
+
"ds-radio": DsRadio;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollArea compound component exports.
|
|
3
|
+
*
|
|
4
|
+
* ScrollArea provides custom styled scrollbars for content containers.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { ScrollArea } from "@/components/ui";
|
|
9
|
+
*
|
|
10
|
+
* <ScrollArea.Root style={{ height: "200px" }}>
|
|
11
|
+
* <ScrollArea.Viewport>
|
|
12
|
+
* <!-- Long content here -->
|
|
13
|
+
* </ScrollArea.Viewport>
|
|
14
|
+
* <ScrollArea.Scrollbar orientation="vertical">
|
|
15
|
+
* <ScrollArea.Thumb />
|
|
16
|
+
* </ScrollArea.Scrollbar>
|
|
17
|
+
* </ScrollArea.Root>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
type CSSProperties,
|
|
23
|
+
type HTMLAttributes,
|
|
24
|
+
type ReactNode,
|
|
25
|
+
createElement,
|
|
26
|
+
forwardRef,
|
|
27
|
+
} from "react";
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Types
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
export type ScrollAreaType = "auto" | "always" | "scroll" | "hover";
|
|
34
|
+
|
|
35
|
+
export interface ScrollAreaRootProps extends HTMLAttributes<HTMLElement> {
|
|
36
|
+
/** Content */
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
/** When scrollbars are visible */
|
|
39
|
+
type?: ScrollAreaType;
|
|
40
|
+
/** Scroll hide delay in ms (for hover type) */
|
|
41
|
+
scrollHideDelay?: number;
|
|
42
|
+
/** Orientation for which to show scrollbar */
|
|
43
|
+
orientation?: "vertical" | "horizontal" | "both";
|
|
44
|
+
/** Custom styles */
|
|
45
|
+
style?: CSSProperties;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ScrollAreaViewportProps extends HTMLAttributes<HTMLElement> {
|
|
49
|
+
/** Scrollable content */
|
|
50
|
+
children?: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ScrollAreaScrollbarProps extends HTMLAttributes<HTMLElement> {
|
|
54
|
+
/** Scrollbar content (typically a thumb) */
|
|
55
|
+
children?: ReactNode;
|
|
56
|
+
/** Scrollbar orientation */
|
|
57
|
+
orientation?: "vertical" | "horizontal";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ScrollAreaThumbProps extends HTMLAttributes<HTMLElement> {}
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Components
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ScrollArea root component.
|
|
68
|
+
*/
|
|
69
|
+
const ScrollAreaRoot = forwardRef<HTMLElement, ScrollAreaRootProps>(function ScrollAreaRoot(
|
|
70
|
+
{
|
|
71
|
+
children,
|
|
72
|
+
className,
|
|
73
|
+
type = "hover",
|
|
74
|
+
scrollHideDelay = 600,
|
|
75
|
+
orientation = "vertical",
|
|
76
|
+
style,
|
|
77
|
+
...props
|
|
78
|
+
},
|
|
79
|
+
ref
|
|
80
|
+
) {
|
|
81
|
+
return createElement(
|
|
82
|
+
"ds-scroll-area",
|
|
83
|
+
{
|
|
84
|
+
ref,
|
|
85
|
+
class: className,
|
|
86
|
+
type,
|
|
87
|
+
"scroll-hide-delay": scrollHideDelay,
|
|
88
|
+
orientation,
|
|
89
|
+
style,
|
|
90
|
+
...props,
|
|
91
|
+
},
|
|
92
|
+
children
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
ScrollAreaRoot.displayName = "ScrollArea.Root";
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* ScrollArea viewport component.
|
|
99
|
+
*/
|
|
100
|
+
const ScrollAreaViewport = forwardRef<HTMLElement, ScrollAreaViewportProps>(
|
|
101
|
+
function ScrollAreaViewport({ children, className, ...props }, ref) {
|
|
102
|
+
return createElement("ds-scroll-area-viewport", { ref, class: className, ...props }, children);
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
ScrollAreaViewport.displayName = "ScrollArea.Viewport";
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* ScrollArea scrollbar component.
|
|
109
|
+
*/
|
|
110
|
+
const ScrollAreaScrollbar = forwardRef<HTMLElement, ScrollAreaScrollbarProps>(
|
|
111
|
+
function ScrollAreaScrollbar({ children, className, orientation = "vertical", ...props }, ref) {
|
|
112
|
+
return createElement(
|
|
113
|
+
"ds-scroll-area-scrollbar",
|
|
114
|
+
{ ref, class: className, orientation, ...props },
|
|
115
|
+
children
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
ScrollAreaScrollbar.displayName = "ScrollArea.Scrollbar";
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* ScrollArea thumb component.
|
|
123
|
+
*/
|
|
124
|
+
const ScrollAreaThumb = forwardRef<HTMLElement, ScrollAreaThumbProps>(function ScrollAreaThumb(
|
|
125
|
+
{ className, ...props },
|
|
126
|
+
ref
|
|
127
|
+
) {
|
|
128
|
+
return createElement("ds-scroll-area-thumb", { ref, class: className, ...props });
|
|
129
|
+
});
|
|
130
|
+
ScrollAreaThumb.displayName = "ScrollArea.Thumb";
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Compound Component
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
export const ScrollArea = {
|
|
137
|
+
Root: ScrollAreaRoot,
|
|
138
|
+
Viewport: ScrollAreaViewport,
|
|
139
|
+
Scrollbar: ScrollAreaScrollbar,
|
|
140
|
+
Thumb: ScrollAreaThumb,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Also export individual components
|
|
144
|
+
export { ScrollAreaRoot, ScrollAreaViewport, ScrollAreaScrollbar, ScrollAreaThumb };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollArea component exports.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { DsScrollArea, type ScrollAreaType } from "./scroll-area.js";
|
|
6
|
+
export { DsScrollAreaViewport } from "./scroll-area-viewport.js";
|
|
7
|
+
export { DsScrollAreaScrollbar } from "./scroll-area-scrollbar.js";
|
|
8
|
+
export { DsScrollAreaThumb } from "./scroll-area-thumb.js";
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollAreaScrollbar component - scrollbar track.
|
|
3
|
+
*
|
|
4
|
+
* @element ds-scroll-area-scrollbar
|
|
5
|
+
*
|
|
6
|
+
* @slot - Scrollbar thumb
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { html } from "lit";
|
|
10
|
+
import { property } from "lit/decorators.js";
|
|
11
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
12
|
+
import { define } from "../../registry/define.js";
|
|
13
|
+
|
|
14
|
+
export class DsScrollAreaScrollbar extends DSElement {
|
|
15
|
+
/** Scrollbar orientation */
|
|
16
|
+
@property({ type: String, reflect: true })
|
|
17
|
+
orientation: "vertical" | "horizontal" = "vertical";
|
|
18
|
+
|
|
19
|
+
private hideTimer: ReturnType<typeof setTimeout> | null = null;
|
|
20
|
+
private isHovered = false;
|
|
21
|
+
|
|
22
|
+
override connectedCallback(): void {
|
|
23
|
+
super.connectedCallback();
|
|
24
|
+
|
|
25
|
+
this.setAttribute("role", "scrollbar");
|
|
26
|
+
this.setAttribute("aria-orientation", this.orientation);
|
|
27
|
+
|
|
28
|
+
// Handle track click
|
|
29
|
+
this.addEventListener("mousedown", this.handleTrackClick);
|
|
30
|
+
this.addEventListener("mouseenter", this.handleMouseEnter);
|
|
31
|
+
this.addEventListener("mouseleave", this.handleMouseLeave);
|
|
32
|
+
|
|
33
|
+
// Listen for viewport scroll
|
|
34
|
+
const scrollArea = this.closest("ds-scroll-area");
|
|
35
|
+
const viewport = scrollArea?.querySelector("ds-scroll-area-viewport");
|
|
36
|
+
if (viewport) {
|
|
37
|
+
viewport.addEventListener("scroll", this.handleViewportScroll);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override disconnectedCallback(): void {
|
|
42
|
+
super.disconnectedCallback();
|
|
43
|
+
this.removeEventListener("mousedown", this.handleTrackClick);
|
|
44
|
+
this.removeEventListener("mouseenter", this.handleMouseEnter);
|
|
45
|
+
this.removeEventListener("mouseleave", this.handleMouseLeave);
|
|
46
|
+
|
|
47
|
+
if (this.hideTimer) {
|
|
48
|
+
clearTimeout(this.hideTimer);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const scrollArea = this.closest("ds-scroll-area");
|
|
52
|
+
const viewport = scrollArea?.querySelector("ds-scroll-area-viewport");
|
|
53
|
+
if (viewport) {
|
|
54
|
+
viewport.removeEventListener("scroll", this.handleViewportScroll);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private handleTrackClick = (event: MouseEvent): void => {
|
|
59
|
+
// Ignore if clicking on thumb
|
|
60
|
+
const thumb = event.target as HTMLElement;
|
|
61
|
+
if (thumb.tagName === "DS-SCROLL-AREA-THUMB") return;
|
|
62
|
+
|
|
63
|
+
const scrollArea = this.closest("ds-scroll-area");
|
|
64
|
+
const viewport = scrollArea?.querySelector("ds-scroll-area-viewport");
|
|
65
|
+
if (!viewport) return;
|
|
66
|
+
|
|
67
|
+
const rect = this.getBoundingClientRect();
|
|
68
|
+
|
|
69
|
+
if (this.orientation === "vertical") {
|
|
70
|
+
const clickRatio = (event.clientY - rect.top) / rect.height;
|
|
71
|
+
const scrollHeight = viewport.scrollHeight - viewport.clientHeight;
|
|
72
|
+
viewport.scrollTo({
|
|
73
|
+
top: clickRatio * scrollHeight,
|
|
74
|
+
behavior: "smooth",
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
const clickRatio = (event.clientX - rect.left) / rect.width;
|
|
78
|
+
const scrollWidth = viewport.scrollWidth - viewport.clientWidth;
|
|
79
|
+
viewport.scrollTo({
|
|
80
|
+
left: clickRatio * scrollWidth,
|
|
81
|
+
behavior: "smooth",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
private handleViewportScroll = (): void => {
|
|
87
|
+
// Show scrollbar
|
|
88
|
+
this.setAttribute("data-state", "visible");
|
|
89
|
+
|
|
90
|
+
// Schedule hide (for hover type)
|
|
91
|
+
this.scheduleHide();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
private handleMouseEnter = (): void => {
|
|
95
|
+
this.isHovered = true;
|
|
96
|
+
if (this.hideTimer) {
|
|
97
|
+
clearTimeout(this.hideTimer);
|
|
98
|
+
this.hideTimer = null;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
private handleMouseLeave = (): void => {
|
|
103
|
+
this.isHovered = false;
|
|
104
|
+
this.scheduleHide();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
private scheduleHide(): void {
|
|
108
|
+
const scrollArea = this.closest("ds-scroll-area");
|
|
109
|
+
const type = scrollArea?.getAttribute("type") || "hover";
|
|
110
|
+
|
|
111
|
+
if (type !== "hover") return;
|
|
112
|
+
|
|
113
|
+
if (this.hideTimer) {
|
|
114
|
+
clearTimeout(this.hideTimer);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const delay = Number(scrollArea?.getAttribute("scroll-hide-delay")) || 600;
|
|
118
|
+
|
|
119
|
+
this.hideTimer = setTimeout(() => {
|
|
120
|
+
if (!this.isHovered) {
|
|
121
|
+
this.setAttribute("data-state", "hidden");
|
|
122
|
+
}
|
|
123
|
+
}, delay);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override updated(changedProperties: Map<string, unknown>): void {
|
|
127
|
+
if (changedProperties.has("orientation")) {
|
|
128
|
+
this.setAttribute("aria-orientation", this.orientation);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override render() {
|
|
133
|
+
return html`<slot></slot>`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
define("ds-scroll-area-scrollbar", DsScrollAreaScrollbar);
|
|
138
|
+
|
|
139
|
+
declare global {
|
|
140
|
+
interface HTMLElementTagNameMap {
|
|
141
|
+
"ds-scroll-area-scrollbar": DsScrollAreaScrollbar;
|
|
142
|
+
}
|
|
143
|
+
}
|