@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,138 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type HTMLAttributes,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
createElement,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
} from "react";
|
|
11
|
+
import "@hypoth-ui/wc";
|
|
12
|
+
|
|
13
|
+
export type TreeSelectionMode = "single" | "multiple" | "none";
|
|
14
|
+
export type TreeSize = "default" | "compact";
|
|
15
|
+
|
|
16
|
+
export interface TreeRootProps extends HTMLAttributes<HTMLElement> {
|
|
17
|
+
/**
|
|
18
|
+
* Selection mode.
|
|
19
|
+
* @default "single"
|
|
20
|
+
*/
|
|
21
|
+
selectionMode?: TreeSelectionMode;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Size variant.
|
|
25
|
+
* @default "default"
|
|
26
|
+
*/
|
|
27
|
+
size?: TreeSize;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Show connecting lines.
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
lines?: boolean;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Accessible label.
|
|
37
|
+
* @default "Tree"
|
|
38
|
+
*/
|
|
39
|
+
label?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether the tree is in a loading state.
|
|
43
|
+
* When true, sets aria-busy and disables keyboard navigation.
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
loading?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Text to display/announce during loading.
|
|
50
|
+
* @default "Loading..."
|
|
51
|
+
*/
|
|
52
|
+
loadingText?: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Node IDs that are currently loading children.
|
|
56
|
+
* Allows for node-level loading indicators.
|
|
57
|
+
*/
|
|
58
|
+
loadingNodes?: string[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Callback when selection changes.
|
|
62
|
+
*/
|
|
63
|
+
onSelectionChange?: (selectedItems: string[]) => void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Tree content (TreeItem elements).
|
|
67
|
+
*/
|
|
68
|
+
children?: ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Tree root component for hierarchical data display.
|
|
73
|
+
*/
|
|
74
|
+
export const TreeRoot = forwardRef<HTMLElement, TreeRootProps>(function TreeRoot(
|
|
75
|
+
{
|
|
76
|
+
selectionMode = "single",
|
|
77
|
+
size = "default",
|
|
78
|
+
lines = false,
|
|
79
|
+
label = "Tree",
|
|
80
|
+
loading = false,
|
|
81
|
+
loadingText = "Loading...",
|
|
82
|
+
loadingNodes,
|
|
83
|
+
onSelectionChange,
|
|
84
|
+
children,
|
|
85
|
+
className,
|
|
86
|
+
...props
|
|
87
|
+
},
|
|
88
|
+
forwardedRef
|
|
89
|
+
) {
|
|
90
|
+
const internalRef = useRef<HTMLElement>(null);
|
|
91
|
+
|
|
92
|
+
// Sync forwarded ref
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (typeof forwardedRef === "function") {
|
|
95
|
+
forwardedRef(internalRef.current);
|
|
96
|
+
} else if (forwardedRef) {
|
|
97
|
+
(forwardedRef as React.MutableRefObject<HTMLElement | null>).current = internalRef.current;
|
|
98
|
+
}
|
|
99
|
+
}, [forwardedRef]);
|
|
100
|
+
|
|
101
|
+
// Set up event listeners
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const element = internalRef.current;
|
|
104
|
+
if (!element) return;
|
|
105
|
+
|
|
106
|
+
const handleSelectionChange = (e: Event) => {
|
|
107
|
+
const event = e as CustomEvent<{ selectedItems: string[] }>;
|
|
108
|
+
onSelectionChange?.(event.detail.selectedItems);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
element.addEventListener("ds:selection-change", handleSelectionChange);
|
|
112
|
+
return () => element.removeEventListener("ds:selection-change", handleSelectionChange);
|
|
113
|
+
}, [onSelectionChange]);
|
|
114
|
+
|
|
115
|
+
// Sync loadingNodes as a property (cannot be set via attribute)
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const element = internalRef.current as HTMLElement & { loadingNodes?: Set<string> | string[] } | null;
|
|
118
|
+
if (element && loadingNodes !== undefined) {
|
|
119
|
+
element.loadingNodes = loadingNodes;
|
|
120
|
+
}
|
|
121
|
+
}, [loadingNodes]);
|
|
122
|
+
|
|
123
|
+
return createElement(
|
|
124
|
+
"ds-tree",
|
|
125
|
+
{
|
|
126
|
+
ref: internalRef,
|
|
127
|
+
"selection-mode": selectionMode,
|
|
128
|
+
size,
|
|
129
|
+
lines: lines || undefined,
|
|
130
|
+
label,
|
|
131
|
+
loading: loading || undefined,
|
|
132
|
+
"loading-text": loadingText,
|
|
133
|
+
class: className,
|
|
134
|
+
...props,
|
|
135
|
+
},
|
|
136
|
+
children
|
|
137
|
+
);
|
|
138
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree component exports
|
|
3
|
+
*/
|
|
4
|
+
export { DsTree, type TreeSelectionMode, type TreeSize } from "./tree.js";
|
|
5
|
+
export { DsTreeItem } from "./tree-item.js";
|
|
6
|
+
export {
|
|
7
|
+
calculateTreeItemPosition,
|
|
8
|
+
updateTreeItemAriaAttributes,
|
|
9
|
+
updateAllTreeItemAriaAttributes,
|
|
10
|
+
type TreeItemPosition,
|
|
11
|
+
} from "./tree-utils.js";
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { type TemplateResult, html, nothing } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
4
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
5
|
+
import { emitEvent } from "../../events/emit.js";
|
|
6
|
+
import { define } from "../../registry/define.js";
|
|
7
|
+
import type { DsTree } from "./tree.js";
|
|
8
|
+
import { calculateTreeItemPosition } from "./tree-utils.js";
|
|
9
|
+
|
|
10
|
+
// Chevron icon
|
|
11
|
+
const chevronIcon = html`
|
|
12
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
13
|
+
<path d="M9 18l6-6-6-6" />
|
|
14
|
+
</svg>
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tree item component for hierarchical nodes.
|
|
19
|
+
*
|
|
20
|
+
* @element ds-tree-item
|
|
21
|
+
*
|
|
22
|
+
* @slot - Item content (label)
|
|
23
|
+
* @slot icon - Optional leading icon
|
|
24
|
+
* @slot children - Nested TreeItem elements
|
|
25
|
+
*
|
|
26
|
+
* @fires ds-expand - When expand state changes
|
|
27
|
+
* @fires ds-select - When item is selected
|
|
28
|
+
* @fires ds-activate - When item is activated (Enter/double-click)
|
|
29
|
+
*/
|
|
30
|
+
export class DsTreeItem extends DSElement {
|
|
31
|
+
static override styles = [];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unique item ID.
|
|
35
|
+
*/
|
|
36
|
+
@property({ type: String, attribute: "item-id" })
|
|
37
|
+
itemId = "";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether item is expanded.
|
|
41
|
+
*/
|
|
42
|
+
@property({ type: Boolean, reflect: true })
|
|
43
|
+
expanded = false;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether item is selected.
|
|
47
|
+
*/
|
|
48
|
+
@property({ type: Boolean, reflect: true })
|
|
49
|
+
selected = false;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether item is disabled.
|
|
53
|
+
*/
|
|
54
|
+
@property({ type: Boolean, reflect: true })
|
|
55
|
+
disabled = false;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether this item is loading (e.g., fetching children).
|
|
59
|
+
* Can be set directly or inherited from tree's loadingNodes.
|
|
60
|
+
*/
|
|
61
|
+
@property({ type: Boolean, reflect: true })
|
|
62
|
+
loading = false;
|
|
63
|
+
|
|
64
|
+
@state()
|
|
65
|
+
private hasChildren = false;
|
|
66
|
+
|
|
67
|
+
@state()
|
|
68
|
+
private _level = 1;
|
|
69
|
+
|
|
70
|
+
@state()
|
|
71
|
+
private _setSize = 1;
|
|
72
|
+
|
|
73
|
+
@state()
|
|
74
|
+
private _posInSet = 1;
|
|
75
|
+
|
|
76
|
+
private get treeRoot(): DsTree | null {
|
|
77
|
+
return this.closest("ds-tree") as DsTree | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns true if this item is loading (either directly or via tree's loadingNodes).
|
|
82
|
+
*/
|
|
83
|
+
private get isLoading(): boolean {
|
|
84
|
+
if (this.loading) return true;
|
|
85
|
+
const tree = this.treeRoot;
|
|
86
|
+
if (tree && this.itemId) {
|
|
87
|
+
return tree.isNodeLoading(this.itemId);
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns true if the tree root is in a loading state.
|
|
94
|
+
*/
|
|
95
|
+
private get isTreeLoading(): boolean {
|
|
96
|
+
return this.treeRoot?.loading ?? false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override connectedCallback(): void {
|
|
100
|
+
super.connectedCallback();
|
|
101
|
+
this.updateHasChildren();
|
|
102
|
+
// Calculate position after DOM is ready
|
|
103
|
+
requestAnimationFrame(() => {
|
|
104
|
+
this.updateAriaPosition();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Update ARIA position attributes for APG compliance.
|
|
110
|
+
* Provides screen readers with context like "Item 3 of 5, level 2".
|
|
111
|
+
*/
|
|
112
|
+
private updateAriaPosition(): void {
|
|
113
|
+
const position = calculateTreeItemPosition(this as unknown as HTMLElement);
|
|
114
|
+
this._level = position.level;
|
|
115
|
+
this._setSize = position.setSize;
|
|
116
|
+
this._posInSet = position.posInSet;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private updateHasChildren(): void {
|
|
120
|
+
const childrenSlot = this.querySelector("[slot=children]");
|
|
121
|
+
this.hasChildren = childrenSlot !== null && childrenSlot.querySelector("ds-tree-item") !== null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private handleExpandClick(event: Event): void {
|
|
125
|
+
event.stopPropagation();
|
|
126
|
+
if (this.disabled || this.isTreeLoading || this.isLoading || !this.hasChildren) return;
|
|
127
|
+
|
|
128
|
+
this.expanded = !this.expanded;
|
|
129
|
+
emitEvent(this, "expand", {
|
|
130
|
+
detail: {
|
|
131
|
+
itemId: this.itemId,
|
|
132
|
+
expanded: this.expanded,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private handleContentClick(): void {
|
|
138
|
+
if (this.disabled || this.isTreeLoading) return;
|
|
139
|
+
|
|
140
|
+
const tree = this.treeRoot;
|
|
141
|
+
if (tree) {
|
|
142
|
+
tree.handleItemSelect(this.itemId);
|
|
143
|
+
this.selected = tree.isItemSelected(this.itemId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
emitEvent(this, "select", {
|
|
147
|
+
detail: {
|
|
148
|
+
itemId: this.itemId,
|
|
149
|
+
selected: this.selected,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private handleKeyDown(event: KeyboardEvent): void {
|
|
155
|
+
if (this.disabled || this.isTreeLoading) return;
|
|
156
|
+
|
|
157
|
+
switch (event.key) {
|
|
158
|
+
case "ArrowRight":
|
|
159
|
+
if (this.hasChildren && !this.expanded) {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
this.expanded = true;
|
|
162
|
+
emitEvent(this, "expand", {
|
|
163
|
+
detail: {
|
|
164
|
+
itemId: this.itemId,
|
|
165
|
+
expanded: true,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case "ArrowLeft":
|
|
172
|
+
if (this.hasChildren && this.expanded) {
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
this.expanded = false;
|
|
175
|
+
emitEvent(this, "expand", {
|
|
176
|
+
detail: {
|
|
177
|
+
itemId: this.itemId,
|
|
178
|
+
expanded: false,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case "Enter":
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
emitEvent(this, "activate", { detail: { itemId: this.itemId } });
|
|
187
|
+
break;
|
|
188
|
+
|
|
189
|
+
case " ":
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
this.handleContentClick();
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
override render(): TemplateResult {
|
|
197
|
+
const classes = {
|
|
198
|
+
"ds-tree-item": true,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Check for children via slot
|
|
202
|
+
this.updateHasChildren();
|
|
203
|
+
|
|
204
|
+
const itemLoading = this.isLoading;
|
|
205
|
+
|
|
206
|
+
return html`
|
|
207
|
+
<li
|
|
208
|
+
class=${classMap(classes)}
|
|
209
|
+
role="treeitem"
|
|
210
|
+
aria-expanded=${this.hasChildren ? (this.expanded ? "true" : "false") : nothing}
|
|
211
|
+
aria-selected=${this.selected ? "true" : "false"}
|
|
212
|
+
aria-busy=${itemLoading ? "true" : nothing}
|
|
213
|
+
aria-level=${this._level}
|
|
214
|
+
aria-setsize=${this._setSize}
|
|
215
|
+
aria-posinset=${this._posInSet}
|
|
216
|
+
?data-expanded=${this.expanded}
|
|
217
|
+
?data-selected=${this.selected}
|
|
218
|
+
?data-disabled=${this.disabled}
|
|
219
|
+
?data-loading=${itemLoading}
|
|
220
|
+
>
|
|
221
|
+
<div
|
|
222
|
+
class="ds-tree-item__content"
|
|
223
|
+
tabindex=${this.disabled ? -1 : 0}
|
|
224
|
+
@click=${this.handleContentClick}
|
|
225
|
+
@keydown=${this.handleKeyDown}
|
|
226
|
+
>
|
|
227
|
+
${
|
|
228
|
+
this.hasChildren
|
|
229
|
+
? html`
|
|
230
|
+
<button
|
|
231
|
+
type="button"
|
|
232
|
+
class="ds-tree-item__expand"
|
|
233
|
+
aria-label=${this.expanded ? "Collapse" : "Expand"}
|
|
234
|
+
tabindex="-1"
|
|
235
|
+
@click=${this.handleExpandClick}
|
|
236
|
+
>
|
|
237
|
+
${chevronIcon}
|
|
238
|
+
</button>
|
|
239
|
+
`
|
|
240
|
+
: html`<span class="ds-tree-item__spacer"></span>`
|
|
241
|
+
}
|
|
242
|
+
<slot name="icon"></slot>
|
|
243
|
+
<span class="ds-tree-item__label">
|
|
244
|
+
<slot></slot>
|
|
245
|
+
</span>
|
|
246
|
+
</div>
|
|
247
|
+
${
|
|
248
|
+
this.hasChildren
|
|
249
|
+
? html`
|
|
250
|
+
<ul
|
|
251
|
+
class="ds-tree-item__children ds-tree"
|
|
252
|
+
role="group"
|
|
253
|
+
?hidden=${!this.expanded}
|
|
254
|
+
>
|
|
255
|
+
<slot name="children" @slotchange=${this.updateHasChildren}></slot>
|
|
256
|
+
</ul>
|
|
257
|
+
`
|
|
258
|
+
: nothing
|
|
259
|
+
}
|
|
260
|
+
</li>
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Register the component
|
|
266
|
+
define("ds-tree-item", DsTreeItem);
|
|
267
|
+
|
|
268
|
+
// TypeScript declaration for HTML
|
|
269
|
+
declare global {
|
|
270
|
+
interface HTMLElementTagNameMap {
|
|
271
|
+
"ds-tree-item": DsTreeItem;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree structure utilities for APG-compliant ARIA attributes.
|
|
3
|
+
*
|
|
4
|
+
* Calculates aria-level, aria-setsize, and aria-posinset for tree items
|
|
5
|
+
* to provide proper screen reader context (e.g., "Item 3 of 5, level 2").
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface TreeItemPosition {
|
|
9
|
+
/** Nesting depth (1-based, root items are level 1) */
|
|
10
|
+
level: number;
|
|
11
|
+
/** Number of siblings at this level */
|
|
12
|
+
setSize: number;
|
|
13
|
+
/** Position among siblings (1-based) */
|
|
14
|
+
posInSet: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculate position attributes for a tree item element.
|
|
19
|
+
*
|
|
20
|
+
* @param element - The ds-tree-item element
|
|
21
|
+
* @returns Position info for aria-level, aria-setsize, aria-posinset
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const position = calculateTreeItemPosition(treeItemElement);
|
|
26
|
+
* // { level: 2, setSize: 3, posInSet: 1 }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function calculateTreeItemPosition(element: HTMLElement): TreeItemPosition {
|
|
30
|
+
// Calculate level by counting ancestor tree items
|
|
31
|
+
let level = 1;
|
|
32
|
+
let parent: HTMLElement | null = element.parentElement;
|
|
33
|
+
|
|
34
|
+
while (parent) {
|
|
35
|
+
// Check if parent is a tree item (indicates nested level)
|
|
36
|
+
if (parent.tagName?.toLowerCase() === "ds-tree-item") {
|
|
37
|
+
level++;
|
|
38
|
+
}
|
|
39
|
+
// Stop at tree root
|
|
40
|
+
if (parent.tagName?.toLowerCase() === "ds-tree") {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
parent = parent.parentElement;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Find siblings at the same level
|
|
47
|
+
const siblings = getSiblingTreeItems(element);
|
|
48
|
+
const setSize = siblings.length;
|
|
49
|
+
const posInSet = siblings.indexOf(element) + 1;
|
|
50
|
+
|
|
51
|
+
return { level, setSize, posInSet };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get sibling tree items at the same level.
|
|
56
|
+
*
|
|
57
|
+
* @param element - The ds-tree-item element
|
|
58
|
+
* @returns Array of sibling tree item elements
|
|
59
|
+
*/
|
|
60
|
+
function getSiblingTreeItems(element: HTMLElement): HTMLElement[] {
|
|
61
|
+
// Find the container (either tree root or children slot)
|
|
62
|
+
let container: HTMLElement | null = element.parentElement;
|
|
63
|
+
|
|
64
|
+
// If parent is a slot, get the slot's parent
|
|
65
|
+
if (container?.tagName?.toLowerCase() === "slot") {
|
|
66
|
+
container = container.parentElement;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If parent is a ul with role="group", get its parent (the tree-item)
|
|
70
|
+
// and then find the children slot
|
|
71
|
+
if (container?.getAttribute("role") === "group") {
|
|
72
|
+
container = container.parentElement;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!container) {
|
|
76
|
+
return [element];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Query for direct tree-item children
|
|
80
|
+
// This handles both root level (ds-tree > ds-tree-item)
|
|
81
|
+
// and nested level (ds-tree-item [slot=children] > ds-tree-item)
|
|
82
|
+
const items: HTMLElement[] = [];
|
|
83
|
+
|
|
84
|
+
// Check direct children and slotted children
|
|
85
|
+
const checkChildren = (parent: HTMLElement) => {
|
|
86
|
+
for (const child of parent.children) {
|
|
87
|
+
if (child.tagName?.toLowerCase() === "ds-tree-item") {
|
|
88
|
+
items.push(child as HTMLElement);
|
|
89
|
+
} else if (child.tagName?.toLowerCase() === "slot") {
|
|
90
|
+
// Get assigned elements from slot
|
|
91
|
+
const slot = child as HTMLSlotElement;
|
|
92
|
+
for (const assigned of slot.assignedElements()) {
|
|
93
|
+
if (assigned.tagName?.toLowerCase() === "ds-tree-item") {
|
|
94
|
+
items.push(assigned as HTMLElement);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// For root tree, check its direct children
|
|
102
|
+
if (container.tagName?.toLowerCase() === "ds-tree") {
|
|
103
|
+
checkChildren(container);
|
|
104
|
+
} else if (container.tagName?.toLowerCase() === "ds-tree-item") {
|
|
105
|
+
// For nested items, find the children slot container
|
|
106
|
+
const childrenSlot = container.querySelector('[slot="children"]');
|
|
107
|
+
if (childrenSlot) {
|
|
108
|
+
for (const child of childrenSlot.children) {
|
|
109
|
+
if (child.tagName?.toLowerCase() === "ds-tree-item") {
|
|
110
|
+
items.push(child as HTMLElement);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// If no items found via DOM traversal, fall back to the element itself
|
|
117
|
+
return items.length > 0 ? items : [element];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Update ARIA position attributes on a tree item element.
|
|
122
|
+
*
|
|
123
|
+
* @param element - The ds-tree-item element to update
|
|
124
|
+
*/
|
|
125
|
+
export function updateTreeItemAriaAttributes(element: HTMLElement): void {
|
|
126
|
+
const position = calculateTreeItemPosition(element);
|
|
127
|
+
|
|
128
|
+
element.setAttribute("aria-level", String(position.level));
|
|
129
|
+
element.setAttribute("aria-setsize", String(position.setSize));
|
|
130
|
+
element.setAttribute("aria-posinset", String(position.posInSet));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update ARIA position attributes on all tree items within a tree.
|
|
135
|
+
*
|
|
136
|
+
* @param treeRoot - The ds-tree root element
|
|
137
|
+
*/
|
|
138
|
+
export function updateAllTreeItemAriaAttributes(treeRoot: HTMLElement): void {
|
|
139
|
+
const items = treeRoot.querySelectorAll("ds-tree-item");
|
|
140
|
+
for (const item of items) {
|
|
141
|
+
updateTreeItemAriaAttributes(item as unknown as HTMLElement);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { type TemplateResult, html, nothing } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
4
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
5
|
+
import { emitEvent } from "../../events/emit.js";
|
|
6
|
+
import { define } from "../../registry/define.js";
|
|
7
|
+
|
|
8
|
+
export type TreeSelectionMode = "single" | "multiple" | "none";
|
|
9
|
+
export type TreeSize = "default" | "compact";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tree root component for hierarchical data display.
|
|
13
|
+
*
|
|
14
|
+
* @element ds-tree
|
|
15
|
+
*
|
|
16
|
+
* @slot - TreeItem elements
|
|
17
|
+
*
|
|
18
|
+
* @fires ds-selection-change - When selection changes
|
|
19
|
+
*
|
|
20
|
+
* @cssprop --ds-tree-indent - Indentation for nested items
|
|
21
|
+
*/
|
|
22
|
+
export class DsTree extends DSElement {
|
|
23
|
+
static override styles = [];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Selection mode.
|
|
27
|
+
*/
|
|
28
|
+
@property({ type: String, attribute: "selection-mode", reflect: true })
|
|
29
|
+
selectionMode: TreeSelectionMode = "single";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Size variant.
|
|
33
|
+
*/
|
|
34
|
+
@property({ type: String, reflect: true })
|
|
35
|
+
size: TreeSize = "default";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Show connecting lines.
|
|
39
|
+
*/
|
|
40
|
+
@property({ type: Boolean, reflect: true })
|
|
41
|
+
lines = false;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Accessible label.
|
|
45
|
+
*/
|
|
46
|
+
@property({ type: String })
|
|
47
|
+
label = "Tree";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether the tree is in a loading state.
|
|
51
|
+
* When true, sets aria-busy and disables keyboard navigation.
|
|
52
|
+
*/
|
|
53
|
+
@property({ type: Boolean, reflect: true })
|
|
54
|
+
loading = false;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Text to display/announce during loading.
|
|
58
|
+
*/
|
|
59
|
+
@property({ type: String, attribute: "loading-text" })
|
|
60
|
+
loadingText = "Loading...";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Node IDs that are currently loading children.
|
|
64
|
+
* Allows for node-level loading indicators.
|
|
65
|
+
*/
|
|
66
|
+
@property({ attribute: false })
|
|
67
|
+
loadingNodes: Set<string> | string[] = new Set();
|
|
68
|
+
|
|
69
|
+
@state()
|
|
70
|
+
private selectedItems: Set<string> = new Set();
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Checks if a specific node is currently loading.
|
|
74
|
+
*/
|
|
75
|
+
isNodeLoading(nodeId: string): boolean {
|
|
76
|
+
if (this.loadingNodes instanceof Set) {
|
|
77
|
+
return this.loadingNodes.has(nodeId);
|
|
78
|
+
}
|
|
79
|
+
return this.loadingNodes.includes(nodeId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
handleItemSelect(itemId: string): void {
|
|
83
|
+
// Disable selection during loading
|
|
84
|
+
if (this.loading) return;
|
|
85
|
+
if (this.selectionMode === "none") return;
|
|
86
|
+
|
|
87
|
+
if (this.selectionMode === "single") {
|
|
88
|
+
this.selectedItems.clear();
|
|
89
|
+
this.selectedItems.add(itemId);
|
|
90
|
+
} else {
|
|
91
|
+
if (this.selectedItems.has(itemId)) {
|
|
92
|
+
this.selectedItems.delete(itemId);
|
|
93
|
+
} else {
|
|
94
|
+
this.selectedItems.add(itemId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
emitEvent(this, "selection-change", {
|
|
99
|
+
detail: { selectedItems: Array.from(this.selectedItems) },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.requestUpdate();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
isItemSelected(itemId: string): boolean {
|
|
106
|
+
return this.selectedItems.has(itemId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override render(): TemplateResult {
|
|
110
|
+
const classes = {
|
|
111
|
+
"ds-tree": true,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return html`
|
|
115
|
+
<ul
|
|
116
|
+
class=${classMap(classes)}
|
|
117
|
+
role="tree"
|
|
118
|
+
aria-label=${this.label}
|
|
119
|
+
aria-multiselectable=${this.selectionMode === "multiple" ? "true" : nothing}
|
|
120
|
+
aria-busy=${this.loading ? "true" : nothing}
|
|
121
|
+
data-size=${this.size !== "default" ? this.size : nothing}
|
|
122
|
+
?data-lines=${this.lines}
|
|
123
|
+
?data-loading=${this.loading}
|
|
124
|
+
>
|
|
125
|
+
<slot></slot>
|
|
126
|
+
</ul>
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Register the component
|
|
132
|
+
define("ds-tree", DsTree);
|
|
133
|
+
|
|
134
|
+
// TypeScript declaration for HTML
|
|
135
|
+
declare global {
|
|
136
|
+
interface HTMLElementTagNameMap {
|
|
137
|
+
"ds-tree": DsTree;
|
|
138
|
+
}
|
|
139
|
+
}
|