@chromvoid/uikit 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 +8 -0
- package/README.md +96 -0
- package/dist/components/cv-accordion-item.d.ts +69 -0
- package/dist/components/cv-accordion-item.js +176 -0
- package/dist/components/cv-accordion.d.ts +79 -0
- package/dist/components/cv-accordion.js +310 -0
- package/dist/components/cv-alert-dialog.d.ts +86 -0
- package/dist/components/cv-alert-dialog.js +393 -0
- package/dist/components/cv-alert.d.ts +48 -0
- package/dist/components/cv-alert.js +156 -0
- package/dist/components/cv-badge.d.ts +56 -0
- package/dist/components/cv-badge.js +280 -0
- package/dist/components/cv-breadcrumb-item.d.ts +35 -0
- package/dist/components/cv-breadcrumb-item.js +64 -0
- package/dist/components/cv-breadcrumb.d.ts +39 -0
- package/dist/components/cv-breadcrumb.js +160 -0
- package/dist/components/cv-button.d.ts +83 -0
- package/dist/components/cv-button.js +541 -0
- package/dist/components/cv-callout.d.ts +32 -0
- package/dist/components/cv-callout.js +221 -0
- package/dist/components/cv-card.d.ts +48 -0
- package/dist/components/cv-card.js +269 -0
- package/dist/components/cv-carousel-slide.d.ts +25 -0
- package/dist/components/cv-carousel-slide.js +51 -0
- package/dist/components/cv-carousel.d.ts +96 -0
- package/dist/components/cv-carousel.js +457 -0
- package/dist/components/cv-checkbox.d.ts +84 -0
- package/dist/components/cv-checkbox.js +274 -0
- package/dist/components/cv-combobox-group.d.ts +15 -0
- package/dist/components/cv-combobox-group.js +34 -0
- package/dist/components/cv-combobox-option.d.ts +30 -0
- package/dist/components/cv-combobox-option.js +66 -0
- package/dist/components/cv-combobox.d.ts +135 -0
- package/dist/components/cv-combobox.js +834 -0
- package/dist/components/cv-command-item.d.ts +30 -0
- package/dist/components/cv-command-item.js +68 -0
- package/dist/components/cv-command-palette.d.ts +105 -0
- package/dist/components/cv-command-palette.js +578 -0
- package/dist/components/cv-context-menu.d.ts +98 -0
- package/dist/components/cv-context-menu.js +515 -0
- package/dist/components/cv-copy-button.d.ts +61 -0
- package/dist/components/cv-copy-button.js +318 -0
- package/dist/components/cv-date-picker.d.ts +161 -0
- package/dist/components/cv-date-picker.js +803 -0
- package/dist/components/cv-dialog.d.ts +89 -0
- package/dist/components/cv-dialog.js +459 -0
- package/dist/components/cv-disclosure.d.ts +57 -0
- package/dist/components/cv-disclosure.js +241 -0
- package/dist/components/cv-drawer.d.ts +102 -0
- package/dist/components/cv-drawer.js +595 -0
- package/dist/components/cv-feed-article.d.ts +26 -0
- package/dist/components/cv-feed-article.js +52 -0
- package/dist/components/cv-feed.d.ts +62 -0
- package/dist/components/cv-feed.js +310 -0
- package/dist/components/cv-grid-cell.d.ts +30 -0
- package/dist/components/cv-grid-cell.js +57 -0
- package/dist/components/cv-grid-column.d.ts +30 -0
- package/dist/components/cv-grid-column.js +43 -0
- package/dist/components/cv-grid-row.d.ts +30 -0
- package/dist/components/cv-grid-row.js +42 -0
- package/dist/components/cv-grid.d.ts +119 -0
- package/dist/components/cv-grid.js +567 -0
- package/dist/components/cv-icon.d.ts +57 -0
- package/dist/components/cv-icon.js +352 -0
- package/dist/components/cv-input.d.ts +127 -0
- package/dist/components/cv-input.js +482 -0
- package/dist/components/cv-landmark.d.ts +32 -0
- package/dist/components/cv-landmark.js +62 -0
- package/dist/components/cv-link.d.ts +22 -0
- package/dist/components/cv-link.js +99 -0
- package/dist/components/cv-listbox-group.d.ts +15 -0
- package/dist/components/cv-listbox-group.js +42 -0
- package/dist/components/cv-listbox.d.ts +81 -0
- package/dist/components/cv-listbox.js +388 -0
- package/dist/components/cv-menu-button.d.ts +118 -0
- package/dist/components/cv-menu-button.js +822 -0
- package/dist/components/cv-menu-group.d.ts +20 -0
- package/dist/components/cv-menu-group.js +48 -0
- package/dist/components/cv-menu-item.d.ts +52 -0
- package/dist/components/cv-menu-item.js +105 -0
- package/dist/components/cv-menu.d.ts +62 -0
- package/dist/components/cv-menu.js +414 -0
- package/dist/components/cv-meter.d.ts +66 -0
- package/dist/components/cv-meter.js +154 -0
- package/dist/components/cv-number.d.ts +139 -0
- package/dist/components/cv-number.js +553 -0
- package/dist/components/cv-option.d.ts +30 -0
- package/dist/components/cv-option.js +84 -0
- package/dist/components/cv-popover.d.ts +87 -0
- package/dist/components/cv-popover.js +373 -0
- package/dist/components/cv-progress-ring.d.ts +45 -0
- package/dist/components/cv-progress-ring.js +169 -0
- package/dist/components/cv-progress.d.ts +45 -0
- package/dist/components/cv-progress.js +148 -0
- package/dist/components/cv-radio-group.d.ts +79 -0
- package/dist/components/cv-radio-group.js +398 -0
- package/dist/components/cv-radio.d.ts +36 -0
- package/dist/components/cv-radio.js +123 -0
- package/dist/components/cv-select-group.d.ts +15 -0
- package/dist/components/cv-select-group.js +44 -0
- package/dist/components/cv-select-option.d.ts +30 -0
- package/dist/components/cv-select-option.js +66 -0
- package/dist/components/cv-select.d.ts +128 -0
- package/dist/components/cv-select.js +666 -0
- package/dist/components/cv-sidebar-item.d.ts +26 -0
- package/dist/components/cv-sidebar-item.js +142 -0
- package/dist/components/cv-sidebar.d.ts +171 -0
- package/dist/components/cv-sidebar.js +767 -0
- package/dist/components/cv-slider-multi-thumb.d.ts +73 -0
- package/dist/components/cv-slider-multi-thumb.js +374 -0
- package/dist/components/cv-slider.d.ts +84 -0
- package/dist/components/cv-slider.js +328 -0
- package/dist/components/cv-spinbutton.d.ts +121 -0
- package/dist/components/cv-spinbutton.js +486 -0
- package/dist/components/cv-spinner.d.ts +18 -0
- package/dist/components/cv-spinner.js +95 -0
- package/dist/components/cv-switch.d.ts +81 -0
- package/dist/components/cv-switch.js +285 -0
- package/dist/components/cv-tab-panel.d.ts +20 -0
- package/dist/components/cv-tab-panel.js +37 -0
- package/dist/components/cv-tab.d.ts +40 -0
- package/dist/components/cv-tab.js +132 -0
- package/dist/components/cv-table-cell.d.ts +31 -0
- package/dist/components/cv-table-cell.js +49 -0
- package/dist/components/cv-table-column.d.ts +37 -0
- package/dist/components/cv-table-column.js +63 -0
- package/dist/components/cv-table-row.d.ts +30 -0
- package/dist/components/cv-table-row.js +45 -0
- package/dist/components/cv-table.d.ts +147 -0
- package/dist/components/cv-table.js +607 -0
- package/dist/components/cv-tabs.d.ts +70 -0
- package/dist/components/cv-tabs.js +524 -0
- package/dist/components/cv-textarea.d.ts +108 -0
- package/dist/components/cv-textarea.js +328 -0
- package/dist/components/cv-toast-region.d.ts +39 -0
- package/dist/components/cv-toast-region.js +162 -0
- package/dist/components/cv-toast.d.ts +67 -0
- package/dist/components/cv-toast.js +315 -0
- package/dist/components/cv-toolbar-item.d.ts +25 -0
- package/dist/components/cv-toolbar-item.js +72 -0
- package/dist/components/cv-toolbar-separator.d.ts +25 -0
- package/dist/components/cv-toolbar-separator.js +45 -0
- package/dist/components/cv-toolbar.d.ts +63 -0
- package/dist/components/cv-toolbar.js +295 -0
- package/dist/components/cv-tooltip.d.ts +83 -0
- package/dist/components/cv-tooltip.js +455 -0
- package/dist/components/cv-treegrid-cell.d.ts +30 -0
- package/dist/components/cv-treegrid-cell.js +57 -0
- package/dist/components/cv-treegrid-column.d.ts +37 -0
- package/dist/components/cv-treegrid-column.js +53 -0
- package/dist/components/cv-treegrid-row.d.ts +55 -0
- package/dist/components/cv-treegrid-row.js +90 -0
- package/dist/components/cv-treegrid.d.ts +96 -0
- package/dist/components/cv-treegrid.js +632 -0
- package/dist/components/cv-treeitem.d.ts +58 -0
- package/dist/components/cv-treeitem.js +144 -0
- package/dist/components/cv-treeview.d.ts +70 -0
- package/dist/components/cv-treeview.js +396 -0
- package/dist/components/cv-window-splitter.d.ts +79 -0
- package/dist/components/cv-window-splitter.js +316 -0
- package/dist/components/index.d.ts +94 -0
- package/dist/components/index.js +79 -0
- package/dist/dialog/create-dialog-controller.d.ts +31 -0
- package/dist/dialog/create-dialog-controller.js +320 -0
- package/dist/dialog/index.d.ts +2 -0
- package/dist/dialog/index.js +1 -0
- package/dist/form-associated/FormAssociatedReatomElement.d.ts +25 -0
- package/dist/form-associated/FormAssociatedReatomElement.js +70 -0
- package/dist/form-associated/withFormAssociated.d.ts +5 -0
- package/dist/form-associated/withFormAssociated.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/reatom-lit/ReatomLitElement.d.ts +27 -0
- package/dist/reatom-lit/ReatomLitElement.js +118 -0
- package/dist/reatom-lit/html.d.ts +4 -0
- package/dist/reatom-lit/html.js +10 -0
- package/dist/reatom-lit/index.d.ts +4 -0
- package/dist/reatom-lit/index.js +4 -0
- package/dist/reatom-lit/watch.d.ts +15 -0
- package/dist/reatom-lit/watch.js +40 -0
- package/dist/reatom-lit/withReatomElement.d.ts +4 -0
- package/dist/reatom-lit/withReatomElement.js +57 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +84 -0
- package/dist/styles/component-styles.d.ts +4 -0
- package/dist/styles/component-styles.js +78 -0
- package/dist/theme/cv-theme-provider.d.ts +32 -0
- package/dist/theme/cv-theme-provider.js +110 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/theme-engine.d.ts +4 -0
- package/dist/theme/theme-engine.js +67 -0
- package/dist/theme/tokens.css +265 -0
- package/dist/theme/types.d.ts +7 -0
- package/dist/theme/types.js +1 -0
- package/dist/toast/create-toast-controller.d.ts +12 -0
- package/dist/toast/create-toast-controller.js +12 -0
- package/dist/toast/index.d.ts +2 -0
- package/dist/toast/index.js +1 -0
- package/package.json +146 -0
- package/specs/_template.md +110 -0
- package/specs/components/accordion.md +207 -0
- package/specs/components/alert.md +83 -0
- package/specs/components/badge.md +183 -0
- package/specs/components/breadcrumb.md +152 -0
- package/specs/components/button.md +227 -0
- package/specs/components/callout.md +153 -0
- package/specs/components/card.md +192 -0
- package/specs/components/carousel.md +232 -0
- package/specs/components/checkbox.md +141 -0
- package/specs/components/combobox.md +427 -0
- package/specs/components/context-menu.md +375 -0
- package/specs/components/copy-button.md +236 -0
- package/specs/components/date-picker.md +290 -0
- package/specs/components/dialog.md +184 -0
- package/specs/components/disclosure.md +151 -0
- package/specs/components/drawer.md +216 -0
- package/specs/components/feed.md +266 -0
- package/specs/components/grid.md +423 -0
- package/specs/components/input.md +237 -0
- package/specs/components/landmark.md +92 -0
- package/specs/components/link.md +117 -0
- package/specs/components/listbox.md +327 -0
- package/specs/components/menu.md +508 -0
- package/specs/components/meter.md +148 -0
- package/specs/components/number.md +268 -0
- package/specs/components/option.md +167 -0
- package/specs/components/popover.md +207 -0
- package/specs/components/progress-ring.md +134 -0
- package/specs/components/progress.md +110 -0
- package/specs/components/radio.md +208 -0
- package/specs/components/select.md +305 -0
- package/specs/components/sidebar.md +204 -0
- package/specs/components/spinbutton.md +157 -0
- package/specs/components/spinner.md +83 -0
- package/specs/components/switch.md +145 -0
- package/specs/components/table.md +372 -0
- package/specs/components/tabs.md +242 -0
- package/specs/components/textarea.md +166 -0
- package/specs/components/theme.md +364 -0
- package/specs/components/toast.md +198 -0
- package/specs/components/toolbar.md +258 -0
- package/specs/components/tooltip.md +152 -0
- package/specs/components/treegrid.md +363 -0
- package/specs/components/treeview.md +263 -0
- package/specs/components/window-splitter.md +225 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# cv-textarea
|
|
2
|
+
|
|
3
|
+
Multi-line text input with form-field chrome, native textarea semantics, and headless state delegation.
|
|
4
|
+
|
|
5
|
+
**Headless:** [`createTextarea`](https://github.com/chromvoid/headless-ui/blob/main/specs/components/textarea.md)
|
|
6
|
+
|
|
7
|
+
## Anatomy
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<cv-textarea> (host)
|
|
11
|
+
├── <span part="form-control-label">
|
|
12
|
+
│ └── <slot name="label">
|
|
13
|
+
├── <div part="base">
|
|
14
|
+
│ └── <textarea part="textarea"></textarea>
|
|
15
|
+
└── <span part="form-control-help-text">
|
|
16
|
+
└── <slot name="help-text">
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Attributes
|
|
20
|
+
|
|
21
|
+
| Attribute | Type | Default | Reflects | Description |
|
|
22
|
+
| ------------- | ------- | ------------ | -------- | -------------------------------------------------- |
|
|
23
|
+
| `value` | String | `""` | no | Current textarea value |
|
|
24
|
+
| `placeholder` | String | `""` | no | Placeholder text |
|
|
25
|
+
| `disabled` | Boolean | `false` | yes | Prevents interaction and removes from tab sequence |
|
|
26
|
+
| `readonly` | Boolean | `false` | yes | Prevents editing while keeping focusability |
|
|
27
|
+
| `required` | Boolean | `false` | yes | Marks the field as required |
|
|
28
|
+
| `rows` | Number | `4` | no | Visible row count |
|
|
29
|
+
| `cols` | Number | `20` | no | Visible column count |
|
|
30
|
+
| `minlength` | Number | — | no | Minimum accepted value length |
|
|
31
|
+
| `maxlength` | Number | — | no | Maximum accepted value length |
|
|
32
|
+
| `resize` | String | `"vertical"` | yes | Resize behavior: `vertical` \| `none` |
|
|
33
|
+
| `size` | String | `"medium"` | yes | Component size: `small` \| `medium` \| `large` |
|
|
34
|
+
| `variant` | String | `"outlined"` | yes | Visual variant: `outlined` \| `filled` |
|
|
35
|
+
| `name` | String | `""` | no | Native textarea form field name |
|
|
36
|
+
|
|
37
|
+
## Variants
|
|
38
|
+
|
|
39
|
+
| Variant | Description |
|
|
40
|
+
| ---------- | ------------------------------------------------------------ |
|
|
41
|
+
| `outlined` | Default style with visible border and transparent background |
|
|
42
|
+
| `filled` | Subtle filled surface with transparent border |
|
|
43
|
+
|
|
44
|
+
## Sizes
|
|
45
|
+
|
|
46
|
+
| Size | `--cv-textarea-min-height` | `--cv-textarea-padding-inline` | `--cv-textarea-font-size` |
|
|
47
|
+
| -------- | -------------------------- | ------------------------------ | -------------------------------- |
|
|
48
|
+
| `small` | `72px` | `var(--cv-space-2, 8px)` | `var(--cv-font-size-sm, 13px)` |
|
|
49
|
+
| `medium` | `96px` | `var(--cv-space-3, 12px)` | `var(--cv-font-size-base, 14px)` |
|
|
50
|
+
| `large` | `120px` | `var(--cv-space-4, 16px)` | `var(--cv-font-size-md, 16px)` |
|
|
51
|
+
|
|
52
|
+
## Slots
|
|
53
|
+
|
|
54
|
+
| Slot | Description |
|
|
55
|
+
| ----------- | ------------------------------------------------------ |
|
|
56
|
+
| `label` | Optional label content above the textarea |
|
|
57
|
+
| `help-text` | Optional helper or description text below the textarea |
|
|
58
|
+
|
|
59
|
+
> The native `<textarea>` is not slottable. There is no default slot.
|
|
60
|
+
|
|
61
|
+
## CSS Parts
|
|
62
|
+
|
|
63
|
+
| Part | Element | Description |
|
|
64
|
+
| ------------------------ | ------------ | ---------------------------------- |
|
|
65
|
+
| `base` | `<div>` | Wrapper around the native textarea |
|
|
66
|
+
| `textarea` | `<textarea>` | Native multi-line text control |
|
|
67
|
+
| `form-control-label` | `<span>` | Wrapper around `label` slot |
|
|
68
|
+
| `form-control-help-text` | `<span>` | Wrapper around `help-text` slot |
|
|
69
|
+
|
|
70
|
+
## CSS Custom Properties
|
|
71
|
+
|
|
72
|
+
| Property | Default | Description |
|
|
73
|
+
| ----------------------------------- | -------------------------------------------- | ----------------------------------------------- |
|
|
74
|
+
| `--cv-textarea-min-height` | `96px` | Minimum block size of textarea control |
|
|
75
|
+
| `--cv-textarea-padding-inline` | `var(--cv-space-3, 12px)` | Horizontal textarea padding |
|
|
76
|
+
| `--cv-textarea-padding-block` | `var(--cv-space-2, 8px)` | Vertical textarea padding |
|
|
77
|
+
| `--cv-textarea-font-size` | `var(--cv-font-size-base, 14px)` | Textarea font size |
|
|
78
|
+
| `--cv-textarea-border-radius` | `var(--cv-radius-sm, 6px)` | Border radius of wrapper and textarea |
|
|
79
|
+
| `--cv-textarea-border-color` | `var(--cv-color-border, #2a3245)` | Border color in default state |
|
|
80
|
+
| `--cv-textarea-background` | `transparent` | Background for outlined variant |
|
|
81
|
+
| `--cv-textarea-color` | `var(--cv-color-text, #e8ecf6)` | Foreground text color |
|
|
82
|
+
| `--cv-textarea-placeholder-color` | `var(--cv-color-text-muted, #6b7a99)` | Placeholder color |
|
|
83
|
+
| `--cv-textarea-focus-ring` | `0 0 0 2px var(--cv-color-primary, #65d7ff)` | Focus ring for focused state |
|
|
84
|
+
| `--cv-textarea-transition-duration` | `var(--cv-duration-fast, 120ms)` | Transition duration for container state changes |
|
|
85
|
+
|
|
86
|
+
## Visual States
|
|
87
|
+
|
|
88
|
+
| Host selector | Description |
|
|
89
|
+
| ----------------------------- | -------------------------------------------------- |
|
|
90
|
+
| `:host([disabled])` | Reduced opacity (`0.55`), no pointer interaction |
|
|
91
|
+
| `:host([readonly])` | Editable appearance with default cursor semantics |
|
|
92
|
+
| `:host([required])` | Required semantic state (no default visual marker) |
|
|
93
|
+
| `:host([focused])` | Focus ring shown on wrapper |
|
|
94
|
+
| `:host([filled])` | Non-empty state for styling hooks |
|
|
95
|
+
| `:host([resize="vertical"])` | Native textarea vertical resize enabled |
|
|
96
|
+
| `:host([resize="none"])` | Native textarea resize disabled |
|
|
97
|
+
| `:host([size="small"])` | Small size token overrides |
|
|
98
|
+
| `:host([size="large"])` | Large size token overrides |
|
|
99
|
+
| `:host([variant="outlined"])` | Outlined variant |
|
|
100
|
+
| `:host([variant="filled"])` | Filled variant |
|
|
101
|
+
|
|
102
|
+
## Reactive State Mapping
|
|
103
|
+
|
|
104
|
+
`cv-textarea` is a visual adapter over headless `createTextarea`.
|
|
105
|
+
|
|
106
|
+
### UIKit properties to headless actions
|
|
107
|
+
|
|
108
|
+
| UIKit Property | Direction | Headless Binding |
|
|
109
|
+
| -------------- | ------------------- | ------------------------------- |
|
|
110
|
+
| `value` | attr/prop -> action | `actions.setValue(value)` |
|
|
111
|
+
| `disabled` | attr -> action | `actions.setDisabled(value)` |
|
|
112
|
+
| `readonly` | attr -> action | `actions.setReadonly(value)` |
|
|
113
|
+
| `required` | attr -> action | `actions.setRequired(value)` |
|
|
114
|
+
| `placeholder` | attr -> action | `actions.setPlaceholder(value)` |
|
|
115
|
+
| `rows` | attr -> action | `actions.setRows(value)` |
|
|
116
|
+
| `cols` | attr -> action | `actions.setCols(value)` |
|
|
117
|
+
| `minlength` | attr -> action | `actions.setMinLength(value)` |
|
|
118
|
+
| `maxlength` | attr -> action | `actions.setMaxLength(value)` |
|
|
119
|
+
| `resize` | attr -> action | `actions.setResize(value)` |
|
|
120
|
+
|
|
121
|
+
### Headless state to DOM reflection
|
|
122
|
+
|
|
123
|
+
| Headless State | Direction | DOM Reflection |
|
|
124
|
+
| ------------------ | ------------- | --------------------------- |
|
|
125
|
+
| `state.disabled()` | state -> attr | `[disabled]` host attribute |
|
|
126
|
+
| `state.readonly()` | state -> attr | `[readonly]` host attribute |
|
|
127
|
+
| `state.required()` | state -> attr | `[required]` host attribute |
|
|
128
|
+
| `state.focused()` | state -> attr | `[focused]` host attribute |
|
|
129
|
+
| `state.filled()` | state -> attr | `[filled]` host attribute |
|
|
130
|
+
| `state.resize()` | state -> attr | `[resize]` host attribute |
|
|
131
|
+
|
|
132
|
+
### Contract props spreading
|
|
133
|
+
|
|
134
|
+
- `contracts.getTextareaProps()` is spread onto `[part="textarea"]` to apply `id`, `aria-disabled`, `aria-readonly`, `aria-required`, `placeholder`, `disabled`, `readonly`, `required`, `tabindex`, `rows`, `cols`, `minlength`, and `maxlength`.
|
|
135
|
+
|
|
136
|
+
### Event wiring
|
|
137
|
+
|
|
138
|
+
- Native `textarea` `input` -> `actions.handleInput(e.target.value)` -> dispatch `cv-input`
|
|
139
|
+
- Native `textarea` `focus` -> `actions.setFocused(true)` -> dispatch `cv-focus`
|
|
140
|
+
- Native `textarea` `blur` -> `actions.setFocused(false)` -> dispatch `cv-blur`; if value changed since focus, dispatch `cv-change`
|
|
141
|
+
|
|
142
|
+
UIKit does not own ARIA computation, disabled/readonly guards, or filled-state derivation.
|
|
143
|
+
|
|
144
|
+
## Events
|
|
145
|
+
|
|
146
|
+
| Event | Detail | Description |
|
|
147
|
+
| ----------- | ------------------- | -------------------------------------------- |
|
|
148
|
+
| `cv-input` | `{ value: string }` | Fires when user input mutates value |
|
|
149
|
+
| `cv-change` | `{ value: string }` | Fires on blur when value changed since focus |
|
|
150
|
+
| `cv-focus` | `{ }` | Fires when textarea receives focus |
|
|
151
|
+
| `cv-blur` | `{ }` | Fires when textarea loses focus |
|
|
152
|
+
|
|
153
|
+
## Usage
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<cv-textarea placeholder="Write a comment"></cv-textarea>
|
|
157
|
+
|
|
158
|
+
<cv-textarea required rows="6">
|
|
159
|
+
<span slot="label">Comment</span>
|
|
160
|
+
<span slot="help-text">Be specific and concise.</span>
|
|
161
|
+
</cv-textarea>
|
|
162
|
+
|
|
163
|
+
<cv-textarea variant="filled" size="small" resize="none"></cv-textarea>
|
|
164
|
+
|
|
165
|
+
<cv-textarea disabled value="Read-only snapshot"></cv-textarea>
|
|
166
|
+
```
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# cv-theme-provider
|
|
2
|
+
|
|
3
|
+
Provides design tokens as CSS custom properties to descendant components, with support for light, dark, and system-auto color schemes.
|
|
4
|
+
|
|
5
|
+
**Headless:** None (UIKit-only component)
|
|
6
|
+
|
|
7
|
+
## Anatomy
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<cv-theme-provider> (host, display: contents)
|
|
11
|
+
└── <slot>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The element uses `display: contents` so it does not generate a box in the layout tree. All slotted children inherit CSS custom properties set on the host.
|
|
15
|
+
|
|
16
|
+
## Attributes
|
|
17
|
+
|
|
18
|
+
| Attribute | Type | Default | Description |
|
|
19
|
+
| --------- | ------ | ---------- | -------------------------------------------------------- |
|
|
20
|
+
| `theme` | String | `""` | Name of a registered theme to apply via the theme engine |
|
|
21
|
+
| `mode` | String | `"system"` | Color scheme mode: `light` \| `dark` \| `system` |
|
|
22
|
+
|
|
23
|
+
### `mode` behavior
|
|
24
|
+
|
|
25
|
+
| Value | Behavior |
|
|
26
|
+
| -------- | ------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `light` | Applies the light token set. Sets `color-scheme: light` on the host. |
|
|
28
|
+
| `dark` | Applies the dark token set. Sets `color-scheme: dark` on the host. |
|
|
29
|
+
| `system` | Listens to `prefers-color-scheme` via `matchMedia` and applies the matching token set at runtime. |
|
|
30
|
+
|
|
31
|
+
When `mode` is `system`, the provider must:
|
|
32
|
+
|
|
33
|
+
1. Query `window.matchMedia('(prefers-color-scheme: dark)')` on connect.
|
|
34
|
+
2. Add a `change` listener to update the active scheme when the OS preference changes.
|
|
35
|
+
3. Remove the listener on disconnect.
|
|
36
|
+
|
|
37
|
+
## Slots
|
|
38
|
+
|
|
39
|
+
| Slot | Description |
|
|
40
|
+
| ----------- | --------------------------------------------------------------------- |
|
|
41
|
+
| `(default)` | All child content; tokens cascade via CSS custom property inheritance |
|
|
42
|
+
|
|
43
|
+
## CSS Parts
|
|
44
|
+
|
|
45
|
+
None. The provider renders only a `<slot>` with no wrapper elements.
|
|
46
|
+
|
|
47
|
+
## CSS Custom Properties
|
|
48
|
+
|
|
49
|
+
The provider defines the full design token surface. All tokens use the `--cv-` prefix. Tokens are applied either via `tokens.css` (static import) or via the theme engine (`defineTheme` + `applyTheme`) at runtime.
|
|
50
|
+
|
|
51
|
+
### Color tokens
|
|
52
|
+
|
|
53
|
+
| Property | Dark value | Light value | Description |
|
|
54
|
+
| ------------------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------ |
|
|
55
|
+
| `--cv-color-bg` | `#0b0d12` | `#f8f9fb` | Page background |
|
|
56
|
+
| `--cv-color-surface` | `#141923` | `#ffffff` | Card / panel background |
|
|
57
|
+
| `--cv-color-surface-2` | `#1d2432` | `#f0f2f5` | Elevated surface level 2 |
|
|
58
|
+
| `--cv-color-surface-3` | `#242c3d` | `#e6e9ee` | Elevated surface level 3 |
|
|
59
|
+
| `--cv-color-surface-4` | `#2b3447` | `#dce0e7` | Elevated surface level 4 |
|
|
60
|
+
| `--cv-color-surface-elevated` | `var(--cv-color-surface-2)` | `var(--cv-color-surface-2)` | Alias: elevated surface |
|
|
61
|
+
| `--cv-color-surface-secondary` | `var(--cv-color-surface-2)` | `var(--cv-color-surface-2)` | Alias: secondary surface |
|
|
62
|
+
| `--cv-color-surface-tertiary` | `var(--cv-color-surface-3)` | `var(--cv-color-surface-3)` | Alias: tertiary surface |
|
|
63
|
+
| `--cv-color-surface-hover` | `color-mix(in oklab, var(--cv-color-primary) 8%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 6%, var(--cv-color-surface))` | Surface hover highlight |
|
|
64
|
+
| `--cv-color-text` | `#e8ecf6` | `#1a1f2e` | Default text color |
|
|
65
|
+
| `--cv-color-text-primary` | `var(--cv-color-text)` | `var(--cv-color-text)` | Alias: primary text |
|
|
66
|
+
| `--cv-color-text-muted` | `#9aa6bf` | `#5c6577` | De-emphasized text |
|
|
67
|
+
| `--cv-color-text-secondary` | `var(--cv-color-text-muted)` | `var(--cv-color-text-muted)` | Alias: secondary text |
|
|
68
|
+
| `--cv-color-text-subtle` | `#7f8aa3` | `#7a8394` | Subtle / placeholder text |
|
|
69
|
+
| `--cv-color-text-strong` | `#f5f7fc` | `#0e1219` | Emphasized text |
|
|
70
|
+
| `--cv-color-text-strongest` | `#ffffff` | `#000000` | Maximum contrast text |
|
|
71
|
+
| `--cv-color-border` | `#2a3245` | `#d0d5de` | Default border color |
|
|
72
|
+
| `--cv-color-border-muted` | `color-mix(in oklab, var(--cv-color-border) 55%, transparent)` | `color-mix(in oklab, var(--cv-color-border) 55%, transparent)` | Subtle border |
|
|
73
|
+
| `--cv-color-border-strong` | `color-mix(in oklab, var(--cv-color-border) 82%, white 18%)` | `color-mix(in oklab, var(--cv-color-border) 82%, black 18%)` | Strong border |
|
|
74
|
+
| `--cv-color-border-accent` | `color-mix(in oklab, var(--cv-color-primary) 35%, var(--cv-color-border))` | `color-mix(in oklab, var(--cv-color-primary) 35%, var(--cv-color-border))` | Accent-tinted border |
|
|
75
|
+
| `--cv-color-brand` | `var(--cv-color-primary)` | _(inherits)_ | Alias: brand color |
|
|
76
|
+
| `--cv-color-primary` | `#65d7ff` | `#0e8ab4` | Primary accent |
|
|
77
|
+
| `--cv-color-primary-dark` | `#36bae8` | `#0b7199` | Darker primary shade |
|
|
78
|
+
| `--cv-color-primary-darker` | `#1794c2` | `#085a7a` | Darkest primary shade |
|
|
79
|
+
| `--cv-color-primary-subtle` | `color-mix(in oklab, var(--cv-color-primary) 12%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 8%, var(--cv-color-surface))` | Subtle primary tint |
|
|
80
|
+
| `--cv-color-primary-muted` | `color-mix(in oklab, var(--cv-color-primary) 22%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 15%, var(--cv-color-surface))` | Muted primary tint |
|
|
81
|
+
| `--cv-color-on-primary` | `#03151c` | `#ffffff` | Text on primary background |
|
|
82
|
+
| `--cv-color-accent` | `#b388ff` | `#7c3aed` | Secondary accent (purple) |
|
|
83
|
+
| `--cv-color-accent-light` | `color-mix(in oklab, var(--cv-color-accent) 70%, white)` | `color-mix(in oklab, var(--cv-color-accent) 70%, white)` | Light accent shade |
|
|
84
|
+
| `--cv-color-accent-dark` | `color-mix(in oklab, var(--cv-color-accent) 70%, black)` | `color-mix(in oklab, var(--cv-color-accent) 70%, black)` | Dark accent shade |
|
|
85
|
+
| `--cv-color-accent-hover` | `color-mix(in oklab, var(--cv-color-accent) 85%, white)` | `color-mix(in oklab, var(--cv-color-accent) 85%, black)` | Accent hover state |
|
|
86
|
+
| `--cv-color-accent-contrast` | `#14001f` | `#ffffff` | Text on accent background |
|
|
87
|
+
| `--cv-color-cyan` | `var(--cv-color-primary)` | _(inherits)_ | Alias: cyan |
|
|
88
|
+
| `--cv-color-cyan-light` | `color-mix(in oklab, var(--cv-color-cyan) 70%, white)` | _(inherits)_ | Light cyan shade |
|
|
89
|
+
| `--cv-color-cyan-dark` | `color-mix(in oklab, var(--cv-color-cyan) 70%, black)` | _(inherits)_ | Dark cyan shade |
|
|
90
|
+
| `--cv-color-success` | `#6ef7c8` | `#16a367` | Success color |
|
|
91
|
+
| `--cv-color-success-dark` | `#32cca0` | `#0f8553` | Dark success shade |
|
|
92
|
+
| `--cv-color-success-text` | `#e8fff5` | `#052e1a` | Text on success background |
|
|
93
|
+
| `--cv-color-warning` | `#ffd36e` | `#b8860b` | Warning color |
|
|
94
|
+
| `--cv-color-warning-dark` | `#d3a74a` | `#9a7209` | Dark warning shade |
|
|
95
|
+
| `--cv-color-warning-text` | `#fff8e6` | `#3d2c04` | Text on warning background |
|
|
96
|
+
| `--cv-color-danger` | `#ff7d86` | `#dc2c3e` | Danger color |
|
|
97
|
+
| `--cv-color-danger-dark` | `#e14f5b` | `#b82232` | Dark danger shade |
|
|
98
|
+
| `--cv-color-danger-text` | `#fff1f2` | `#450a10` | Text on danger background |
|
|
99
|
+
| `--cv-color-info` | `var(--cv-color-primary)` | _(inherits)_ | Info color |
|
|
100
|
+
| `--cv-color-info-text` | `var(--cv-color-text)` | `var(--cv-color-text)` | Text on info background |
|
|
101
|
+
| `--cv-color-focus` | `var(--cv-color-primary)` | _(inherits)_ | Focus indicator color |
|
|
102
|
+
| `--cv-color-focus-ring` | `var(--cv-color-primary)` | _(inherits)_ | Focus ring color |
|
|
103
|
+
| `--cv-color-hover` | `color-mix(in oklab, var(--cv-color-primary) 10%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 8%, var(--cv-color-surface))` | General hover state |
|
|
104
|
+
| `--cv-color-active` | `color-mix(in oklab, var(--cv-color-primary) 18%, transparent)` | `color-mix(in oklab, var(--cv-color-primary) 14%, transparent)` | General active / pressed state |
|
|
105
|
+
| `--cv-color-selected` | `color-mix(in oklab, var(--cv-color-primary) 16%, var(--cv-color-surface))` | `color-mix(in oklab, var(--cv-color-primary) 12%, var(--cv-color-surface))` | Selected item background |
|
|
106
|
+
| `--cv-color-overlay` | `rgba(4, 7, 13, 0.72)` | `rgba(15, 20, 30, 0.38)` | Modal / overlay backdrop |
|
|
107
|
+
|
|
108
|
+
### Spacing tokens
|
|
109
|
+
|
|
110
|
+
| Property | Value | Description |
|
|
111
|
+
| -------------- | ------ | ----------------- |
|
|
112
|
+
| `--cv-space-1` | `4px` | Extra-small space |
|
|
113
|
+
| `--cv-space-2` | `8px` | Small space |
|
|
114
|
+
| `--cv-space-3` | `12px` | Medium space |
|
|
115
|
+
| `--cv-space-4` | `16px` | Default space |
|
|
116
|
+
| `--cv-space-5` | `20px` | Large space |
|
|
117
|
+
| `--cv-space-6` | `24px` | Extra-large space |
|
|
118
|
+
| `--cv-space-7` | `32px` | 2x-large space |
|
|
119
|
+
| `--cv-space-8` | `40px` | 3x-large space |
|
|
120
|
+
|
|
121
|
+
### Radius tokens
|
|
122
|
+
|
|
123
|
+
| Property | Value | Description |
|
|
124
|
+
| ------------------ | -------------------- | ------------------ |
|
|
125
|
+
| `--cv-radius-1` | `6px` | Base small radius |
|
|
126
|
+
| `--cv-radius-2` | `10px` | Base medium radius |
|
|
127
|
+
| `--cv-radius-3` | `14px` | Base large radius |
|
|
128
|
+
| `--cv-radius-4` | `18px` | Base extra-large |
|
|
129
|
+
| `--cv-radius-s` | `var(--cv-radius-1)` | Alias: small |
|
|
130
|
+
| `--cv-radius-sm` | `var(--cv-radius-1)` | Alias: small |
|
|
131
|
+
| `--cv-radius-m` | `var(--cv-radius-2)` | Alias: medium |
|
|
132
|
+
| `--cv-radius-md` | `var(--cv-radius-2)` | Alias: medium |
|
|
133
|
+
| `--cv-radius-lg` | `var(--cv-radius-3)` | Alias: large |
|
|
134
|
+
| `--cv-radius-xl` | `var(--cv-radius-4)` | Alias: extra-large |
|
|
135
|
+
| `--cv-radius-pill` | `999px` | Pill shape |
|
|
136
|
+
| `--cv-radius-full` | `9999px` | Full circle |
|
|
137
|
+
|
|
138
|
+
### Typography tokens
|
|
139
|
+
|
|
140
|
+
| Property | Value | Description |
|
|
141
|
+
| ---------------------------- | ------------------------------------------------------------------------------- | ---------------------- |
|
|
142
|
+
| `--cv-font-family-primary` | `'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif` | Primary font stack |
|
|
143
|
+
| `--cv-font-family-body` | `var(--cv-font-family-primary)` | Body text font |
|
|
144
|
+
| `--cv-font-family-display` | `'Satoshi', var(--cv-font-family-primary)` | Display / heading font |
|
|
145
|
+
| `--cv-font-family-sans` | `var(--cv-font-family-primary)` | Alias: sans-serif |
|
|
146
|
+
| `--cv-font-family-code` | `'JetBrains Mono', 'SF Mono', 'Monaco', 'Consolas', monospace` | Monospace font |
|
|
147
|
+
| `--cv-font-size-xs` | `0.75rem` | Extra-small text |
|
|
148
|
+
| `--cv-font-size-sm` | `0.875rem` | Small text |
|
|
149
|
+
| `--cv-font-size-base` | `1rem` | Base text size |
|
|
150
|
+
| `--cv-font-size-md` | `var(--cv-font-size-base)` | Alias: medium |
|
|
151
|
+
| `--cv-font-size-lg` | `1.125rem` | Large text |
|
|
152
|
+
| `--cv-font-size-xl` | `1.25rem` | Extra-large text |
|
|
153
|
+
| `--cv-font-size-2xl` | `1.5rem` | 2x-large text |
|
|
154
|
+
| `--cv-font-size-3xl` | `1.875rem` | 3x-large text |
|
|
155
|
+
| `--cv-font-size-4xl` | `2.25rem` | 4x-large text |
|
|
156
|
+
| `--cv-font-size-5xl` | `3rem` | 5x-large text |
|
|
157
|
+
| `--cv-font-size-6xl` | `3.75rem` | 6x-large text |
|
|
158
|
+
| `--cv-font-weight-thin` | `100` | Thin weight |
|
|
159
|
+
| `--cv-font-weight-light` | `300` | Light weight |
|
|
160
|
+
| `--cv-font-weight-normal` | `400` | Normal weight |
|
|
161
|
+
| `--cv-font-weight-regular` | `var(--cv-font-weight-normal)` | Alias: regular |
|
|
162
|
+
| `--cv-font-weight-medium` | `500` | Medium weight |
|
|
163
|
+
| `--cv-font-weight-semibold` | `600` | Semi-bold weight |
|
|
164
|
+
| `--cv-font-weight-bold` | `700` | Bold weight |
|
|
165
|
+
| `--cv-font-weight-extrabold` | `800` | Extra-bold weight |
|
|
166
|
+
| `--cv-font-weight-black` | `900` | Black weight |
|
|
167
|
+
|
|
168
|
+
### Shadow tokens
|
|
169
|
+
|
|
170
|
+
| Property | Dark value | Light value | Description |
|
|
171
|
+
| ------------------ | --------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------ |
|
|
172
|
+
| `--cv-shadow-sm` | `0 2px 8px rgba(0, 0, 0, 0.24)` | `0 2px 8px rgba(0, 0, 0, 0.08)` | Small shadow |
|
|
173
|
+
| `--cv-shadow-md` | `0 8px 28px rgba(0, 0, 0, 0.32)` | `0 8px 28px rgba(0, 0, 0, 0.12)` | Medium shadow |
|
|
174
|
+
| `--cv-shadow-lg` | `0 16px 48px rgba(0, 0, 0, 0.38)` | `0 16px 48px rgba(0, 0, 0, 0.14)` | Large shadow |
|
|
175
|
+
| `--cv-shadow-xl` | `0 24px 64px rgba(0, 0, 0, 0.42)` | `0 24px 64px rgba(0, 0, 0, 0.16)` | Extra-large shadow |
|
|
176
|
+
| `--cv-shadow-glow` | `0 0 40px color-mix(in oklab, var(--cv-color-cyan) 15%, transparent)` | `0 0 40px color-mix(in oklab, var(--cv-color-cyan) 10%, transparent)` | Glow effect |
|
|
177
|
+
| `--cv-shadow-1` | `var(--cv-shadow-sm)` | `var(--cv-shadow-sm)` | Alias: level 1 |
|
|
178
|
+
| `--cv-shadow-2` | `var(--cv-shadow-md)` | `var(--cv-shadow-md)` | Alias: level 2 |
|
|
179
|
+
| `--cv-shadow-3` | `var(--cv-shadow-lg)` | `var(--cv-shadow-lg)` | Alias: level 3 |
|
|
180
|
+
| `--cv-shadow-4` | `var(--cv-shadow-xl)` | `var(--cv-shadow-xl)` | Alias: level 4 |
|
|
181
|
+
|
|
182
|
+
### Motion tokens
|
|
183
|
+
|
|
184
|
+
| Property | Value | Description |
|
|
185
|
+
| ------------------------ | ------------------------------- | ------------------- |
|
|
186
|
+
| `--cv-duration-instant` | `0ms` | No transition |
|
|
187
|
+
| `--cv-duration-fast` | `120ms` | Fast transition |
|
|
188
|
+
| `--cv-duration-normal` | `220ms` | Standard transition |
|
|
189
|
+
| `--cv-duration-slow` | `320ms` | Slow transition |
|
|
190
|
+
| `--cv-duration-slower` | `500ms` | Slower transition |
|
|
191
|
+
| `--cv-duration-slowest` | `800ms` | Slowest transition |
|
|
192
|
+
| `--cv-easing-standard` | `cubic-bezier(0.2, 0, 0, 1)` | Standard easing |
|
|
193
|
+
| `--cv-easing-accelerate` | `cubic-bezier(0.4, 0, 1, 1)` | Accelerate easing |
|
|
194
|
+
| `--cv-easing-decelerate` | `cubic-bezier(0, 0, 0.2, 1)` | Decelerate easing |
|
|
195
|
+
| `--cv-easing-spring` | `cubic-bezier(0.16, 1, 0.3, 1)` | Spring easing |
|
|
196
|
+
|
|
197
|
+
### Z-index tokens
|
|
198
|
+
|
|
199
|
+
| Property | Value | Description |
|
|
200
|
+
| ---------------- | ------ | ------------- |
|
|
201
|
+
| `--cv-z-base` | `0` | Base layer |
|
|
202
|
+
| `--cv-z-overlay` | `1000` | Overlay layer |
|
|
203
|
+
| `--cv-z-modal` | `1100` | Modal layer |
|
|
204
|
+
| `--cv-z-toast` | `1200` | Toast layer |
|
|
205
|
+
|
|
206
|
+
### Sizing tokens
|
|
207
|
+
|
|
208
|
+
| Property | Value | Description |
|
|
209
|
+
| -------------------------- | ------ | ---------------------- |
|
|
210
|
+
| `--cv-size-control-height` | `48px` | Default control height |
|
|
211
|
+
|
|
212
|
+
## Visual States
|
|
213
|
+
|
|
214
|
+
| Host selector | Description |
|
|
215
|
+
| ------------------------ | ----------------------------------------------------- |
|
|
216
|
+
| `:host([mode="light"])` | Light color scheme active; `color-scheme: light` |
|
|
217
|
+
| `:host([mode="dark"])` | Dark color scheme active; `color-scheme: dark` |
|
|
218
|
+
| `:host([mode="system"])` | Follows OS preference; `color-scheme` set dynamically |
|
|
219
|
+
|
|
220
|
+
The provider also sets a `data-cv-theme` attribute on the host element when a named theme is applied via the theme engine. This attribute can be used for CSS targeting:
|
|
221
|
+
|
|
222
|
+
```css
|
|
223
|
+
cv-theme-provider[data-cv-theme='my-theme'] {
|
|
224
|
+
/* overrides */
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Events
|
|
229
|
+
|
|
230
|
+
None. The theme provider does not emit events. Theme changes propagate via CSS custom property inheritance.
|
|
231
|
+
|
|
232
|
+
## Accessibility
|
|
233
|
+
|
|
234
|
+
- No ARIA roles are required. The provider is invisible infrastructure.
|
|
235
|
+
- All color token pairings (text on surface, text on primary, etc.) must meet WCAG AA contrast ratios: 4.5:1 for normal text, 3:1 for large text and UI components.
|
|
236
|
+
- The `color-scheme` CSS property must be set on the host to ensure native form controls (inputs, selects, scrollbars) render with the correct system appearance.
|
|
237
|
+
|
|
238
|
+
## Theme Engine API
|
|
239
|
+
|
|
240
|
+
The theme engine (`theme-engine.ts`) provides a runtime API for registering and applying named themes programmatically. It is independent of the `cv-theme-provider` element and can target any `HTMLElement`, `ShadowRoot`, or `Document`.
|
|
241
|
+
|
|
242
|
+
### Types
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
type CVThemeTokenName = `--cv-${string}`
|
|
246
|
+
type CVThemeTokens = Record<CVThemeTokenName, string>
|
|
247
|
+
|
|
248
|
+
interface CVThemeDefinition {
|
|
249
|
+
name: string
|
|
250
|
+
tokens: CVThemeTokens
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
type CVThemeTarget = HTMLElement | ShadowRoot | Document
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### `defineTheme(name: string, tokens: CVThemeTokens): CVThemeDefinition`
|
|
257
|
+
|
|
258
|
+
Registers a named theme in the global theme registry.
|
|
259
|
+
|
|
260
|
+
- `name` must be a non-empty string.
|
|
261
|
+
- All keys in `tokens` must start with `--cv-`. Invalid keys throw an `Error`.
|
|
262
|
+
- Returns a defensive copy of the registered definition.
|
|
263
|
+
- Calling `defineTheme` with an existing name overwrites the previous definition.
|
|
264
|
+
|
|
265
|
+
### `getTheme(name: string): CVThemeDefinition | undefined`
|
|
266
|
+
|
|
267
|
+
Retrieves a registered theme definition by name.
|
|
268
|
+
|
|
269
|
+
- Returns `undefined` if no theme is registered with the given name.
|
|
270
|
+
- Returns a defensive copy; mutations do not affect the registry.
|
|
271
|
+
|
|
272
|
+
### `applyTheme(target: CVThemeTarget, name: string): HTMLElement`
|
|
273
|
+
|
|
274
|
+
Applies a registered theme to a target element.
|
|
275
|
+
|
|
276
|
+
- Resolves the target: `HTMLElement` is used directly; `Document` resolves to `document.documentElement`; `ShadowRoot` resolves to `shadowRoot.host`.
|
|
277
|
+
- Removes all CSS custom properties previously applied to the target by a prior `applyTheme` call (tracked via a `WeakMap`).
|
|
278
|
+
- Sets each token as an inline `style.setProperty(key, value)` on the resolved element.
|
|
279
|
+
- Sets the `data-cv-theme` attribute on the resolved element to the theme name.
|
|
280
|
+
- Throws an `Error` if the named theme is not registered.
|
|
281
|
+
- Returns the resolved `HTMLElement`.
|
|
282
|
+
|
|
283
|
+
### Token prefix rule
|
|
284
|
+
|
|
285
|
+
All theme tokens must use the `--cv-` prefix. The engine validates this at registration time and rejects tokens that do not conform.
|
|
286
|
+
|
|
287
|
+
## Light / Dark CSS Cascade Strategy
|
|
288
|
+
|
|
289
|
+
Light and dark tokens are defined entirely in `tokens.css` using CSS selectors and a media query — no JavaScript token switching is needed.
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
1. :root, cv-theme-provider → dark tokens (default)
|
|
293
|
+
2. cv-theme-provider[mode="light"] → light token overrides
|
|
294
|
+
3. @media (prefers-color-scheme: light) {
|
|
295
|
+
:root,
|
|
296
|
+
cv-theme-provider[mode="system"] → light token overrides
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
| `mode` value | Resolution |
|
|
301
|
+
| ------------ | ------------------------------------------------------------------------------------------------------- |
|
|
302
|
+
| `dark` | Uses the default dark block (no extra selector needed) |
|
|
303
|
+
| `light` | Matched by `[mode="light"]` selector |
|
|
304
|
+
| `system` | Matched by `@media` + `[mode="system"]` when OS is light; falls through to dark default when OS is dark |
|
|
305
|
+
|
|
306
|
+
The `:root` selector inside the `@media` block handles the no-provider case (bare `import 'tokens.css'`).
|
|
307
|
+
|
|
308
|
+
The light block overrides only color-varying tokens (colors, shadows, overlay). Scheme-invariant tokens (spacing, radius, typography, motion, z-index, sizing) are defined once in the default block and shared.
|
|
309
|
+
|
|
310
|
+
## Usage
|
|
311
|
+
|
|
312
|
+
### Basic dark theme
|
|
313
|
+
|
|
314
|
+
```html
|
|
315
|
+
<cv-theme-provider mode="dark">
|
|
316
|
+
<cv-button variant="primary">Save</cv-button>
|
|
317
|
+
</cv-theme-provider>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### System-auto (default)
|
|
321
|
+
|
|
322
|
+
```html
|
|
323
|
+
<cv-theme-provider>
|
|
324
|
+
<!-- Follows OS light/dark preference -->
|
|
325
|
+
<my-app></my-app>
|
|
326
|
+
</cv-theme-provider>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Named theme via engine
|
|
330
|
+
|
|
331
|
+
```html
|
|
332
|
+
<script type="module">
|
|
333
|
+
import {defineTheme} from '@chromvoid/uikit/theme'
|
|
334
|
+
|
|
335
|
+
defineTheme('brand', {
|
|
336
|
+
'--cv-color-primary': '#ff6600',
|
|
337
|
+
'--cv-color-bg': '#1a1a2e',
|
|
338
|
+
})
|
|
339
|
+
</script>
|
|
340
|
+
|
|
341
|
+
<cv-theme-provider theme="brand">
|
|
342
|
+
<cv-button variant="primary">Branded</cv-button>
|
|
343
|
+
</cv-theme-provider>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Nested providers (scoped override)
|
|
347
|
+
|
|
348
|
+
```html
|
|
349
|
+
<cv-theme-provider mode="dark">
|
|
350
|
+
<main>
|
|
351
|
+
<cv-theme-provider theme="sidebar-theme">
|
|
352
|
+
<nav><!-- tokens scoped to sidebar --></nav>
|
|
353
|
+
</cv-theme-provider>
|
|
354
|
+
</main>
|
|
355
|
+
</cv-theme-provider>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### CSS targeting via data attribute
|
|
359
|
+
|
|
360
|
+
```css
|
|
361
|
+
cv-theme-provider[data-cv-theme='brand'] {
|
|
362
|
+
--cv-color-accent: #ff9900;
|
|
363
|
+
}
|
|
364
|
+
```
|