@redvars/peacock 3.3.3 → 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 +2742 -757
- 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 +34 -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/{radio-b70_Ie9n.js → select-C3XAzenC.js} +1706 -192
- 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 +6 -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/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 +7 -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 +28 -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 +85 -11
- 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/radio-b70_Ie9n.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,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
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
@use '../../scss/mixin';
|
|
2
|
+
|
|
3
|
+
@include mixin.base-styles;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
display: contents;
|
|
7
|
+
|
|
8
|
+
--_segmented-button-height: 2.5rem;
|
|
9
|
+
--_segmented-button-outline-color: var(--color-outline, #79747e);
|
|
10
|
+
--_segmented-button-selected-container-color: var(--color-secondary-container, #e8def8);
|
|
11
|
+
--_segmented-button-selected-label-text-color: var(--color-on-secondary-container, #1d192b);
|
|
12
|
+
--_segmented-button-unselected-label-text-color: var(--color-on-surface, #1c1b1f);
|
|
13
|
+
--_segmented-button-disabled-opacity: 0.38;
|
|
14
|
+
--_segmented-button-state-layer-color: var(--color-on-surface, #1c1b1f);
|
|
15
|
+
--_segmented-button-selected-state-layer-color: var(--color-on-secondary-container, #1d192b);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.segment {
|
|
19
|
+
position: relative;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
height: var(--segmented-button-height, var(--_segmented-button-height));
|
|
24
|
+
padding: 0 1.5rem;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
flex: 1;
|
|
27
|
+
min-width: 0;
|
|
28
|
+
outline: none;
|
|
29
|
+
user-select: none;
|
|
30
|
+
-webkit-user-select: none;
|
|
31
|
+
background: transparent;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
transition:
|
|
34
|
+
background-color 200ms ease,
|
|
35
|
+
color 200ms ease;
|
|
36
|
+
|
|
37
|
+
.content {
|
|
38
|
+
position: relative;
|
|
39
|
+
z-index: 1;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 0.5rem;
|
|
43
|
+
@include mixin.get-typography(label-large);
|
|
44
|
+
color: var(--segmented-button-unselected-label-text-color, var(--_segmented-button-unselected-label-text-color));
|
|
45
|
+
|
|
46
|
+
.check-icon,
|
|
47
|
+
.leading-icon {
|
|
48
|
+
--icon-size: 1.125rem;
|
|
49
|
+
--icon-color: currentColor;
|
|
50
|
+
flex-shrink: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.label {
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
text-overflow: ellipsis;
|
|
56
|
+
white-space: nowrap;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.state-layer {
|
|
61
|
+
position: absolute;
|
|
62
|
+
inset: 0;
|
|
63
|
+
background: var(--_segmented-button-state-layer-color);
|
|
64
|
+
opacity: 0;
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
transition: opacity 200ms ease;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* right-side divider between segments */
|
|
70
|
+
.segment-outline {
|
|
71
|
+
position: absolute;
|
|
72
|
+
top: 0;
|
|
73
|
+
right: 0;
|
|
74
|
+
width: 1px;
|
|
75
|
+
height: 100%;
|
|
76
|
+
background: var(--segmented-button-outline-color, var(--_segmented-button-outline-color));
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&:hover:not(.disabled) .state-layer {
|
|
81
|
+
opacity: 0.08;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&.has-focus:not(.disabled) .state-layer {
|
|
85
|
+
opacity: 0.12;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&.active:not(.disabled) .state-layer {
|
|
89
|
+
opacity: 0.16;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Selected state */
|
|
93
|
+
&.selected {
|
|
94
|
+
background-color: var(--segmented-button-selected-container-color, var(--_segmented-button-selected-container-color));
|
|
95
|
+
|
|
96
|
+
.content {
|
|
97
|
+
color: var(--segmented-button-selected-label-text-color, var(--_segmented-button-selected-label-text-color));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.state-layer {
|
|
101
|
+
background: var(--_segmented-button-selected-state-layer-color);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Disabled state */
|
|
106
|
+
&.disabled {
|
|
107
|
+
cursor: not-allowed;
|
|
108
|
+
opacity: var(--segmented-button-disabled-opacity, var(--_segmented-button-disabled-opacity));
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
:host(:last-child) .segment-outline {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { html, LitElement, nothing } from 'lit';
|
|
2
|
+
import { property, state } from 'lit/decorators.js';
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
4
|
+
import styles from './segmented-button.scss';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @label Segmented Button
|
|
8
|
+
* @tag wc-segmented-button
|
|
9
|
+
* @rawTag segmented-button
|
|
10
|
+
* @summary An individual segment within a segmented button group.
|
|
11
|
+
* @parentRawTag segmented-button-group
|
|
12
|
+
* @overview
|
|
13
|
+
* <p>Segmented buttons help people select options, switch views, or sort elements. They are used within a <code>wc-segmented-button-group</code>.</p>
|
|
14
|
+
*
|
|
15
|
+
* @cssprop --segmented-button-height: Height of the segmented button.
|
|
16
|
+
* @cssprop --segmented-button-selected-container-color: Background color when the segment is selected.
|
|
17
|
+
* @cssprop --segmented-button-selected-label-text-color: Text color when the segment is selected.
|
|
18
|
+
* @cssprop --segmented-button-unselected-label-text-color: Text color when the segment is unselected.
|
|
19
|
+
* @cssprop --segmented-button-outline-color: Outline / border color.
|
|
20
|
+
* @cssprop --segmented-button-disabled-opacity: Opacity when the segment is disabled.
|
|
21
|
+
*
|
|
22
|
+
* @fires {CustomEvent} segmented-button--change - Dispatched when the selected state changes.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```html
|
|
26
|
+
* <wc-segmented-button-group>
|
|
27
|
+
* <wc-segmented-button value="day">Day</wc-segmented-button>
|
|
28
|
+
* <wc-segmented-button value="week" selected>Week</wc-segmented-button>
|
|
29
|
+
* <wc-segmented-button value="month">Month</wc-segmented-button>
|
|
30
|
+
* </wc-segmented-button-group>
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @tags controls
|
|
34
|
+
*/
|
|
35
|
+
export class SegmentedButton extends LitElement {
|
|
36
|
+
static styles = [styles];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The value associated with this segment.
|
|
40
|
+
*/
|
|
41
|
+
@property({ type: String, reflect: true })
|
|
42
|
+
value: string = '';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether this segment is currently selected.
|
|
46
|
+
*/
|
|
47
|
+
@property({ type: Boolean, reflect: true })
|
|
48
|
+
selected: boolean = false;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* If true, the user cannot interact with this segment.
|
|
52
|
+
*/
|
|
53
|
+
@property({ type: Boolean, reflect: true })
|
|
54
|
+
disabled: boolean = false;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Optional icon name to display (uses wc-icon).
|
|
58
|
+
*/
|
|
59
|
+
@property({ type: String })
|
|
60
|
+
icon?: string;
|
|
61
|
+
|
|
62
|
+
@state()
|
|
63
|
+
private hasFocus = false;
|
|
64
|
+
|
|
65
|
+
@state()
|
|
66
|
+
private isActive = false;
|
|
67
|
+
|
|
68
|
+
connectedCallback() {
|
|
69
|
+
super.connectedCallback();
|
|
70
|
+
window.addEventListener('mouseup', this._windowMouseUp);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
disconnectedCallback() {
|
|
74
|
+
super.disconnectedCallback();
|
|
75
|
+
window.removeEventListener('mouseup', this._windowMouseUp);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private _windowMouseUp = () => {
|
|
79
|
+
if (this.isActive) {
|
|
80
|
+
this.isActive = false;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
private _mouseDownHandler = () => {
|
|
85
|
+
this.isActive = true;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
private _keyDownHandler = (evt: KeyboardEvent) => {
|
|
89
|
+
if (evt.key === ' ' || evt.key === 'Enter') {
|
|
90
|
+
evt.preventDefault();
|
|
91
|
+
this.isActive = true;
|
|
92
|
+
this._toggle(evt);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
private _clickHandler = (ev: MouseEvent) => {
|
|
97
|
+
this._toggle(ev);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
private _toggle(ev: MouseEvent | KeyboardEvent) {
|
|
101
|
+
if (this.disabled) return;
|
|
102
|
+
this.dispatchEvent(
|
|
103
|
+
new CustomEvent('segmented-button--change', {
|
|
104
|
+
detail: {
|
|
105
|
+
value: this.value || this.textContent?.trim(),
|
|
106
|
+
selected: !this.selected,
|
|
107
|
+
originalEvent: ev,
|
|
108
|
+
},
|
|
109
|
+
bubbles: true,
|
|
110
|
+
composed: true,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private _blurHandler = (ev: FocusEvent) => {
|
|
116
|
+
this.hasFocus = false;
|
|
117
|
+
this.dispatchEvent(
|
|
118
|
+
new CustomEvent('blur', { detail: ev, bubbles: true, composed: true }),
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
private _focusHandler = (ev: FocusEvent) => {
|
|
123
|
+
this.hasFocus = true;
|
|
124
|
+
this.dispatchEvent(
|
|
125
|
+
new CustomEvent('focus', { detail: ev, bubbles: true, composed: true }),
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** Sets focus on the segment. */
|
|
130
|
+
focus() {
|
|
131
|
+
this.renderRoot.querySelector<HTMLElement>('.segment')?.focus();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Removes focus from the segment. */
|
|
135
|
+
blur() {
|
|
136
|
+
this.renderRoot.querySelector<HTMLElement>('.segment')?.blur();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
render() {
|
|
140
|
+
const cssClasses = {
|
|
141
|
+
segment: true,
|
|
142
|
+
selected: this.selected,
|
|
143
|
+
disabled: this.disabled,
|
|
144
|
+
'has-focus': this.hasFocus,
|
|
145
|
+
active: this.isActive,
|
|
146
|
+
'has-icon': !!this.icon,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return html`
|
|
150
|
+
<div
|
|
151
|
+
class=${classMap(cssClasses)}
|
|
152
|
+
role="button"
|
|
153
|
+
tabindex=${this.disabled ? -1 : 0}
|
|
154
|
+
aria-pressed=${this.selected}
|
|
155
|
+
aria-disabled=${this.disabled}
|
|
156
|
+
@click=${this._clickHandler}
|
|
157
|
+
@mousedown=${this._mouseDownHandler}
|
|
158
|
+
@keydown=${this._keyDownHandler}
|
|
159
|
+
@blur=${this._blurHandler}
|
|
160
|
+
@focus=${this._focusHandler}
|
|
161
|
+
>
|
|
162
|
+
<div class="state-layer"></div>
|
|
163
|
+
<div class="content">
|
|
164
|
+
${this.selected
|
|
165
|
+
? html`<wc-icon class="check-icon" name="check"></wc-icon>`
|
|
166
|
+
: this.icon
|
|
167
|
+
? html`<wc-icon class="leading-icon" name=${this.icon}></wc-icon>`
|
|
168
|
+
: nothing}
|
|
169
|
+
<span class="label"><slot></slot></span>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="segment-outline"></div>
|
|
172
|
+
</div>
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
2
|
+
import { property, query } from 'lit/decorators.js';
|
|
3
|
+
import type { MenuItem } from '../menu/menu-item/menu-item.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @label Select Option
|
|
7
|
+
* @tag wc-option
|
|
8
|
+
* @rawTag option
|
|
9
|
+
* @parentRawTag select
|
|
10
|
+
*
|
|
11
|
+
* @summary A declarative option element for use inside wc-select.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```html
|
|
15
|
+
* <wc-select label="Fruit">
|
|
16
|
+
* <wc-option value="apple">Apple</wc-option>
|
|
17
|
+
* <wc-option value="banana">Banana</wc-option>
|
|
18
|
+
* </wc-select>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class SelectOptionElement extends LitElement {
|
|
22
|
+
static override styles = [
|
|
23
|
+
css`
|
|
24
|
+
:host {
|
|
25
|
+
display: contents;
|
|
26
|
+
}
|
|
27
|
+
:host([filtered]) {
|
|
28
|
+
display: none;
|
|
29
|
+
}
|
|
30
|
+
`,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The option's submitted value.
|
|
35
|
+
*/
|
|
36
|
+
@property({ type: String, reflect: true })
|
|
37
|
+
value: string = '';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Optional Material Symbol icon name shown before the label.
|
|
41
|
+
*/
|
|
42
|
+
@property({ type: String, reflect: true })
|
|
43
|
+
icon: string = '';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Disables the option so it cannot be selected.
|
|
47
|
+
*/
|
|
48
|
+
@property({ type: Boolean, reflect: true })
|
|
49
|
+
disabled: boolean = false;
|
|
50
|
+
|
|
51
|
+
// ── Managed by wc-select ──────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/** Reflects whether this option is currently selected. Set by wc-select. */
|
|
54
|
+
@property({ type: Boolean, reflect: true })
|
|
55
|
+
selected: boolean = false;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* When true the menu stays open after selection (used for multi-select).
|
|
59
|
+
* Set by wc-select.
|
|
60
|
+
*/
|
|
61
|
+
@property({ type: Boolean, attribute: 'keep-open' })
|
|
62
|
+
keepOpen: boolean = false;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* When true the option is hidden and excluded from keyboard navigation
|
|
66
|
+
* because it does not match the current typeahead search query.
|
|
67
|
+
* Set by wc-select.
|
|
68
|
+
*/
|
|
69
|
+
@property({ type: Boolean, reflect: true })
|
|
70
|
+
filtered: boolean = false;
|
|
71
|
+
|
|
72
|
+
@query('wc-menu-item')
|
|
73
|
+
private readonly _menuItemEl?: HTMLElement;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns the inner `wc-menu-item` element.
|
|
77
|
+
* `wc-menu` discovers this via its `items` getter which checks `el.item`
|
|
78
|
+
* as a `MenuItem` proxy, so keyboard navigation and activation work
|
|
79
|
+
* without `wc-option` extending `MenuItem` directly.
|
|
80
|
+
*/
|
|
81
|
+
get item(): MenuItem | null {
|
|
82
|
+
const el = this._menuItemEl;
|
|
83
|
+
// Narrow to MenuItem — the shadow DOM only ever contains a wc-menu-item
|
|
84
|
+
// (a MenuItem subclass), so this cast is safe by construction.
|
|
85
|
+
return el != null ? (el as unknown as MenuItem) : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
override render() {
|
|
89
|
+
return html`
|
|
90
|
+
<wc-menu-item
|
|
91
|
+
value=${this.value}
|
|
92
|
+
?disabled=${this.disabled || this.filtered}
|
|
93
|
+
?selected=${this.selected}
|
|
94
|
+
?keep-open=${this.keepOpen}
|
|
95
|
+
>
|
|
96
|
+
${this.icon
|
|
97
|
+
? html`<wc-icon name=${this.icon} slot="leading-icon"></wc-icon>`
|
|
98
|
+
: nothing}
|
|
99
|
+
<slot></slot>
|
|
100
|
+
${this.selected && this.keepOpen
|
|
101
|
+
? html`<wc-icon
|
|
102
|
+
name="check"
|
|
103
|
+
slot="trailing-supporting-text"
|
|
104
|
+
></wc-icon>`
|
|
105
|
+
: nothing}
|
|
106
|
+
</wc-menu-item>
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
@use '../../scss/mixin';
|
|
2
|
+
|
|
3
|
+
@include mixin.base-styles;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
display: block;
|
|
7
|
+
width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Make the field wrapper behave as a select (pointer cursor) */
|
|
11
|
+
.select-field {
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.select-trigger {
|
|
16
|
+
flex: 1;
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
min-height: 1.5rem;
|
|
20
|
+
outline: none;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
user-select: none;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
min-width: 0;
|
|
25
|
+
|
|
26
|
+
&:focus-visible {
|
|
27
|
+
outline: none;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.display-value {
|
|
32
|
+
@include mixin.get-typography(body-large);
|
|
33
|
+
color: var(--color-on-surface);
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
text-overflow: ellipsis;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.placeholder {
|
|
40
|
+
@include mixin.get-typography(body-large);
|
|
41
|
+
color: var(--color-on-surface-variant);
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
text-overflow: ellipsis;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Typeahead search input */
|
|
48
|
+
.search-input {
|
|
49
|
+
flex: 1;
|
|
50
|
+
width: 100%;
|
|
51
|
+
border: none;
|
|
52
|
+
outline: none;
|
|
53
|
+
background: transparent;
|
|
54
|
+
margin: 0;
|
|
55
|
+
padding: 0;
|
|
56
|
+
@include mixin.get-typography(body-large);
|
|
57
|
+
color: var(--color-on-surface);
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
|
|
60
|
+
&::placeholder {
|
|
61
|
+
color: var(--color-on-surface-variant);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&:focus {
|
|
65
|
+
cursor: text;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Field end slot wrapper */
|
|
70
|
+
.field-end-wrapper {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: var(--spacing-050);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Dropdown chevron icon */
|
|
77
|
+
.dropdown-icon {
|
|
78
|
+
--icon-size: 1.5rem;
|
|
79
|
+
--icon-color: var(--color-on-surface-variant);
|
|
80
|
+
transition: transform 200ms ease;
|
|
81
|
+
flex-shrink: 0;
|
|
82
|
+
|
|
83
|
+
&.dropdown-icon--open {
|
|
84
|
+
transform: rotate(180deg);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Clear button */
|
|
89
|
+
.clear-btn {
|
|
90
|
+
--button-container-shape: var(--shape-corner-full);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Multi-select chips area — single scrollable row, no vertical growth */
|
|
94
|
+
.chips-container {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-wrap: nowrap;
|
|
97
|
+
overflow-x: auto;
|
|
98
|
+
scrollbar-width: none;
|
|
99
|
+
gap: var(--spacing-050);
|
|
100
|
+
padding-block: var(--spacing-050);
|
|
101
|
+
align-items: center;
|
|
102
|
+
flex: 1;
|
|
103
|
+
min-width: 0;
|
|
104
|
+
|
|
105
|
+
&::-webkit-scrollbar {
|
|
106
|
+
display: none;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Disabled state */
|
|
111
|
+
:host([disabled]) .select-trigger,
|
|
112
|
+
:host([disabled]) .select-field {
|
|
113
|
+
cursor: not-allowed;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Readonly state */
|
|
117
|
+
:host([readonly]) .select-trigger,
|
|
118
|
+
:host([readonly]) .select-field {
|
|
119
|
+
cursor: default;
|
|
120
|
+
}
|