@hypoth-ui/cli 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,76 @@
|
|
|
1
|
+
import { createElement, forwardRef, useEffect, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
type ResponsiveProp,
|
|
4
|
+
generateResponsiveDataAttr,
|
|
5
|
+
isResponsiveObject,
|
|
6
|
+
resolveResponsiveValue,
|
|
7
|
+
} from "../primitives/responsive.js";
|
|
8
|
+
|
|
9
|
+
export type IconSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Icon name from Lucide library (kebab-case format).
|
|
13
|
+
* This is a nominal type alias to document the expected format.
|
|
14
|
+
*/
|
|
15
|
+
export type IconName = string;
|
|
16
|
+
|
|
17
|
+
export interface IconProps {
|
|
18
|
+
/** Icon name from Lucide library (kebab-case) */
|
|
19
|
+
name: string;
|
|
20
|
+
/**
|
|
21
|
+
* Icon size - supports responsive object syntax
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* // Single value
|
|
25
|
+
* <Icon name="check" size="md" />
|
|
26
|
+
*
|
|
27
|
+
* // Responsive
|
|
28
|
+
* <Icon name="check" size={{ base: "sm", md: "md", lg: "lg" }} />
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
size?: ResponsiveProp<IconSize>;
|
|
32
|
+
/** Accessible label. When omitted, icon is decorative. */
|
|
33
|
+
label?: string;
|
|
34
|
+
/** Custom color (CSS value) */
|
|
35
|
+
color?: string;
|
|
36
|
+
/** Additional CSS classes */
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* React wrapper for ds-icon Web Component.
|
|
42
|
+
* Provides type-safe props for icon display.
|
|
43
|
+
*/
|
|
44
|
+
export const Icon = forwardRef<HTMLElement, IconProps>((props, forwardedRef) => {
|
|
45
|
+
const { name, size = "md", label, color, className, ...rest } = props;
|
|
46
|
+
|
|
47
|
+
const internalRef = useRef<HTMLElement>(null);
|
|
48
|
+
|
|
49
|
+
// Merge refs
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (typeof forwardedRef === "function") {
|
|
52
|
+
forwardedRef(internalRef.current);
|
|
53
|
+
} else if (forwardedRef) {
|
|
54
|
+
(forwardedRef as React.MutableRefObject<HTMLElement | null>).current = internalRef.current;
|
|
55
|
+
}
|
|
56
|
+
}, [forwardedRef]);
|
|
57
|
+
|
|
58
|
+
// Resolve responsive size - use base value for the WC attribute
|
|
59
|
+
const resolvedSize = resolveResponsiveValue(size, "md");
|
|
60
|
+
const isResponsive = isResponsiveObject(size);
|
|
61
|
+
const responsiveSizeAttr = isResponsive ? generateResponsiveDataAttr(size) : undefined;
|
|
62
|
+
|
|
63
|
+
return createElement("ds-icon", {
|
|
64
|
+
ref: internalRef,
|
|
65
|
+
name,
|
|
66
|
+
size: resolvedSize,
|
|
67
|
+
label: label || undefined,
|
|
68
|
+
color: color || undefined,
|
|
69
|
+
class: className,
|
|
70
|
+
// Add responsive data attribute for CSS targeting
|
|
71
|
+
"data-size-responsive": responsiveSizeAttr,
|
|
72
|
+
...rest,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
Icon.displayName = "Icon";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon Adapter for Lucide Icons
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified interface for accessing icons from the Lucide library.
|
|
5
|
+
* Uses createElement() for efficient SVG generation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createElement, icons } from "lucide";
|
|
9
|
+
import type { IconNode } from "lucide";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert kebab-case to PascalCase for Lucide icon names
|
|
13
|
+
* e.g., "arrow-right" -> "ArrowRight"
|
|
14
|
+
*/
|
|
15
|
+
function toPascalCase(str: string): string {
|
|
16
|
+
return str
|
|
17
|
+
.split("-")
|
|
18
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
19
|
+
.join("");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get an SVG element for the specified icon name.
|
|
24
|
+
*
|
|
25
|
+
* @param name - The icon name in kebab-case (e.g., "arrow-right", "search", "external-link")
|
|
26
|
+
* @param options - Optional attributes to apply to the SVG
|
|
27
|
+
* @returns SVGSVGElement or null if icon not found
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const svg = getIconSvg("search");
|
|
32
|
+
* if (svg) {
|
|
33
|
+
* container.appendChild(svg);
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function getIconSvg(
|
|
38
|
+
name: string,
|
|
39
|
+
options?: {
|
|
40
|
+
size?: number | string;
|
|
41
|
+
color?: string;
|
|
42
|
+
strokeWidth?: number | string;
|
|
43
|
+
}
|
|
44
|
+
): SVGSVGElement | null {
|
|
45
|
+
// Convert kebab-case name to PascalCase for Lucide lookup
|
|
46
|
+
const pascalName = toPascalCase(name);
|
|
47
|
+
|
|
48
|
+
// Check if icon exists in Lucide
|
|
49
|
+
const iconNode = icons[pascalName as keyof typeof icons] as IconNode | undefined;
|
|
50
|
+
|
|
51
|
+
if (!iconNode) {
|
|
52
|
+
if (process.env.NODE_ENV !== "production") {
|
|
53
|
+
console.warn(`[ds-icon] Icon "${name}" not found in Lucide library.`);
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Build custom attributes to merge with default icon attrs
|
|
59
|
+
const customAttrs: Record<string, string | number> = {};
|
|
60
|
+
|
|
61
|
+
if (options?.size) {
|
|
62
|
+
customAttrs.width = options.size;
|
|
63
|
+
customAttrs.height = options.size;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options?.color) {
|
|
67
|
+
customAttrs.color = options.color;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (options?.strokeWidth) {
|
|
71
|
+
customAttrs["stroke-width"] = options.strokeWidth;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Merge custom attributes with the icon's default attributes
|
|
75
|
+
// IconNode is [tag, attrs, children?]
|
|
76
|
+
const [tag, defaultAttrs, children] = iconNode;
|
|
77
|
+
const mergedAttrs = { ...defaultAttrs, ...customAttrs };
|
|
78
|
+
const modifiedIconNode: IconNode = children ? [tag, mergedAttrs, children] : [tag, mergedAttrs];
|
|
79
|
+
|
|
80
|
+
const element = createElement(modifiedIconNode);
|
|
81
|
+
|
|
82
|
+
return element as SVGSVGElement;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if an icon name exists in the Lucide library.
|
|
87
|
+
*
|
|
88
|
+
* @param name - The icon name in kebab-case
|
|
89
|
+
* @returns True if the icon exists
|
|
90
|
+
*/
|
|
91
|
+
export function hasIcon(name: string): boolean {
|
|
92
|
+
const pascalName = toPascalCase(name);
|
|
93
|
+
return pascalName in icons;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all available icon names.
|
|
98
|
+
*
|
|
99
|
+
* @returns Array of icon names in kebab-case
|
|
100
|
+
*/
|
|
101
|
+
export function getAvailableIcons(): string[] {
|
|
102
|
+
return Object.keys(icons).map((name) =>
|
|
103
|
+
name
|
|
104
|
+
.replace(/([A-Z])/g, "-$1")
|
|
105
|
+
.toLowerCase()
|
|
106
|
+
.replace(/^-/, "")
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
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 { define } from "../../registry/define.js";
|
|
6
|
+
import { getIconSvg, hasIcon } from "./icon-adapter.js";
|
|
7
|
+
|
|
8
|
+
export type IconSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Size mappings in pixels for SVG generation
|
|
12
|
+
*/
|
|
13
|
+
const SIZE_MAP: Record<IconSize, number> = {
|
|
14
|
+
xs: 12,
|
|
15
|
+
sm: 16,
|
|
16
|
+
md: 20,
|
|
17
|
+
lg: 24,
|
|
18
|
+
xl: 32,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An accessible icon component with Lucide integration.
|
|
23
|
+
*
|
|
24
|
+
* @element ds-icon
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```html
|
|
28
|
+
* <!-- Decorative icon (hidden from screen readers) -->
|
|
29
|
+
* <ds-icon name="search"></ds-icon>
|
|
30
|
+
*
|
|
31
|
+
* <!-- Meaningful icon (announced to screen readers) -->
|
|
32
|
+
* <ds-icon name="alert-triangle" label="Warning"></ds-icon>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class DsIcon extends DSElement {
|
|
36
|
+
static override styles = [];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The icon name from Lucide library (kebab-case).
|
|
40
|
+
* e.g., "search", "arrow-right", "external-link"
|
|
41
|
+
*/
|
|
42
|
+
@property({ type: String, reflect: true })
|
|
43
|
+
name = "";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The icon size.
|
|
47
|
+
*/
|
|
48
|
+
@property({ type: String, reflect: true })
|
|
49
|
+
size: IconSize = "md";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Accessible label for meaningful icons.
|
|
53
|
+
* When provided, the icon is announced to screen readers.
|
|
54
|
+
* When omitted, the icon is decorative and hidden from screen readers.
|
|
55
|
+
*/
|
|
56
|
+
@property({ type: String })
|
|
57
|
+
label = "";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Custom color for the icon.
|
|
61
|
+
* Accepts any valid CSS color value.
|
|
62
|
+
* Default is currentColor (inherits from text color).
|
|
63
|
+
*/
|
|
64
|
+
@property({ type: String })
|
|
65
|
+
color = "";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Cached SVG element
|
|
69
|
+
*/
|
|
70
|
+
@state()
|
|
71
|
+
private _svgElement: SVGSVGElement | null = null;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Track if icon is valid
|
|
75
|
+
*/
|
|
76
|
+
@state()
|
|
77
|
+
private _isValidIcon = true;
|
|
78
|
+
|
|
79
|
+
override willUpdate(changedProperties: Map<string, unknown>): void {
|
|
80
|
+
if (changedProperties.has("name") || changedProperties.has("size")) {
|
|
81
|
+
this.updateSvg();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private updateSvg(): void {
|
|
86
|
+
if (!this.name) {
|
|
87
|
+
this._svgElement = null;
|
|
88
|
+
this._isValidIcon = false;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!hasIcon(this.name)) {
|
|
93
|
+
this._svgElement = null;
|
|
94
|
+
this._isValidIcon = false;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this._svgElement = getIconSvg(this.name, {
|
|
99
|
+
size: SIZE_MAP[this.size],
|
|
100
|
+
});
|
|
101
|
+
this._isValidIcon = this._svgElement !== null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override render(): TemplateResult {
|
|
105
|
+
const isDecorative = !this.label;
|
|
106
|
+
|
|
107
|
+
const classes = {
|
|
108
|
+
"ds-icon": true,
|
|
109
|
+
[`ds-icon--${this.size}`]: true,
|
|
110
|
+
"ds-icon--fallback": !this._isValidIcon,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const style = this.color ? `color: ${this.color}` : "";
|
|
114
|
+
|
|
115
|
+
// Render fallback if icon not found
|
|
116
|
+
if (!this._isValidIcon) {
|
|
117
|
+
return html`
|
|
118
|
+
<span
|
|
119
|
+
class=${classMap(classes)}
|
|
120
|
+
style=${style || nothing}
|
|
121
|
+
aria-hidden="true"
|
|
122
|
+
></span>
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Decorative icon (no label)
|
|
127
|
+
if (isDecorative) {
|
|
128
|
+
return html`
|
|
129
|
+
<span
|
|
130
|
+
class=${classMap(classes)}
|
|
131
|
+
style=${style || nothing}
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
>
|
|
134
|
+
${this._svgElement}
|
|
135
|
+
</span>
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Meaningful icon (with label)
|
|
140
|
+
return html`
|
|
141
|
+
<span
|
|
142
|
+
class=${classMap(classes)}
|
|
143
|
+
style=${style || nothing}
|
|
144
|
+
role="img"
|
|
145
|
+
aria-label=${this.label}
|
|
146
|
+
>
|
|
147
|
+
${this._svgElement}
|
|
148
|
+
</span>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Register the component
|
|
154
|
+
define("ds-icon", DsIcon);
|
|
155
|
+
|
|
156
|
+
// TypeScript declaration for HTML
|
|
157
|
+
declare global {
|
|
158
|
+
interface HTMLElementTagNameMap {
|
|
159
|
+
"ds-icon": DsIcon;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { type InputHTMLAttributes, createElement, forwardRef, useEffect, useRef } from "react";
|
|
3
|
+
import {
|
|
4
|
+
type ResponsiveProp,
|
|
5
|
+
generateResponsiveDataAttr,
|
|
6
|
+
isResponsiveObject,
|
|
7
|
+
resolveResponsiveValue,
|
|
8
|
+
} from "@/lib/primitives/responsive";
|
|
9
|
+
|
|
10
|
+
export type InputType = "text" | "email" | "password" | "number" | "tel" | "url" | "search";
|
|
11
|
+
export type InputSize = "sm" | "md" | "lg";
|
|
12
|
+
|
|
13
|
+
export interface InputProps
|
|
14
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange" | "onInput"> {
|
|
15
|
+
/** Input type */
|
|
16
|
+
type?: InputType;
|
|
17
|
+
/**
|
|
18
|
+
* Input size - supports responsive object syntax
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* // Single value
|
|
22
|
+
* <Input size="md" />
|
|
23
|
+
*
|
|
24
|
+
* // Responsive
|
|
25
|
+
* <Input size={{ base: "sm", md: "md", lg: "lg" }} />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
size?: ResponsiveProp<InputSize>;
|
|
29
|
+
/** Error state */
|
|
30
|
+
error?: boolean;
|
|
31
|
+
/** Change handler - fires when input loses focus with changed value */
|
|
32
|
+
onChange?: (value: string, event: Event) => void;
|
|
33
|
+
/** Value change handler - fires on every keystroke */
|
|
34
|
+
onValueChange?: (value: string, event: Event) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* React wrapper for ds-input Web Component.
|
|
39
|
+
* Provides type-safe props and event handling.
|
|
40
|
+
*/
|
|
41
|
+
export const Input = forwardRef<HTMLElement, InputProps>((props, forwardedRef) => {
|
|
42
|
+
const {
|
|
43
|
+
type = "text",
|
|
44
|
+
size = "md",
|
|
45
|
+
name,
|
|
46
|
+
value,
|
|
47
|
+
placeholder,
|
|
48
|
+
disabled = false,
|
|
49
|
+
readOnly = false,
|
|
50
|
+
required = false,
|
|
51
|
+
error = false,
|
|
52
|
+
minLength,
|
|
53
|
+
maxLength,
|
|
54
|
+
pattern,
|
|
55
|
+
onChange,
|
|
56
|
+
onValueChange,
|
|
57
|
+
className,
|
|
58
|
+
...rest
|
|
59
|
+
} = props;
|
|
60
|
+
|
|
61
|
+
const internalRef = useRef<HTMLElement>(null);
|
|
62
|
+
|
|
63
|
+
// Merge refs
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (typeof forwardedRef === "function") {
|
|
66
|
+
forwardedRef(internalRef.current);
|
|
67
|
+
} else if (forwardedRef) {
|
|
68
|
+
(forwardedRef as React.MutableRefObject<HTMLElement | null>).current = internalRef.current;
|
|
69
|
+
}
|
|
70
|
+
}, [forwardedRef]);
|
|
71
|
+
|
|
72
|
+
// Handle input events
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const element = internalRef.current;
|
|
75
|
+
if (!element) return;
|
|
76
|
+
|
|
77
|
+
const handleInputEvent = (event: Event) => {
|
|
78
|
+
const customEvent = event as CustomEvent<{ value: string }>;
|
|
79
|
+
onValueChange?.(customEvent.detail.value, event);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleChangeEvent = (event: Event) => {
|
|
83
|
+
const customEvent = event as CustomEvent<{ value: string }>;
|
|
84
|
+
onChange?.(customEvent.detail.value, event);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (onValueChange) {
|
|
88
|
+
element.addEventListener("input", handleInputEvent);
|
|
89
|
+
}
|
|
90
|
+
if (onChange) {
|
|
91
|
+
element.addEventListener("change", handleChangeEvent);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
if (onValueChange) {
|
|
96
|
+
element.removeEventListener("input", handleInputEvent);
|
|
97
|
+
}
|
|
98
|
+
if (onChange) {
|
|
99
|
+
element.removeEventListener("change", handleChangeEvent);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}, [onValueChange, onChange]);
|
|
103
|
+
|
|
104
|
+
// Resolve responsive size - use base value for the WC attribute
|
|
105
|
+
const resolvedSize = resolveResponsiveValue(size, "md");
|
|
106
|
+
const isResponsive = isResponsiveObject(size);
|
|
107
|
+
const responsiveSizeAttr = isResponsive ? generateResponsiveDataAttr(size) : undefined;
|
|
108
|
+
|
|
109
|
+
return createElement("ds-input", {
|
|
110
|
+
ref: internalRef,
|
|
111
|
+
type,
|
|
112
|
+
size: resolvedSize,
|
|
113
|
+
name,
|
|
114
|
+
value,
|
|
115
|
+
placeholder,
|
|
116
|
+
disabled: disabled || undefined,
|
|
117
|
+
readonly: readOnly || undefined,
|
|
118
|
+
required: required || undefined,
|
|
119
|
+
error: error || undefined,
|
|
120
|
+
minlength: minLength,
|
|
121
|
+
maxlength: maxLength,
|
|
122
|
+
pattern,
|
|
123
|
+
class: className,
|
|
124
|
+
// Add responsive data attribute for CSS targeting
|
|
125
|
+
"data-size-responsive": responsiveSizeAttr,
|
|
126
|
+
...rest,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
Input.displayName = "Input";
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
4
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
5
|
+
import { StandardEvents, emitEvent } from "../../events/emit.js";
|
|
6
|
+
import { define } from "../../registry/define.js";
|
|
7
|
+
import { devWarn, hasAccessibleLabel, Warnings } from "../../utils/dev-warnings.js";
|
|
8
|
+
|
|
9
|
+
export type InputType = "text" | "email" | "password" | "number" | "tel" | "url" | "search";
|
|
10
|
+
export type InputSize = "sm" | "md" | "lg";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A text input field component.
|
|
14
|
+
*
|
|
15
|
+
* @element ds-input
|
|
16
|
+
* @fires input - Fired when the input value changes
|
|
17
|
+
* @fires change - Fired when the input value is committed
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```html
|
|
21
|
+
* <ds-field>
|
|
22
|
+
* <ds-label>Email</ds-label>
|
|
23
|
+
* <ds-input type="email" name="email"></ds-input>
|
|
24
|
+
* </ds-field>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class DsInput extends DSElement {
|
|
28
|
+
/** Input type */
|
|
29
|
+
@property({ type: String, reflect: true })
|
|
30
|
+
type: InputType = "text";
|
|
31
|
+
|
|
32
|
+
/** Input size */
|
|
33
|
+
@property({ type: String, reflect: true })
|
|
34
|
+
size: InputSize = "md";
|
|
35
|
+
|
|
36
|
+
/** Input name */
|
|
37
|
+
@property({ type: String, reflect: true })
|
|
38
|
+
name = "";
|
|
39
|
+
|
|
40
|
+
/** Input value */
|
|
41
|
+
@property({ type: String })
|
|
42
|
+
value = "";
|
|
43
|
+
|
|
44
|
+
/** Placeholder text */
|
|
45
|
+
@property({ type: String })
|
|
46
|
+
placeholder = "";
|
|
47
|
+
|
|
48
|
+
/** Disabled state */
|
|
49
|
+
@property({ type: Boolean, reflect: true })
|
|
50
|
+
disabled = false;
|
|
51
|
+
|
|
52
|
+
/** Read-only state */
|
|
53
|
+
@property({ type: Boolean, reflect: true })
|
|
54
|
+
readonly = false;
|
|
55
|
+
|
|
56
|
+
/** Required state */
|
|
57
|
+
@property({ type: Boolean, reflect: true })
|
|
58
|
+
required = false;
|
|
59
|
+
|
|
60
|
+
/** Error state */
|
|
61
|
+
@property({ type: Boolean, reflect: true })
|
|
62
|
+
error = false;
|
|
63
|
+
|
|
64
|
+
/** Minimum length */
|
|
65
|
+
@property({ type: Number })
|
|
66
|
+
minlength?: number;
|
|
67
|
+
|
|
68
|
+
/** Maximum length */
|
|
69
|
+
@property({ type: Number })
|
|
70
|
+
maxlength?: number;
|
|
71
|
+
|
|
72
|
+
/** Pattern for validation */
|
|
73
|
+
@property({ type: String })
|
|
74
|
+
pattern?: string;
|
|
75
|
+
|
|
76
|
+
/** ARIA labelledby - ID of element that labels this input */
|
|
77
|
+
@state()
|
|
78
|
+
private ariaLabelledBy?: string;
|
|
79
|
+
|
|
80
|
+
/** ARIA describedby - IDs of elements that describe this input */
|
|
81
|
+
@state()
|
|
82
|
+
private ariaDescribedBy?: string;
|
|
83
|
+
|
|
84
|
+
private attributeObserver: MutationObserver | null = null;
|
|
85
|
+
|
|
86
|
+
override connectedCallback(): void {
|
|
87
|
+
super.connectedCallback();
|
|
88
|
+
|
|
89
|
+
// Observe ARIA attribute changes on the host element
|
|
90
|
+
this.attributeObserver = new MutationObserver((mutations) => {
|
|
91
|
+
for (const mutation of mutations) {
|
|
92
|
+
if (mutation.type === "attributes") {
|
|
93
|
+
this.syncAriaAttributes();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
this.attributeObserver.observe(this, {
|
|
99
|
+
attributes: true,
|
|
100
|
+
attributeFilter: [
|
|
101
|
+
"aria-labelledby",
|
|
102
|
+
"aria-describedby",
|
|
103
|
+
"aria-invalid",
|
|
104
|
+
"aria-required",
|
|
105
|
+
"aria-disabled",
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Initial sync
|
|
110
|
+
this.syncAriaAttributes();
|
|
111
|
+
|
|
112
|
+
// Dev warning: Check for accessible label after DOM is ready
|
|
113
|
+
requestAnimationFrame(() => {
|
|
114
|
+
if (!hasAccessibleLabel(this)) {
|
|
115
|
+
// Also check if inside a ds-field with a label
|
|
116
|
+
const field = this.closest("ds-field");
|
|
117
|
+
const hasFieldLabel = field?.querySelector("ds-label") !== null;
|
|
118
|
+
if (!hasFieldLabel) {
|
|
119
|
+
devWarn(Warnings.inputMissingLabel("ds-input"));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override disconnectedCallback(): void {
|
|
126
|
+
super.disconnectedCallback();
|
|
127
|
+
this.attributeObserver?.disconnect();
|
|
128
|
+
this.attributeObserver = null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Syncs ARIA attributes from the host element to internal state.
|
|
133
|
+
* The render method will apply these to the native input.
|
|
134
|
+
*/
|
|
135
|
+
private syncAriaAttributes(): void {
|
|
136
|
+
this.ariaLabelledBy = this.getAttribute("aria-labelledby") ?? undefined;
|
|
137
|
+
this.ariaDescribedBy = this.getAttribute("aria-describedby") ?? undefined;
|
|
138
|
+
|
|
139
|
+
// Sync error state from aria-invalid
|
|
140
|
+
const ariaInvalid = this.getAttribute("aria-invalid");
|
|
141
|
+
if (ariaInvalid === "true") {
|
|
142
|
+
this.error = true;
|
|
143
|
+
} else if (ariaInvalid === "false") {
|
|
144
|
+
this.error = false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Sync required state from aria-required
|
|
148
|
+
const ariaRequired = this.getAttribute("aria-required");
|
|
149
|
+
if (ariaRequired === "true") {
|
|
150
|
+
this.required = true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Sync disabled state from aria-disabled
|
|
154
|
+
const ariaDisabled = this.getAttribute("aria-disabled");
|
|
155
|
+
if (ariaDisabled === "true") {
|
|
156
|
+
this.disabled = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private handleInput(event: Event) {
|
|
161
|
+
const input = event.target as HTMLInputElement;
|
|
162
|
+
this.value = input.value;
|
|
163
|
+
// Also emit native input event for compatibility
|
|
164
|
+
this.dispatchEvent(
|
|
165
|
+
new CustomEvent("input", {
|
|
166
|
+
detail: { value: this.value },
|
|
167
|
+
bubbles: true,
|
|
168
|
+
composed: true,
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private handleChange(event: Event) {
|
|
174
|
+
const input = event.target as HTMLInputElement;
|
|
175
|
+
this.value = input.value;
|
|
176
|
+
// Emit ds:change event using standard convention
|
|
177
|
+
emitEvent(this, StandardEvents.CHANGE, {
|
|
178
|
+
detail: { value: this.value },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
override render() {
|
|
183
|
+
return html`
|
|
184
|
+
<div class="ds-input" part="container" data-size=${this.size}>
|
|
185
|
+
<input
|
|
186
|
+
part="input"
|
|
187
|
+
class="ds-input__field"
|
|
188
|
+
type=${this.type}
|
|
189
|
+
name=${this.name}
|
|
190
|
+
.value=${this.value}
|
|
191
|
+
placeholder=${this.placeholder}
|
|
192
|
+
?disabled=${this.disabled}
|
|
193
|
+
?readonly=${this.readonly}
|
|
194
|
+
?required=${this.required}
|
|
195
|
+
aria-invalid=${this.error ? "true" : "false"}
|
|
196
|
+
aria-labelledby=${ifDefined(this.ariaLabelledBy)}
|
|
197
|
+
aria-describedby=${ifDefined(this.ariaDescribedBy)}
|
|
198
|
+
aria-required=${this.required ? "true" : "false"}
|
|
199
|
+
minlength=${this.minlength ?? ""}
|
|
200
|
+
maxlength=${this.maxlength ?? ""}
|
|
201
|
+
pattern=${this.pattern ?? ""}
|
|
202
|
+
@input=${this.handleInput}
|
|
203
|
+
@change=${this.handleChange}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
define("ds-input", DsInput);
|
|
211
|
+
|
|
212
|
+
declare global {
|
|
213
|
+
interface HTMLElementTagNameMap {
|
|
214
|
+
"ds-input": DsInput;
|
|
215
|
+
}
|
|
216
|
+
}
|