@redvars/peacock 3.3.2 → 3.4.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/dist/IndividualComponent-DUINtMGK.js +67 -0
- package/dist/IndividualComponent-DUINtMGK.js.map +1 -0
- package/dist/assets/images/empty-state/no-document.svg +11 -12
- package/dist/assets/images/empty-state/page.svg +15 -9
- package/dist/bottom-sheet.js +238 -0
- package/dist/bottom-sheet.js.map +1 -0
- package/dist/{button-ClzS8JLq.js → button-COYCtuA8.js} +306 -149
- package/dist/button-COYCtuA8.js.map +1 -0
- package/dist/button-group-DsXquZQn.js +440 -0
- package/dist/button-group-DsXquZQn.js.map +1 -0
- package/dist/button-group.js +6 -4
- package/dist/button-group.js.map +1 -1
- package/dist/button.js +5 -3
- package/dist/button.js.map +1 -1
- package/dist/card-content.js +29 -0
- package/dist/card-content.js.map +1 -0
- package/dist/card.js +418 -44
- package/dist/card.js.map +1 -1
- package/dist/{chart-bar-DbnXQgvS.js → chart-bar-cn6rrna-.js} +2 -2
- package/dist/{chart-bar-DbnXQgvS.js.map → chart-bar-cn6rrna-.js.map} +1 -1
- package/dist/chart-bar.js +4 -3
- package/dist/chart-bar.js.map +1 -1
- package/dist/chart-doughnut.js +2 -1
- package/dist/chart-doughnut.js.map +1 -1
- package/dist/chart-pie.js +2 -1
- package/dist/chart-pie.js.map +1 -1
- package/dist/chart-stacked-bar.js +4 -3
- package/dist/chart-stacked-bar.js.map +1 -1
- package/dist/{class-map-59YGWLnx.js → class-map-3TAnCMAX.js} +3 -9
- package/dist/class-map-3TAnCMAX.js.map +1 -0
- package/dist/clock.js +2 -1
- package/dist/clock.js.map +1 -1
- package/dist/code-editor.js +6 -4
- package/dist/code-editor.js.map +1 -1
- package/dist/code-highlighter.js +5 -3
- package/dist/code-highlighter.js.map +1 -1
- package/dist/custom-elements-jsdocs.json +2458 -2753
- package/dist/custom-elements.json +3185 -777
- package/dist/dispatch-event-utils-B4odODQf.js.map +1 -1
- package/dist/index.js +14 -10
- package/dist/index.js.map +1 -1
- package/dist/number-counter.js +3 -2
- package/dist/number-counter.js.map +1 -1
- package/dist/{observe-theme-change-pALI5fmV.js → observe-theme-change-DKAIv5BB.js} +3 -2
- package/dist/observe-theme-change-DKAIv5BB.js.map +1 -0
- package/dist/peacock-loader.js +37 -8
- package/dist/peacock-loader.js.map +1 -1
- package/dist/property-1psGvXOq.js +10 -0
- package/dist/property-1psGvXOq.js.map +1 -0
- package/dist/{snackbar-74YCdMPL.js → select-C3XAzenC.js} +2158 -191
- package/dist/select-C3XAzenC.js.map +1 -0
- package/dist/side-sheet.js +186 -0
- package/dist/side-sheet.js.map +1 -0
- package/dist/src/bottom-sheet/bottom-sheet.d.ts +42 -0
- package/dist/src/bottom-sheet/index.d.ts +1 -0
- package/dist/src/button/BaseButton.d.ts +4 -3
- package/dist/src/button/button/button.d.ts +4 -0
- package/dist/src/button/button-group/button-group.d.ts +32 -3
- package/dist/src/button/icon-button/icon-button.d.ts +4 -0
- package/dist/src/card/card-content.d.ts +15 -0
- package/dist/src/card/card.d.ts +37 -3
- package/dist/src/card/index.d.ts +1 -0
- package/dist/src/container/container.d.ts +1 -1
- package/dist/src/empty-state/empty-state.d.ts +1 -1
- package/dist/src/focus-ring/focus-ring.d.ts +4 -1
- package/dist/src/index.d.ts +7 -1
- package/dist/src/menu/menu/menu.d.ts +1 -0
- package/dist/src/menu/menu-item/menu-item.d.ts +0 -1
- package/dist/src/radio/index.d.ts +1 -0
- package/dist/src/radio/radio.d.ts +73 -0
- package/dist/src/ripple/ripple.d.ts +19 -3
- package/dist/src/segmented-button/index.d.ts +2 -0
- package/dist/src/segmented-button/segmented-button-group.d.ts +46 -0
- package/dist/src/segmented-button/segmented-button.d.ts +65 -0
- package/dist/src/select/index.d.ts +3 -0
- package/dist/src/select/option.d.ts +55 -0
- package/dist/src/select/select.d.ts +116 -0
- package/dist/src/side-sheet/index.d.ts +1 -0
- package/dist/src/side-sheet/side-sheet.d.ts +41 -0
- package/dist/src/tabs/tab-group.d.ts +0 -1
- package/dist/src/tabs/tab.d.ts +8 -2
- package/dist/src/tabs/tabs.d.ts +13 -1
- package/dist/state-DwbEjqVk.js +10 -0
- package/dist/state-DwbEjqVk.js.map +1 -0
- package/dist/{style-map-DcB52w-l.js → style-map-CRFEoCEg.js} +2 -2
- package/dist/{style-map-DcB52w-l.js.map → style-map-CRFEoCEg.js.map} +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/{unsafe-html-C2r3PyzF.js → unsafe-html-D3GHRaGQ.js} +2 -2
- package/dist/{unsafe-html-C2r3PyzF.js.map → unsafe-html-D3GHRaGQ.js.map} +1 -1
- package/package.json +1 -1
- package/readme.md +2 -2
- package/src/bottom-sheet/bottom-sheet.scss +88 -0
- package/src/bottom-sheet/bottom-sheet.ts +135 -0
- package/src/bottom-sheet/index.ts +1 -0
- package/src/button/BaseButton.ts +16 -7
- package/src/button/button/button-colors.scss +76 -5
- package/src/button/button/button-sizes.scss +39 -19
- package/src/button/button/button.scss +117 -116
- package/src/button/button/button.ts +23 -1
- package/src/button/button-group/button-group.scss +25 -22
- package/src/button/button-group/button-group.ts +121 -4
- package/src/button/icon-button/icon-button-sizes.scss +35 -15
- package/src/button/icon-button/icon-button.ts +21 -1
- package/src/card/card-colors.scss +10 -0
- package/src/card/card-content.ts +26 -0
- package/src/card/card.scss +221 -41
- package/src/card/card.ts +240 -7
- package/src/card/index.ts +1 -0
- package/src/code-editor/code-editor.ts +1 -1
- package/src/container/container.ts +1 -1
- package/src/empty-state/empty-state.scss +8 -0
- package/src/empty-state/empty-state.ts +2 -2
- package/src/focus-ring/focus-ring.ts +37 -19
- package/src/index.ts +8 -1
- package/src/menu/menu/menu.scss +24 -3
- package/src/menu/menu/menu.ts +23 -2
- package/src/menu/menu-item/menu-item.scss +1 -0
- package/src/menu/menu-item/menu-item.ts +1 -9
- package/src/peacock-loader.ts +32 -0
- package/src/radio/index.ts +1 -0
- package/src/radio/radio.scss +181 -0
- package/src/radio/radio.ts +362 -0
- package/src/ripple/ripple.ts +19 -3
- package/src/segmented-button/index.ts +2 -0
- package/src/segmented-button/segmented-button-group.scss +21 -0
- package/src/segmented-button/segmented-button-group.ts +110 -0
- package/src/segmented-button/segmented-button.scss +115 -0
- package/src/segmented-button/segmented-button.ts +175 -0
- package/src/select/index.ts +3 -0
- package/src/select/option.ts +109 -0
- package/src/select/select.scss +120 -0
- package/src/select/select.ts +486 -0
- package/src/side-sheet/index.ts +1 -0
- package/src/side-sheet/side-sheet.scss +79 -0
- package/src/side-sheet/side-sheet.ts +100 -0
- package/src/slider/slider.scss +0 -1
- package/src/tabs/demo/index.html +90 -0
- package/src/tabs/tab-group.ts +0 -3
- package/src/tabs/tab.scss +237 -25
- package/src/tabs/tab.ts +86 -12
- package/src/tabs/tabs.scss +37 -3
- package/src/tabs/tabs.ts +118 -2
- package/src/utils/dispatch-event-utils.ts +1 -0
- package/dist/IndividualComponent-Dt5xirYG.js +0 -73
- package/dist/IndividualComponent-Dt5xirYG.js.map +0 -1
- package/dist/button-ClzS8JLq.js.map +0 -1
- package/dist/button-group-BMS5WvaF.js +0 -292
- package/dist/button-group-BMS5WvaF.js.map +0 -1
- package/dist/chart-donut.js +0 -309
- package/dist/chart-donut.js.map +0 -1
- package/dist/class-map-59YGWLnx.js.map +0 -1
- package/dist/observe-theme-change-pALI5fmV.js.map +0 -1
- package/dist/snackbar-74YCdMPL.js.map +0 -1
- package/dist/src/chart-donut/chart-donut.d.ts +0 -53
- package/dist/src/chart-donut/index.d.ts +0 -1
- package/dist/test/card.test.d.ts +0 -1
- package/src/chart-donut/chart-donut.scss +0 -37
- package/src/chart-donut/chart-donut.ts +0 -287
- package/src/chart-donut/demo/index.html +0 -51
- package/src/chart-donut/index.ts +0 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
@use '../../scss/mixin';
|
|
2
|
+
|
|
3
|
+
@include mixin.base-styles;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
display: inline-block;
|
|
7
|
+
--radio-size: 20px;
|
|
8
|
+
--radio-dot-size: 12px;
|
|
9
|
+
--radio-selected-color: var(--color-primary);
|
|
10
|
+
--radio-unselected-color: var(--color-on-surface-variant);
|
|
11
|
+
--radio-state-layer-size: 40px;
|
|
12
|
+
--radio-disabled-opacity: 0.38;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.radio {
|
|
16
|
+
position: relative;
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
user-select: none;
|
|
21
|
+
vertical-align: middle;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
|
|
24
|
+
@include mixin.get-typography('body-medium');
|
|
25
|
+
|
|
26
|
+
.input-native {
|
|
27
|
+
position: absolute;
|
|
28
|
+
width: 1px;
|
|
29
|
+
height: 1px;
|
|
30
|
+
padding: 0;
|
|
31
|
+
margin: -1px;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
clip: rect(0, 0, 0, 0);
|
|
34
|
+
border: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.container {
|
|
38
|
+
position: relative;
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
width: var(--radio-state-layer-size);
|
|
43
|
+
height: var(--radio-state-layer-size);
|
|
44
|
+
cursor: inherit;
|
|
45
|
+
outline: none;
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.state-layer {
|
|
50
|
+
position: absolute;
|
|
51
|
+
inset: 0;
|
|
52
|
+
border-radius: 50%;
|
|
53
|
+
opacity: 0;
|
|
54
|
+
transition: opacity var(--duration-short2) var(--easing-standard);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.outer-circle {
|
|
58
|
+
position: absolute;
|
|
59
|
+
width: var(--radio-size);
|
|
60
|
+
height: var(--radio-size);
|
|
61
|
+
border: 2px solid var(--radio-unselected-color);
|
|
62
|
+
border-radius: 50%;
|
|
63
|
+
box-sizing: border-box;
|
|
64
|
+
transition: border-color var(--duration-short2) var(--easing-standard);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.inner-circle {
|
|
68
|
+
position: absolute;
|
|
69
|
+
width: var(--radio-dot-size);
|
|
70
|
+
height: var(--radio-dot-size);
|
|
71
|
+
background: var(--radio-selected-color);
|
|
72
|
+
border-radius: 50%;
|
|
73
|
+
opacity: 0;
|
|
74
|
+
transform: scale(0.5);
|
|
75
|
+
transition:
|
|
76
|
+
transform var(--duration-short2) var(--easing-standard),
|
|
77
|
+
opacity var(--duration-short2) var(--easing-standard);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.label {
|
|
81
|
+
color: var(--color-on-surface);
|
|
82
|
+
cursor: inherit;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Hover state
|
|
86
|
+
&:hover:not(.disabled):not(.readonly) {
|
|
87
|
+
.state-layer {
|
|
88
|
+
opacity: 0.08;
|
|
89
|
+
background: var(--color-on-surface);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&.state-checked .state-layer {
|
|
93
|
+
background: var(--radio-selected-color);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Focus state
|
|
98
|
+
&.has-focus:not(.active):not(.disabled):not(.readonly) {
|
|
99
|
+
.state-layer {
|
|
100
|
+
opacity: 0.12;
|
|
101
|
+
background: var(--color-on-surface);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.container {
|
|
105
|
+
outline: 3px solid var(--color-primary);
|
|
106
|
+
outline-offset: 2px;
|
|
107
|
+
border-radius: 50%;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&.state-checked .state-layer {
|
|
111
|
+
background: var(--radio-selected-color);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Active/pressed state
|
|
116
|
+
&.active:not(.disabled):not(.readonly) {
|
|
117
|
+
.state-layer {
|
|
118
|
+
opacity: 0.12;
|
|
119
|
+
background: var(--color-on-surface);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&.state-checked .state-layer {
|
|
123
|
+
background: var(--radio-selected-color);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Checked state
|
|
129
|
+
.radio.state-checked {
|
|
130
|
+
.outer-circle {
|
|
131
|
+
border-color: var(--radio-selected-color);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.inner-circle {
|
|
135
|
+
opacity: 1;
|
|
136
|
+
transform: scale(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Readonly state
|
|
141
|
+
.radio.readonly {
|
|
142
|
+
cursor: default;
|
|
143
|
+
|
|
144
|
+
.state-layer {
|
|
145
|
+
display: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.outer-circle {
|
|
149
|
+
border-color: var(--color-on-surface-variant);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&.state-checked {
|
|
153
|
+
.inner-circle {
|
|
154
|
+
background: var(--color-on-surface-variant);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Disabled state
|
|
160
|
+
.radio.disabled {
|
|
161
|
+
cursor: not-allowed;
|
|
162
|
+
opacity: var(--radio-disabled-opacity);
|
|
163
|
+
|
|
164
|
+
.state-layer {
|
|
165
|
+
display: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.label {
|
|
169
|
+
color: var(--color-on-surface);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.outer-circle {
|
|
173
|
+
border-color: var(--color-on-surface);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
&.state-checked {
|
|
177
|
+
.inner-circle {
|
|
178
|
+
background: var(--color-on-surface);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit';
|
|
2
|
+
import { property, query, state } from 'lit/decorators.js';
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
4
|
+
import { spread } from '../spread.js';
|
|
5
|
+
import styles from './radio.scss';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @label Radio
|
|
9
|
+
* @tag wc-radio
|
|
10
|
+
* @rawTag radio
|
|
11
|
+
* @summary Allows selection of a single option from a set.
|
|
12
|
+
* @overview
|
|
13
|
+
* <p>Radio buttons follow the Material Design 3 specifications with clear focus, hover, and selected states.</p>
|
|
14
|
+
*
|
|
15
|
+
* @cssprop --radio-size: Size of the outer radio circle.
|
|
16
|
+
* @cssprop --radio-dot-size: Size of the inner dot when selected.
|
|
17
|
+
* @cssprop --radio-selected-color: Color of the radio when selected.
|
|
18
|
+
* @cssprop --radio-unselected-color: Color of the radio outline when unselected.
|
|
19
|
+
* @cssprop --radio-state-layer-size: Size of the state layer for touch target.
|
|
20
|
+
* @cssprop --radio-disabled-opacity: Opacity applied when the radio is disabled.
|
|
21
|
+
*
|
|
22
|
+
* @fires {CustomEvent} change - Dispatched when the radio selection changes.
|
|
23
|
+
* @fires {CustomEvent} blur - Dispatched when the radio loses focus.
|
|
24
|
+
* @fires {CustomEvent} focus - Dispatched when the radio receives focus.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```html
|
|
28
|
+
* <wc-radio name="fruits" value="apple" label="Apple"></wc-radio>
|
|
29
|
+
* ```
|
|
30
|
+
* @tags input, form
|
|
31
|
+
*/
|
|
32
|
+
type RadioDirection = 1 | -1;
|
|
33
|
+
|
|
34
|
+
export class Radio extends LitElement {
|
|
35
|
+
private static readonly DIRECTION_NEXT: RadioDirection = 1;
|
|
36
|
+
private static readonly DIRECTION_PREVIOUS: RadioDirection = -1;
|
|
37
|
+
|
|
38
|
+
static styles = [styles];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The input field name for grouping radios.
|
|
42
|
+
*/
|
|
43
|
+
@property({ type: String })
|
|
44
|
+
name: string = '';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The submitted value when this radio is selected.
|
|
48
|
+
*/
|
|
49
|
+
@property({ type: String })
|
|
50
|
+
value: string = '';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The radio label.
|
|
54
|
+
*/
|
|
55
|
+
@property({ type: String })
|
|
56
|
+
label: string = '';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether the radio is selected.
|
|
60
|
+
*/
|
|
61
|
+
@property({ type: Boolean, reflect: true })
|
|
62
|
+
checked: boolean = false;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* If true, required icon is shown. Defaults to `false`.
|
|
66
|
+
*/
|
|
67
|
+
@property({ type: Boolean, reflect: true })
|
|
68
|
+
required: boolean = false;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* If true, the radio is readonly. Defaults to `false`.
|
|
72
|
+
*/
|
|
73
|
+
@property({ type: Boolean, reflect: true })
|
|
74
|
+
readonly: boolean = false;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* If true, the user cannot interact with the radio. Defaults to `false`.
|
|
78
|
+
*/
|
|
79
|
+
@property({ type: Boolean, reflect: true })
|
|
80
|
+
disabled: boolean = false;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Configuration object for aria attributes.
|
|
84
|
+
*/
|
|
85
|
+
@property({ type: Object })
|
|
86
|
+
configAria: Record<string, string> = {};
|
|
87
|
+
|
|
88
|
+
@state()
|
|
89
|
+
private hasFocus = false;
|
|
90
|
+
|
|
91
|
+
@state()
|
|
92
|
+
private isActive = false;
|
|
93
|
+
|
|
94
|
+
@state()
|
|
95
|
+
private slotHasContent = false;
|
|
96
|
+
|
|
97
|
+
@state()
|
|
98
|
+
private isGroupFocusTarget = false;
|
|
99
|
+
|
|
100
|
+
@query('.container')
|
|
101
|
+
private containerElement?: HTMLElement;
|
|
102
|
+
|
|
103
|
+
@query('.input-native')
|
|
104
|
+
private nativeElement?: HTMLInputElement;
|
|
105
|
+
|
|
106
|
+
private tabindex?: number;
|
|
107
|
+
|
|
108
|
+
connectedCallback() {
|
|
109
|
+
super.connectedCallback();
|
|
110
|
+
this.handleInitialAttributes();
|
|
111
|
+
window.addEventListener('mouseup', this.windowMouseUp);
|
|
112
|
+
window.addEventListener('keyup', this.windowKeyUp);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
disconnectedCallback() {
|
|
116
|
+
super.disconnectedCallback();
|
|
117
|
+
window.removeEventListener('mouseup', this.windowMouseUp);
|
|
118
|
+
window.removeEventListener('keyup', this.windowKeyUp);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
firstUpdated() {
|
|
122
|
+
this.slotHasContent = this.hasChildNodes() || !!this.label;
|
|
123
|
+
this.updateGroupFocusTarget();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
updated(changedProps: Map<string, unknown>) {
|
|
127
|
+
if (changedProps.has('label')) {
|
|
128
|
+
this.slotHasContent = this.hasChildNodes() || !!this.label;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (changedProps.has('checked') && this.checked) {
|
|
132
|
+
this.uncheckSiblings();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (changedProps.has('checked') || changedProps.has('name')) {
|
|
136
|
+
this.updateGroupFocusTarget();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private handleInitialAttributes() {
|
|
141
|
+
if (this.hasAttribute('tabindex')) {
|
|
142
|
+
const attrValue = this.getAttribute('tabindex');
|
|
143
|
+
if (attrValue !== null) {
|
|
144
|
+
this.tabindex = parseInt(attrValue, 10);
|
|
145
|
+
}
|
|
146
|
+
this.removeAttribute('tabindex');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Array.from(this.attributes).forEach(attr => {
|
|
150
|
+
if (attr.name.startsWith('aria-')) {
|
|
151
|
+
this.configAria[attr.name] = attr.value;
|
|
152
|
+
this.removeAttribute(attr.name);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private windowMouseUp = () => {
|
|
158
|
+
if (this.isActive) {
|
|
159
|
+
this.isActive = false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
private windowKeyUp = (evt: KeyboardEvent) => {
|
|
164
|
+
if (this.isActive && evt.key === ' ') {
|
|
165
|
+
evt.preventDefault();
|
|
166
|
+
this.isActive = false;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
private mouseDownHandler = () => {
|
|
171
|
+
this.isActive = true;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
private keyDownHandler = (evt: KeyboardEvent) => {
|
|
175
|
+
if (evt.key === ' ') {
|
|
176
|
+
evt.preventDefault();
|
|
177
|
+
this.isActive = true;
|
|
178
|
+
this.selectRadio(evt);
|
|
179
|
+
} else if (evt.key === 'ArrowRight' || evt.key === 'ArrowDown') {
|
|
180
|
+
evt.preventDefault();
|
|
181
|
+
this.navigateGroup(Radio.DIRECTION_NEXT);
|
|
182
|
+
} else if (evt.key === 'ArrowLeft' || evt.key === 'ArrowUp') {
|
|
183
|
+
evt.preventDefault();
|
|
184
|
+
this.navigateGroup(Radio.DIRECTION_PREVIOUS);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
private clickHandler = (ev: MouseEvent | KeyboardEvent) => {
|
|
189
|
+
this.selectRadio(ev);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
private blurHandler = (ev: FocusEvent) => {
|
|
193
|
+
this.hasFocus = false;
|
|
194
|
+
this.dispatchEvent(
|
|
195
|
+
new CustomEvent('blur', {
|
|
196
|
+
detail: ev,
|
|
197
|
+
bubbles: true,
|
|
198
|
+
composed: true,
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
private focusHandler = (ev: FocusEvent) => {
|
|
204
|
+
this.hasFocus = true;
|
|
205
|
+
this.dispatchEvent(
|
|
206
|
+
new CustomEvent('focus', {
|
|
207
|
+
detail: ev,
|
|
208
|
+
bubbles: true,
|
|
209
|
+
composed: true,
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
private selectRadio(ev?: Event) {
|
|
215
|
+
if (this.disabled || this.readonly) return;
|
|
216
|
+
|
|
217
|
+
if (!this.checked) {
|
|
218
|
+
this.checked = true;
|
|
219
|
+
this.uncheckSiblings();
|
|
220
|
+
this.dispatchChange(ev);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.containerElement?.focus();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private dispatchChange(ev?: Event) {
|
|
227
|
+
this.dispatchEvent(
|
|
228
|
+
new CustomEvent('change', {
|
|
229
|
+
detail: { value: this.value, checked: this.checked, originalEvent: ev },
|
|
230
|
+
bubbles: true,
|
|
231
|
+
composed: true,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private uncheckSiblings() {
|
|
237
|
+
if (!this.name || !this.checked) return;
|
|
238
|
+
const radios = this.getGroupRadios();
|
|
239
|
+
|
|
240
|
+
radios.forEach(radio => {
|
|
241
|
+
if (radio === this) return;
|
|
242
|
+
radio.checked = false;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private getGroupRadios(): Radio[] {
|
|
247
|
+
if (!this.name) return [this];
|
|
248
|
+
const scopeRoot = this.closest('form') ?? document;
|
|
249
|
+
return Array.from(
|
|
250
|
+
scopeRoot.querySelectorAll<Radio>(`wc-radio[name="${this.name}"]`),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private isRadioEnabled(radio: Radio) {
|
|
255
|
+
return !radio.disabled && !radio.readonly;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private updateGroupFocusTarget() {
|
|
259
|
+
const group = this.getGroupRadios();
|
|
260
|
+
if (!group.length) return;
|
|
261
|
+
|
|
262
|
+
const enabledGroup = group.filter(radio => this.isRadioEnabled(radio));
|
|
263
|
+
if (!enabledGroup.length) {
|
|
264
|
+
group.forEach(radio => {
|
|
265
|
+
radio.isGroupFocusTarget = false;
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const target =
|
|
271
|
+
enabledGroup.find(radio => radio.checked) || enabledGroup[0];
|
|
272
|
+
|
|
273
|
+
group.forEach(radio => {
|
|
274
|
+
radio.isGroupFocusTarget = radio === target;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private navigateGroup(direction: RadioDirection) {
|
|
279
|
+
const group = this.getGroupRadios().filter(radio =>
|
|
280
|
+
this.isRadioEnabled(radio),
|
|
281
|
+
);
|
|
282
|
+
if (!group.length) return;
|
|
283
|
+
|
|
284
|
+
const currentIndex = group.indexOf(this);
|
|
285
|
+
const startIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
286
|
+
const nextIndex = (startIndex + direction + group.length) % group.length;
|
|
287
|
+
const target = group[nextIndex];
|
|
288
|
+
|
|
289
|
+
target.selectRadio();
|
|
290
|
+
target.containerElement?.focus();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Sets focus on the radio.
|
|
295
|
+
*/
|
|
296
|
+
focus() {
|
|
297
|
+
this.containerElement?.focus();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Removes focus from the radio.
|
|
302
|
+
*/
|
|
303
|
+
blur() {
|
|
304
|
+
this.containerElement?.blur();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
render() {
|
|
308
|
+
const cssClasses = {
|
|
309
|
+
radio: true,
|
|
310
|
+
'state-checked': this.checked,
|
|
311
|
+
'has-focus': this.hasFocus,
|
|
312
|
+
active: this.isActive,
|
|
313
|
+
disabled: this.disabled,
|
|
314
|
+
readonly: this.readonly,
|
|
315
|
+
required: this.required,
|
|
316
|
+
'has-content': this.slotHasContent,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return html`
|
|
320
|
+
<label class=${classMap(cssClasses)}>
|
|
321
|
+
<div
|
|
322
|
+
class="container"
|
|
323
|
+
tabindex=${this.isGroupFocusTarget
|
|
324
|
+
? this.tabindex !== undefined
|
|
325
|
+
? this.tabindex
|
|
326
|
+
: 0
|
|
327
|
+
: -1}
|
|
328
|
+
role="radio"
|
|
329
|
+
aria-disabled=${this.disabled}
|
|
330
|
+
aria-required=${this.required}
|
|
331
|
+
aria-checked=${this.checked}
|
|
332
|
+
@click=${this.clickHandler}
|
|
333
|
+
@mousedown=${this.mouseDownHandler}
|
|
334
|
+
@keydown=${this.keyDownHandler}
|
|
335
|
+
@blur=${this.blurHandler}
|
|
336
|
+
@focus=${this.focusHandler}
|
|
337
|
+
${spread(this.configAria)}
|
|
338
|
+
>
|
|
339
|
+
<div class="state-layer"></div>
|
|
340
|
+
<div class="outer-circle"></div>
|
|
341
|
+
<div class="inner-circle"></div>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<input
|
|
345
|
+
type="radio"
|
|
346
|
+
class="input-native"
|
|
347
|
+
name=${this.name}
|
|
348
|
+
.value=${this.value}
|
|
349
|
+
.checked=${this.checked}
|
|
350
|
+
aria-hidden="true"
|
|
351
|
+
?required=${this.required}
|
|
352
|
+
?disabled=${this.disabled}
|
|
353
|
+
tabindex="-1"
|
|
354
|
+
/>
|
|
355
|
+
|
|
356
|
+
${this.label
|
|
357
|
+
? html`<div class="label">${this.label}</div>`
|
|
358
|
+
: html`<div class="label slot-container"><slot></slot></div>`}
|
|
359
|
+
</label>
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
}
|
package/src/ripple/ripple.ts
CHANGED
|
@@ -91,10 +91,26 @@ const FORCED_COLORS = window.matchMedia('(forced-colors: active)');
|
|
|
91
91
|
*
|
|
92
92
|
* @example
|
|
93
93
|
* ```html
|
|
94
|
-
* <
|
|
94
|
+
* <style>
|
|
95
|
+
* .ripple-surface {
|
|
96
|
+
* position: relative;
|
|
97
|
+
* display: inline-flex;
|
|
98
|
+
* align-items: center;
|
|
99
|
+
* justify-content: center;
|
|
100
|
+
* width: 220px;
|
|
101
|
+
* height: 64px;
|
|
102
|
+
* border-radius: 12px;
|
|
103
|
+
* background: var(--color-surface-container-high);
|
|
104
|
+
* color: var(--color-on-surface);
|
|
105
|
+
* overflow: hidden;
|
|
106
|
+
* cursor: pointer;
|
|
107
|
+
* user-select: none;
|
|
108
|
+
* }
|
|
109
|
+
* </style>
|
|
110
|
+
* <div class="ripple-surface">
|
|
95
111
|
* <wc-ripple></wc-ripple>
|
|
96
|
-
*
|
|
97
|
-
* </
|
|
112
|
+
* Ripple Effect
|
|
113
|
+
* </div>
|
|
98
114
|
* ```
|
|
99
115
|
* @tags display
|
|
100
116
|
*/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@use '../../scss/mixin';
|
|
2
|
+
|
|
3
|
+
@include mixin.base-styles;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
--_segmented-button-group-shape: var(--shape-corner-full, 9999px);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.segmented-button-group {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: stretch;
|
|
13
|
+
border-radius: var(--segmented-button-group-shape, var(--_segmented-button-group-shape));
|
|
14
|
+
border: 1px solid var(--segmented-button-outline-color, var(--color-outline, #79747e));
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
min-width: max-content;
|
|
17
|
+
|
|
18
|
+
::slotted(wc-segmented-button) {
|
|
19
|
+
flex: 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit';
|
|
2
|
+
import { property } from 'lit/decorators.js';
|
|
3
|
+
import styles from './segmented-button-group.scss';
|
|
4
|
+
import { SegmentedButton } from './segmented-button.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @label Segmented Button Group
|
|
8
|
+
* @tag wc-segmented-button-group
|
|
9
|
+
* @rawTag segmented-button-group
|
|
10
|
+
* @summary A container for segmented buttons following Material Design 3.
|
|
11
|
+
* @overview
|
|
12
|
+
* <p>Segmented buttons help people select options, switch views, or sort elements. They follow the Material Design 3 specification.</p>
|
|
13
|
+
* <p>Use <code>multi-select</code> to allow more than one segment to be selected at a time. By default only one segment can be active (single-select).</p>
|
|
14
|
+
*
|
|
15
|
+
* @cssprop --segmented-button-group-shape: Border-radius of the group container.
|
|
16
|
+
*
|
|
17
|
+
* @fires {CustomEvent} change - Dispatched when the selection changes. Detail contains <code>{ value, values }</code>.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```html
|
|
21
|
+
* <wc-segmented-button-group>
|
|
22
|
+
* <wc-segmented-button value="day">Day</wc-segmented-button>
|
|
23
|
+
* <wc-segmented-button value="week" selected>Week</wc-segmented-button>
|
|
24
|
+
* <wc-segmented-button value="month">Month</wc-segmented-button>
|
|
25
|
+
* </wc-segmented-button-group>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @tags controls
|
|
29
|
+
*/
|
|
30
|
+
export class SegmentedButtonGroup extends LitElement {
|
|
31
|
+
static styles = [styles];
|
|
32
|
+
|
|
33
|
+
static SegmentedButton = SegmentedButton;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* When true, multiple segments can be selected simultaneously.
|
|
37
|
+
* Defaults to single-select mode.
|
|
38
|
+
*/
|
|
39
|
+
@property({ type: Boolean, reflect: true, attribute: 'multi-select' })
|
|
40
|
+
multiSelect: boolean = false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* When true, in single-select mode the currently selected segment can be
|
|
44
|
+
* deselected by clicking it again (allowing an empty selection).
|
|
45
|
+
* Defaults to `false`.
|
|
46
|
+
*/
|
|
47
|
+
@property({ type: Boolean, reflect: true })
|
|
48
|
+
nullable: boolean = false;
|
|
49
|
+
|
|
50
|
+
connectedCallback() {
|
|
51
|
+
super.connectedCallback();
|
|
52
|
+
this.addEventListener(
|
|
53
|
+
'segmented-button--change',
|
|
54
|
+
this._onSegmentChange as EventListener,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
disconnectedCallback() {
|
|
59
|
+
super.disconnectedCallback();
|
|
60
|
+
this.removeEventListener(
|
|
61
|
+
'segmented-button--change',
|
|
62
|
+
this._onSegmentChange as EventListener,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private _getSegments(): SegmentedButton[] {
|
|
67
|
+
return Array.from(this.querySelectorAll<SegmentedButton>('wc-segmented-button'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private _onSegmentChange = (ev: CustomEvent) => {
|
|
71
|
+
ev.stopPropagation();
|
|
72
|
+
const target = ev.composedPath()[0] as SegmentedButton;
|
|
73
|
+
const segments = this._getSegments();
|
|
74
|
+
|
|
75
|
+
if (this.multiSelect) {
|
|
76
|
+
target.selected = !target.selected;
|
|
77
|
+
} else {
|
|
78
|
+
if (target.selected && this.nullable) {
|
|
79
|
+
target.selected = false;
|
|
80
|
+
} else {
|
|
81
|
+
segments.forEach(seg => {
|
|
82
|
+
seg.selected = seg === target;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const selectedValues = segments
|
|
88
|
+
.filter(s => s.selected)
|
|
89
|
+
.map(s => s.value || s.textContent?.trim() || '');
|
|
90
|
+
|
|
91
|
+
this.dispatchEvent(
|
|
92
|
+
new CustomEvent('change', {
|
|
93
|
+
detail: {
|
|
94
|
+
value: selectedValues[0] ?? null,
|
|
95
|
+
values: selectedValues,
|
|
96
|
+
},
|
|
97
|
+
bubbles: true,
|
|
98
|
+
composed: true,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
render() {
|
|
104
|
+
return html`
|
|
105
|
+
<div class="segmented-button-group" role="group">
|
|
106
|
+
<slot></slot>
|
|
107
|
+
</div>
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
}
|