@nuvia-ui/components 4.0.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/package.json +27 -0
- package/src/ds-accordion/ds-accordion-item.js +288 -0
- package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
- package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
- package/src/ds-accordion/ds-accordion.js +68 -0
- package/src/ds-accordion/ds-accordion.stories.js +118 -0
- package/src/ds-accordion/ds-accordion.test.js +146 -0
- package/src/ds-accordion/index.js +2 -0
- package/src/ds-action-bar/ds-action-bar.js +116 -0
- package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
- package/src/ds-action-bar/ds-action-bar.test.js +64 -0
- package/src/ds-action-bar/index.js +1 -0
- package/src/ds-alert/ds-alert.a11y.test.js +151 -0
- package/src/ds-alert/ds-alert.js +223 -0
- package/src/ds-alert/ds-alert.mdx +142 -0
- package/src/ds-alert/ds-alert.stories.js +166 -0
- package/src/ds-alert/ds-alert.test.js +256 -0
- package/src/ds-alert/index.js +1 -0
- package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
- package/src/ds-avatar/ds-avatar.js +216 -0
- package/src/ds-avatar/ds-avatar.stories.js +120 -0
- package/src/ds-avatar/ds-avatar.test.js +83 -0
- package/src/ds-avatar/index.js +1 -0
- package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
- package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
- package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
- package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
- package/src/ds-avatar-extended/index.js +1 -0
- package/src/ds-banner/ds-banner.a11y.test.js +51 -0
- package/src/ds-banner/ds-banner.js +233 -0
- package/src/ds-banner/ds-banner.stories.js +185 -0
- package/src/ds-banner/ds-banner.test.js +116 -0
- package/src/ds-banner/index.js +1 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
- package/src/ds-button/ds-button.a11y.test.js +49 -0
- package/src/ds-button/ds-button.js +205 -0
- package/src/ds-button/ds-button.mdx +141 -0
- package/src/ds-button/ds-button.stories.js +152 -0
- package/src/ds-button/ds-button.test.js +62 -0
- package/src/ds-button/index.js +1 -0
- package/src/ds-button-group/ds-button-group.js +82 -0
- package/src/ds-button-group/ds-button-group.mdx +39 -0
- package/src/ds-button-group/ds-button-group.stories.js +47 -0
- package/src/ds-button-group/ds-button-group.test.js +47 -0
- package/src/ds-button-group/index.js +1 -0
- package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
- package/src/ds-checkbox/ds-checkbox.js +271 -0
- package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
- package/src/ds-checkbox/ds-checkbox.test.js +191 -0
- package/src/ds-checkbox/index.js +1 -0
- package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
- package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
- package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
- package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
- package/src/ds-checkbox-group/index.js +1 -0
- package/src/ds-dialog/ds-dialog.js +466 -0
- package/src/ds-dialog/ds-dialog.stories.js +274 -0
- package/src/ds-dialog/ds-dialog.test.js +441 -0
- package/src/ds-dialog/index.js +1 -0
- package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
- package/src/ds-dropdown/ds-dropdown.js +891 -0
- package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
- package/src/ds-dropdown/ds-dropdown.test.js +268 -0
- package/src/ds-dropdown/index.js +1 -0
- package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
- package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
- package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
- package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
- package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
- package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
- package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
- package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
- package/src/ds-file-uploader/ds-file-uploader.js +442 -0
- package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
- package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
- package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
- package/src/ds-header/ds-header.a11y.test.js +38 -0
- package/src/ds-header/ds-header.js +149 -0
- package/src/ds-header/ds-header.stories.js +63 -0
- package/src/ds-header/ds-header.test.js +52 -0
- package/src/ds-header/index.js +1 -0
- package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
- package/src/ds-header-nav/ds-header-nav.js +114 -0
- package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
- package/src/ds-header-nav/ds-header-nav.test.js +93 -0
- package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
- package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
- package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
- package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
- package/src/ds-icon/ds-icon.a11y.test.js +49 -0
- package/src/ds-icon/ds-icon.js +75 -0
- package/src/ds-icon/ds-icon.mdx +36 -0
- package/src/ds-icon/ds-icon.stories.js +88 -0
- package/src/ds-icon/ds-icon.test.js +97 -0
- package/src/ds-icon/index.js +1 -0
- package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
- package/src/ds-icon-button/ds-icon-button.js +224 -0
- package/src/ds-icon-button/ds-icon-button.mdx +131 -0
- package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
- package/src/ds-icon-button/ds-icon-button.test.js +90 -0
- package/src/ds-icon-button/index.js +1 -0
- package/src/ds-input/ds-input.a11y.test.js +145 -0
- package/src/ds-input/ds-input.js +645 -0
- package/src/ds-input/ds-input.mdx +251 -0
- package/src/ds-input/ds-input.stories.js +298 -0
- package/src/ds-input/ds-input.test.js +792 -0
- package/src/ds-input/index.js +1 -0
- package/src/ds-link/ds-link.js +111 -0
- package/src/ds-link/ds-link.stories.js +56 -0
- package/src/ds-link/ds-link.test.js +74 -0
- package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
- package/src/ds-list-item/ds-list-item.js +292 -0
- package/src/ds-list-item/ds-list-item.stories.js +101 -0
- package/src/ds-list-item/ds-list-item.test.js +63 -0
- package/src/ds-menu/ds-menu.js +30 -0
- package/src/ds-menu/ds-menu.stories.js +120 -0
- package/src/ds-menu/ds-menu.test.js +123 -0
- package/src/ds-menu-group/ds-menu-group.js +101 -0
- package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
- package/src/ds-nav-item/ds-nav-item.js +307 -0
- package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.test.js +169 -0
- package/src/ds-nav-item/index.js +1 -0
- package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
- package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
- package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
- package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
- package/src/ds-nav-vertical/index.js +1 -0
- package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
- package/src/ds-pagination/ds-pagination.js +232 -0
- package/src/ds-pagination/ds-pagination.stories.js +63 -0
- package/src/ds-pagination/ds-pagination.test.js +141 -0
- package/src/ds-pagination/index.js +1 -0
- package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
- package/src/ds-progress-bar/ds-progress-bar.js +81 -0
- package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
- package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
- package/src/ds-radio/ds-radio.a11y.test.js +69 -0
- package/src/ds-radio/ds-radio.js +240 -0
- package/src/ds-radio/ds-radio.stories.js +102 -0
- package/src/ds-radio/ds-radio.test.js +114 -0
- package/src/ds-radio/index.js +1 -0
- package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
- package/src/ds-radio-group/ds-radio-group.js +257 -0
- package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
- package/src/ds-radio-group/ds-radio-group.test.js +194 -0
- package/src/ds-radio-group/index.js +1 -0
- package/src/ds-rich-list/ds-rich-list.js +246 -0
- package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
- package/src/ds-rich-list/ds-rich-list.test.js +293 -0
- package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
- package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
- package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
- package/src/ds-slider/ds-slider.js +399 -0
- package/src/ds-slider/ds-slider.stories.js +107 -0
- package/src/ds-slider/ds-slider.test.js +308 -0
- package/src/ds-spinner/ds-spinner.js +173 -0
- package/src/ds-spinner/ds-spinner.stories.js +52 -0
- package/src/ds-spinner/ds-spinner.test.js +50 -0
- package/src/ds-status-border/ds-status-border.js +88 -0
- package/src/ds-status-border/ds-status-border.stories.js +242 -0
- package/src/ds-status-border/ds-status-border.test.js +168 -0
- package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
- package/src/ds-stepper/ds-stepper.js +207 -0
- package/src/ds-stepper/ds-stepper.stories.js +530 -0
- package/src/ds-stepper/ds-stepper.test.js +311 -0
- package/src/ds-stepper-item/ds-stepper-item.js +485 -0
- package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
- package/src/ds-switch/ds-switch.js +348 -0
- package/src/ds-switch/ds-switch.stories.js +145 -0
- package/src/ds-switch/ds-switch.test.js +226 -0
- package/src/ds-switch/index.js +1 -0
- package/src/ds-tab-item/ds-tab-item.js +341 -0
- package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
- package/src/ds-tabs/ds-tab-panel.js +48 -0
- package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
- package/src/ds-tabs/ds-tabs.js +180 -0
- package/src/ds-tabs/ds-tabs.stories.js +152 -0
- package/src/ds-tabs/ds-tabs.test.js +306 -0
- package/src/ds-tabs/index.js +3 -0
- package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
- package/src/ds-tag-action/ds-tag-action.js +185 -0
- package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
- package/src/ds-tag-action/ds-tag-action.test.js +44 -0
- package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
- package/src/ds-tag-removable/ds-tag-removable.js +146 -0
- package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
- package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
- package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
- package/src/ds-tag-status/ds-tag-status.js +164 -0
- package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
- package/src/ds-tag-status/ds-tag-status.test.js +140 -0
- package/src/ds-tag-status/index.js +1 -0
- package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
- package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
- package/src/ds-textarea/ds-textarea.js +505 -0
- package/src/ds-textarea/ds-textarea.stories.js +335 -0
- package/src/ds-textarea/ds-textarea.test.js +218 -0
- package/src/ds-textarea/index.js +1 -0
- package/src/ds-thumbnail/ds-thumbnail.js +207 -0
- package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
- package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
- package/src/ds-toast/ds-toast-provider.js +110 -0
- package/src/ds-toast/ds-toast.a11y.test.js +34 -0
- package/src/ds-toast/ds-toast.js +243 -0
- package/src/ds-toast/ds-toast.stories.js +143 -0
- package/src/ds-toast/ds-toast.test.js +93 -0
- package/src/ds-toast/index.js +2 -0
- package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
- package/src/ds-tooltip/ds-tooltip.js +217 -0
- package/src/ds-tooltip/ds-tooltip.mdx +75 -0
- package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
- package/src/ds-tooltip/ds-tooltip.test.js +191 -0
- package/src/ds-tooltip/index.js +1 -0
- package/src/ds-tooltip/positioner.js +117 -0
- package/src/index.js +50 -0
- package/src/mixins/field-label.mixin.js +113 -0
- package/src/mixins/field-message.mixin.js +66 -0
- package/src/token-provider/index.js +1 -0
- package/src/token-provider/token-provider.a11y.test.js +44 -0
- package/src/token-provider/token-provider.js +85 -0
- package/src/token-provider/token-provider.stories.js +105 -0
- package/src/token-provider/token-provider.test.js +134 -0
- package/src/utils/number-input.utils.js +42 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '../ds-icon/ds-icon.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checkbox component for boolean selections or multi-selection groups.
|
|
6
|
+
*
|
|
7
|
+
* @element ds-checkbox
|
|
8
|
+
*
|
|
9
|
+
* @prop {boolean} checked - Whether the checkbox is checked
|
|
10
|
+
* @prop {boolean} indeterminate - Visual partial selection state
|
|
11
|
+
* @prop {string} label - Label text to display
|
|
12
|
+
* @prop {string} name - Form name
|
|
13
|
+
* @prop {string} value - Form value (default: 'on')
|
|
14
|
+
* @prop {boolean} disabled - Disabled state
|
|
15
|
+
* @prop {boolean} required - Required field
|
|
16
|
+
* @prop {string} validationStatus - Validation state: 'error' | undefined
|
|
17
|
+
*
|
|
18
|
+
* @fires change - Fired when checked state changes
|
|
19
|
+
*/
|
|
20
|
+
export class DsCheckbox extends LitElement {
|
|
21
|
+
static properties = {
|
|
22
|
+
checked: { type: Boolean, reflect: true },
|
|
23
|
+
indeterminate: { type: Boolean, reflect: true },
|
|
24
|
+
label: { type: String },
|
|
25
|
+
name: { type: String },
|
|
26
|
+
value: { type: String },
|
|
27
|
+
disabled: { type: Boolean, reflect: true },
|
|
28
|
+
required: { type: Boolean, reflect: true },
|
|
29
|
+
validationStatus: { type: String, attribute: 'validation-status', reflect: true },
|
|
30
|
+
standalone: { type: Boolean, reflect: true }
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
static styles = css`
|
|
34
|
+
:host {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
vertical-align: middle;
|
|
37
|
+
outline: none;
|
|
38
|
+
font-family: var(--ds-font-family-content);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.checkbox-wrapper {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: flex-start; /* Align to top to handle multiline labels if needed, though center is fine for single line */
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
padding: var(--ds-size-6) var(--ds-space-sm); /* 6px vertical (to get 32px total with 20px content), 8px horizontal */
|
|
46
|
+
min-height: var(--ds-size-32);
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
border-radius: var(--ds-radius-container); /* 0px */
|
|
49
|
+
transition: background-color 0.2s;
|
|
50
|
+
position: relative;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Standalone Mode: Remove padding and min-height constraint for tight layouts (Data Tables, List Items) */
|
|
54
|
+
:host([standalone]) .checkbox-wrapper {
|
|
55
|
+
padding: 0;
|
|
56
|
+
min-height: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:host([standalone]) .checkbox-wrapper:hover {
|
|
60
|
+
background-color: transparent; /* No hover bg in standalone mode */
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Hover state - Covers control and label */
|
|
64
|
+
:host(:not([disabled])) .checkbox-wrapper:hover {
|
|
65
|
+
background-color: var(--ds-color-bg-hover);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Control Container - 20px height to match label line-height */
|
|
69
|
+
.control-container {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
height: var(--ds-size-20);
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Visual Control Box - 16px */
|
|
78
|
+
.control {
|
|
79
|
+
width: var(--ds-size-16);
|
|
80
|
+
height: var(--ds-size-16);
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
border-radius: var(--ds-radius-container); /* 0px */
|
|
86
|
+
transition: all 0.2s ease-in-out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Unchecked State */
|
|
90
|
+
.control {
|
|
91
|
+
border: 2px solid var(--ds-color-border-strongest);
|
|
92
|
+
background-color: transparent;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Checked & Indeterminate States */
|
|
96
|
+
:host([checked]) .control,
|
|
97
|
+
:host([indeterminate]) .control {
|
|
98
|
+
background-color: var(--ds-color-bg-brand);
|
|
99
|
+
border-color: var(--ds-color-border-brand);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Icon styles */
|
|
103
|
+
ds-icon {
|
|
104
|
+
color: var(--ds-color-icon-inverse);
|
|
105
|
+
--ds-icon-size: var(--ds-icon-size-xs);
|
|
106
|
+
display: block;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Label Styles */
|
|
110
|
+
.label {
|
|
111
|
+
margin-inline-start: var(--ds-space-sm);
|
|
112
|
+
font: var(--ds-typo-content-body-regular);
|
|
113
|
+
color: var(--ds-color-text-default);
|
|
114
|
+
user-select: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Focus Visible - applied when internal input is focused via keyboard */
|
|
118
|
+
.checkbox-wrapper:has(input:focus-visible) {
|
|
119
|
+
outline: 2px solid var(--ds-color-border-focus);
|
|
120
|
+
outline-offset: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Native Input (Hidden but accessible) */
|
|
124
|
+
input {
|
|
125
|
+
position: absolute;
|
|
126
|
+
opacity: 0;
|
|
127
|
+
width: 100%;
|
|
128
|
+
height: 100%;
|
|
129
|
+
top: 0;
|
|
130
|
+
left: 0;
|
|
131
|
+
margin: 0;
|
|
132
|
+
cursor: inherit;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Disabled State */
|
|
136
|
+
:host([disabled]) .checkbox-wrapper {
|
|
137
|
+
cursor: not-allowed;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
:host([disabled]) .checkbox-wrapper:hover {
|
|
141
|
+
background-color: transparent;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
:host([disabled]) .label {
|
|
145
|
+
color: var(--ds-color-text-disabled);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Disabled State Control */
|
|
149
|
+
:host([disabled]) .control {
|
|
150
|
+
background-color: transparent;
|
|
151
|
+
border-color: var(--ds-color-icon-disabled);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
:host([disabled][checked]) .control,
|
|
155
|
+
:host([disabled][indeterminate]) .control {
|
|
156
|
+
background-color: var(--ds-color-icon-disabled);
|
|
157
|
+
border-color: transparent;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Disabled Icon Color */
|
|
161
|
+
:host([disabled]) ds-icon {
|
|
162
|
+
color: var(--ds-color-icon-inverse);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Error State */
|
|
166
|
+
:host([validation-status="error"]) .control {
|
|
167
|
+
border-color: var(--ds-color-border-error);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
:host([validation-status="error"][checked]) .control,
|
|
171
|
+
:host([validation-status="error"][indeterminate]) .control {
|
|
172
|
+
background-color: var(--ds-color-bg-error);
|
|
173
|
+
border-color: var(--ds-color-border-error);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Error State Icon Color */
|
|
177
|
+
:host([validation-status="error"][checked]) ds-icon,
|
|
178
|
+
:host([validation-status="error"][indeterminate]) ds-icon {
|
|
179
|
+
color: var(--ds-color-icon-inverse);
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
constructor() {
|
|
184
|
+
super();
|
|
185
|
+
this.checked = false;
|
|
186
|
+
this.indeterminate = false;
|
|
187
|
+
this.name = '';
|
|
188
|
+
this.value = 'on';
|
|
189
|
+
this.disabled = false;
|
|
190
|
+
this.required = false;
|
|
191
|
+
this.standalone = false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
* Handle user interaction
|
|
196
|
+
* If indeterminate, interaction typically clears it and checks/unchecks based on previous state.
|
|
197
|
+
* Standard pattern: Indeterminate -> Checked -> Unchecked
|
|
198
|
+
*/
|
|
199
|
+
_handleClick(e) {
|
|
200
|
+
if (this.disabled) return;
|
|
201
|
+
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
|
|
204
|
+
// If indeterminate, we usually want to transition to checked, or just toggle checked state
|
|
205
|
+
if (this.indeterminate) {
|
|
206
|
+
this.indeterminate = false;
|
|
207
|
+
this.checked = true;
|
|
208
|
+
} else {
|
|
209
|
+
this.checked = !this.checked;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this._dispatchChange();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
_dispatchChange() {
|
|
216
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
217
|
+
detail: {
|
|
218
|
+
checked: this.checked,
|
|
219
|
+
indeterminate: this.indeterminate,
|
|
220
|
+
value: this.value
|
|
221
|
+
},
|
|
222
|
+
bubbles: true,
|
|
223
|
+
composed: true
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_getAriaLabel() {
|
|
228
|
+
return this.getAttribute('aria-label') || this.label || '';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
updated(changedProperties) {
|
|
232
|
+
if (changedProperties.has('indeterminate')) {
|
|
233
|
+
const input = this.shadowRoot.querySelector('input');
|
|
234
|
+
if (input) {
|
|
235
|
+
input.indeterminate = this.indeterminate;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
render() {
|
|
241
|
+
return html`
|
|
242
|
+
<div class="checkbox-wrapper" @click="${this._handleClick}">
|
|
243
|
+
<input
|
|
244
|
+
type="checkbox"
|
|
245
|
+
.checked="${this.checked}"
|
|
246
|
+
.indeterminate="${this.indeterminate}"
|
|
247
|
+
?disabled="${this.disabled}"
|
|
248
|
+
?required="${this.required}"
|
|
249
|
+
name="${this.name}"
|
|
250
|
+
value="${this.value}"
|
|
251
|
+
aria-label="${this._getAriaLabel()}"
|
|
252
|
+
>
|
|
253
|
+
|
|
254
|
+
<div class="control-container">
|
|
255
|
+
<div class="control">
|
|
256
|
+
${this.indeterminate
|
|
257
|
+
? html`<ds-icon name="remove" size="xs"></ds-icon>` // Horizontal dash
|
|
258
|
+
: this.checked
|
|
259
|
+
? html`<ds-icon name="check" size="xs"></ds-icon>` // Checkmark
|
|
260
|
+
: ''
|
|
261
|
+
}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
${this.label ? html`<span class="label">${this.label}</span>` : ''}
|
|
266
|
+
</div>
|
|
267
|
+
`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
customElements.define('ds-checkbox', DsCheckbox);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import './ds-checkbox.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/Checkbox',
|
|
6
|
+
component: 'ds-checkbox',
|
|
7
|
+
argTypes: {
|
|
8
|
+
checked: { control: 'boolean' },
|
|
9
|
+
indeterminate: { control: 'boolean' },
|
|
10
|
+
disabled: { control: 'boolean' },
|
|
11
|
+
required: { control: 'boolean' },
|
|
12
|
+
label: { control: 'text' },
|
|
13
|
+
validationStatus: {
|
|
14
|
+
control: { type: 'select' },
|
|
15
|
+
options: [undefined, 'error', 'success'],
|
|
16
|
+
},
|
|
17
|
+
standalone: { control: 'boolean' },
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const Template = (args) => html`
|
|
22
|
+
<ds-checkbox
|
|
23
|
+
?checked=${args.checked}
|
|
24
|
+
?indeterminate=${args.indeterminate}
|
|
25
|
+
?disabled=${args.disabled}
|
|
26
|
+
?required=${args.required}
|
|
27
|
+
.label=${args.label}
|
|
28
|
+
.validationStatus=${args.validationStatus}
|
|
29
|
+
?standalone=${args.standalone}
|
|
30
|
+
></ds-checkbox>
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export const Default = Template.bind({});
|
|
34
|
+
Default.args = {
|
|
35
|
+
label: 'Accept terms and conditions',
|
|
36
|
+
checked: false,
|
|
37
|
+
indeterminate: false,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Checked = Template.bind({});
|
|
41
|
+
Checked.args = {
|
|
42
|
+
label: 'Option selected',
|
|
43
|
+
checked: true,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Indeterminate = Template.bind({});
|
|
47
|
+
Indeterminate.args = {
|
|
48
|
+
label: 'Parent option',
|
|
49
|
+
indeterminate: true,
|
|
50
|
+
checked: false, // Indeterminate usually overrides checked visually
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Disabled = Template.bind({});
|
|
54
|
+
Disabled.args = {
|
|
55
|
+
label: 'This option is disabled',
|
|
56
|
+
disabled: true,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const DisabledChecked = Template.bind({});
|
|
60
|
+
DisabledChecked.args = {
|
|
61
|
+
label: 'Disabled and checked',
|
|
62
|
+
disabled: true,
|
|
63
|
+
checked: true,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const WithError = Template.bind({});
|
|
67
|
+
WithError.args = {
|
|
68
|
+
label: 'Checkbox with error',
|
|
69
|
+
validationStatus: 'error',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Standalone = Template.bind({});
|
|
73
|
+
Standalone.args = {
|
|
74
|
+
standalone: true,
|
|
75
|
+
checked: true,
|
|
76
|
+
label: 'Label hidden/ignored visually if standalone removes padding',
|
|
77
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import './ds-checkbox.js';
|
|
3
|
+
import '../ds-icon/ds-icon.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-checkbox', () => {
|
|
6
|
+
let container;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
container = document.createElement('div');
|
|
10
|
+
document.body.appendChild(container);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
container.remove();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders with default values', async () => {
|
|
18
|
+
container.innerHTML = '<ds-checkbox></ds-checkbox>';
|
|
19
|
+
const element = container.querySelector('ds-checkbox');
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
21
|
+
|
|
22
|
+
expect(element.checked).toBe(false);
|
|
23
|
+
expect(element.disabled).toBe(false);
|
|
24
|
+
expect(element.indeterminate).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders label when provided', async () => {
|
|
28
|
+
container.innerHTML = '<ds-checkbox label="Test Label"></ds-checkbox>';
|
|
29
|
+
const element = container.querySelector('ds-checkbox');
|
|
30
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
31
|
+
|
|
32
|
+
const label = element.shadowRoot.querySelector('.label');
|
|
33
|
+
expect(label).toBeTruthy();
|
|
34
|
+
expect(label.textContent).toBe('Test Label');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('reflects checked property to attribute', async () => {
|
|
38
|
+
container.innerHTML = '<ds-checkbox checked></ds-checkbox>';
|
|
39
|
+
const element = container.querySelector('ds-checkbox');
|
|
40
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
41
|
+
|
|
42
|
+
expect(element.hasAttribute('checked')).toBe(true);
|
|
43
|
+
const input = element.shadowRoot.querySelector('input');
|
|
44
|
+
expect(input.checked).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('handles click events to toggle state', async () => {
|
|
48
|
+
container.innerHTML = '<ds-checkbox></ds-checkbox>';
|
|
49
|
+
const element = container.querySelector('ds-checkbox');
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
51
|
+
|
|
52
|
+
const wrapper = element.shadowRoot.querySelector('.checkbox-wrapper');
|
|
53
|
+
|
|
54
|
+
// Click to check
|
|
55
|
+
wrapper.click();
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
57
|
+
expect(element.checked).toBe(true);
|
|
58
|
+
|
|
59
|
+
// Click to uncheck
|
|
60
|
+
wrapper.click();
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
62
|
+
expect(element.checked).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('handles indeterminate state correctly', async () => {
|
|
66
|
+
container.innerHTML = '<ds-checkbox indeterminate></ds-checkbox>';
|
|
67
|
+
const element = container.querySelector('ds-checkbox');
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
69
|
+
|
|
70
|
+
expect(element.indeterminate).toBe(true);
|
|
71
|
+
|
|
72
|
+
const icon = element.shadowRoot.querySelector('ds-icon');
|
|
73
|
+
expect(icon.getAttribute('name')).toBe('remove'); // Dash icon
|
|
74
|
+
|
|
75
|
+
const wrapper = element.shadowRoot.querySelector('.checkbox-wrapper');
|
|
76
|
+
|
|
77
|
+
// Click should clear indeterminate and set checked
|
|
78
|
+
wrapper.click();
|
|
79
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
80
|
+
|
|
81
|
+
expect(element.indeterminate).toBe(false);
|
|
82
|
+
expect(element.checked).toBe(true);
|
|
83
|
+
|
|
84
|
+
const newIcon = element.shadowRoot.querySelector('ds-icon');
|
|
85
|
+
expect(newIcon.getAttribute('name')).toBe('check');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('dispatches change event on interaction', async () => {
|
|
89
|
+
container.innerHTML = '<ds-checkbox></ds-checkbox>';
|
|
90
|
+
const element = container.querySelector('ds-checkbox');
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
92
|
+
|
|
93
|
+
const wrapper = element.shadowRoot.querySelector('.checkbox-wrapper');
|
|
94
|
+
|
|
95
|
+
let eventFired = false;
|
|
96
|
+
let eventDetail = null;
|
|
97
|
+
|
|
98
|
+
element.addEventListener('change', (e) => {
|
|
99
|
+
eventFired = true;
|
|
100
|
+
eventDetail = e.detail;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
wrapper.click();
|
|
104
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
105
|
+
|
|
106
|
+
expect(eventFired).toBe(true);
|
|
107
|
+
expect(eventDetail.checked).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('does not toggle when disabled', async () => {
|
|
111
|
+
container.innerHTML = '<ds-checkbox disabled></ds-checkbox>';
|
|
112
|
+
const element = container.querySelector('ds-checkbox');
|
|
113
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
114
|
+
|
|
115
|
+
const wrapper = element.shadowRoot.querySelector('.checkbox-wrapper');
|
|
116
|
+
|
|
117
|
+
wrapper.click();
|
|
118
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
119
|
+
|
|
120
|
+
expect(element.checked).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('sets name and value on internal input', async () => {
|
|
124
|
+
container.innerHTML = '<ds-checkbox name="test-checkbox" value="accepted"></ds-checkbox>';
|
|
125
|
+
const element = container.querySelector('ds-checkbox');
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
127
|
+
|
|
128
|
+
const input = element.shadowRoot.querySelector('input');
|
|
129
|
+
expect(input.name).toBe('test-checkbox');
|
|
130
|
+
expect(input.value).toBe('accepted');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('toggles state with Space key on set focus', async () => {
|
|
134
|
+
container.innerHTML = '<ds-checkbox></ds-checkbox>';
|
|
135
|
+
const element = container.querySelector('ds-checkbox');
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
137
|
+
|
|
138
|
+
const input = element.shadowRoot.querySelector('input');
|
|
139
|
+
input.focus();
|
|
140
|
+
|
|
141
|
+
// Dispatch Space keydown
|
|
142
|
+
const spaceEvent = new KeyboardEvent('keydown', {
|
|
143
|
+
key: ' ',
|
|
144
|
+
code: 'Space',
|
|
145
|
+
bubbles: true,
|
|
146
|
+
composed: true
|
|
147
|
+
});
|
|
148
|
+
input.dispatchEvent(spaceEvent);
|
|
149
|
+
|
|
150
|
+
// Simulating standard checkbox behavior where Space triggers a click
|
|
151
|
+
const clickEvent = new MouseEvent('click', {
|
|
152
|
+
bubbles: true,
|
|
153
|
+
composed: true,
|
|
154
|
+
cancelable: true
|
|
155
|
+
});
|
|
156
|
+
input.dispatchEvent(clickEvent);
|
|
157
|
+
|
|
158
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
159
|
+
expect(element.checked).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('clears indeterminate when clicked', async () => {
|
|
163
|
+
container.innerHTML = '<ds-checkbox indeterminate></ds-checkbox>';
|
|
164
|
+
const element = container.querySelector('ds-checkbox');
|
|
165
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
166
|
+
|
|
167
|
+
expect(element.indeterminate).toBe(true);
|
|
168
|
+
|
|
169
|
+
const wrapper = element.shadowRoot.querySelector('.checkbox-wrapper');
|
|
170
|
+
wrapper.click();
|
|
171
|
+
|
|
172
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
173
|
+
expect(element.indeterminate).toBe(false);
|
|
174
|
+
expect(element.checked).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('uses aria-label from host if provided', async () => {
|
|
178
|
+
container.innerHTML = '<ds-checkbox aria-label="custom accessible name"></ds-checkbox>';
|
|
179
|
+
const element = container.querySelector('ds-checkbox');
|
|
180
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
181
|
+
|
|
182
|
+
const input = element.shadowRoot.querySelector('input');
|
|
183
|
+
expect(input.getAttribute('aria-label')).toBe('custom accessible name');
|
|
184
|
+
});
|
|
185
|
+
it('reflects standalone property to attribute', async () => {
|
|
186
|
+
container.innerHTML = '<ds-checkbox standalone></ds-checkbox>';
|
|
187
|
+
const element = container.querySelector('ds-checkbox');
|
|
188
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
189
|
+
expect(element.hasAttribute('standalone')).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DsCheckbox } from './ds-checkbox.js';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-checkbox-group.js';
|
|
4
|
+
import '../ds-checkbox/ds-checkbox.js';
|
|
5
|
+
|
|
6
|
+
describe('ds-checkbox-group a11y', () => {
|
|
7
|
+
let container;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
container = document.createElement('div');
|
|
11
|
+
document.body.appendChild(container);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
container.remove();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should pass axe accessibility checks with label and children', async () => {
|
|
19
|
+
container.innerHTML = `
|
|
20
|
+
<ds-checkbox-group label="Preferences">
|
|
21
|
+
<ds-checkbox label="Option A" value="a"></ds-checkbox>
|
|
22
|
+
<ds-checkbox label="Option B" value="b"></ds-checkbox>
|
|
23
|
+
<ds-checkbox label="Option C" value="c"></ds-checkbox>
|
|
24
|
+
</ds-checkbox-group>
|
|
25
|
+
`;
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
27
|
+
|
|
28
|
+
const results = await axe.run(container, {
|
|
29
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
30
|
+
});
|
|
31
|
+
if (results.violations.length > 0) {
|
|
32
|
+
console.log('Violations:', JSON.stringify(results.violations, null, 2));
|
|
33
|
+
}
|
|
34
|
+
expect(results.violations).toHaveLength(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should pass axe accessibility checks with helper text', async () => {
|
|
38
|
+
container.innerHTML = `
|
|
39
|
+
<ds-checkbox-group label="Features" helper="Select all that apply">
|
|
40
|
+
<ds-checkbox label="Feature 1" value="1"></ds-checkbox>
|
|
41
|
+
<ds-checkbox label="Feature 2" value="2"></ds-checkbox>
|
|
42
|
+
</ds-checkbox-group>
|
|
43
|
+
`;
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
45
|
+
|
|
46
|
+
const results = await axe.run(container, {
|
|
47
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
48
|
+
});
|
|
49
|
+
expect(results.violations).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should pass axe accessibility checks in error state', async () => {
|
|
53
|
+
container.innerHTML = `
|
|
54
|
+
<ds-checkbox-group
|
|
55
|
+
label="Required selections"
|
|
56
|
+
validation-status="error"
|
|
57
|
+
validation-message="Please select at least one option"
|
|
58
|
+
>
|
|
59
|
+
<ds-checkbox label="Option A" value="a"></ds-checkbox>
|
|
60
|
+
<ds-checkbox label="Option B" value="b"></ds-checkbox>
|
|
61
|
+
</ds-checkbox-group>
|
|
62
|
+
`;
|
|
63
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
64
|
+
|
|
65
|
+
const results = await axe.run(container, {
|
|
66
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
67
|
+
});
|
|
68
|
+
expect(results.violations).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should pass axe accessibility checks when disabled', async () => {
|
|
72
|
+
container.innerHTML = `
|
|
73
|
+
<ds-checkbox-group label="Disabled options" disabled>
|
|
74
|
+
<ds-checkbox label="Option A" value="a"></ds-checkbox>
|
|
75
|
+
<ds-checkbox label="Option B" value="b"></ds-checkbox>
|
|
76
|
+
</ds-checkbox-group>
|
|
77
|
+
`;
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
79
|
+
|
|
80
|
+
const results = await axe.run(container, {
|
|
81
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
82
|
+
});
|
|
83
|
+
expect(results.violations).toHaveLength(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should pass axe accessibility checks with horizontal orientation', async () => {
|
|
87
|
+
container.innerHTML = `
|
|
88
|
+
<ds-checkbox-group label="Inline options" orientation="horizontal">
|
|
89
|
+
<ds-checkbox label="Option A" value="a"></ds-checkbox>
|
|
90
|
+
<ds-checkbox label="Option B" value="b"></ds-checkbox>
|
|
91
|
+
</ds-checkbox-group>
|
|
92
|
+
`;
|
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
94
|
+
|
|
95
|
+
const results = await axe.run(container, {
|
|
96
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
97
|
+
});
|
|
98
|
+
expect(results.violations).toHaveLength(0);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should pass axe accessibility checks with some items checked', async () => {
|
|
102
|
+
container.innerHTML = `
|
|
103
|
+
<ds-checkbox-group label="Pre-selected options">
|
|
104
|
+
<ds-checkbox label="Option A" value="a" checked></ds-checkbox>
|
|
105
|
+
<ds-checkbox label="Option B" value="b"></ds-checkbox>
|
|
106
|
+
<ds-checkbox label="Option C" value="c" checked></ds-checkbox>
|
|
107
|
+
</ds-checkbox-group>
|
|
108
|
+
`;
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
110
|
+
|
|
111
|
+
const results = await axe.run(container, {
|
|
112
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
113
|
+
});
|
|
114
|
+
expect(results.violations).toHaveLength(0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should have proper group role attribute', async () => {
|
|
118
|
+
container.innerHTML = `
|
|
119
|
+
<ds-checkbox-group label="Test group">
|
|
120
|
+
<ds-checkbox label="Option A" value="a"></ds-checkbox>
|
|
121
|
+
</ds-checkbox-group>
|
|
122
|
+
`;
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
124
|
+
|
|
125
|
+
const group = container.querySelector('ds-checkbox-group');
|
|
126
|
+
const groupContent = group.shadowRoot.querySelector('.group-content');
|
|
127
|
+
|
|
128
|
+
expect(groupContent.getAttribute('role')).toBe('group');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should have aria-labelledby referencing the label', async () => {
|
|
132
|
+
container.innerHTML = `
|
|
133
|
+
<ds-checkbox-group label="Labeled group">
|
|
134
|
+
<ds-checkbox label="Option A" value="a"></ds-checkbox>
|
|
135
|
+
</ds-checkbox-group>
|
|
136
|
+
`;
|
|
137
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
138
|
+
|
|
139
|
+
const group = container.querySelector('ds-checkbox-group');
|
|
140
|
+
const groupContent = group.shadowRoot.querySelector('.group-content');
|
|
141
|
+
const labelId = groupContent.getAttribute('aria-labelledby');
|
|
142
|
+
|
|
143
|
+
expect(labelId).toBeTruthy();
|
|
144
|
+
expect(group.shadowRoot.getElementById(labelId)).toBeTruthy();
|
|
145
|
+
});
|
|
146
|
+
});
|