@api-client/ui 0.5.26 → 0.5.28

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.
Files changed (92) hide show
  1. package/build/src/elements/app-bar/internals/AppBar.d.ts +1 -1
  2. package/build/src/elements/app-bar/internals/AppBar.d.ts.map +1 -1
  3. package/build/src/elements/app-bar/internals/AppBar.js +5 -5
  4. package/build/src/elements/app-bar/internals/AppBar.js.map +1 -1
  5. package/build/src/elements/currency/internals/Picker.d.ts +2 -2
  6. package/build/src/elements/currency/internals/Picker.d.ts.map +1 -1
  7. package/build/src/elements/currency/internals/Picker.js +13 -12
  8. package/build/src/elements/currency/internals/Picker.js.map +1 -1
  9. package/build/src/elements/data-table/DataTable.d.ts +1 -3
  10. package/build/src/elements/data-table/DataTable.d.ts.map +1 -1
  11. package/build/src/elements/data-table/DataTable.js +2 -4
  12. package/build/src/elements/data-table/DataTable.js.map +1 -1
  13. package/build/src/elements/file-system/internals/Breadcrumbs.d.ts +1 -2
  14. package/build/src/elements/file-system/internals/Breadcrumbs.d.ts.map +1 -1
  15. package/build/src/elements/file-system/internals/Breadcrumbs.js +2 -3
  16. package/build/src/elements/file-system/internals/Breadcrumbs.js.map +1 -1
  17. package/build/src/elements/navigation/internals/NavigationItem.js +1 -1
  18. package/build/src/elements/navigation/internals/NavigationItem.js.map +1 -1
  19. package/build/src/md/list/internals/List.d.ts +4 -2
  20. package/build/src/md/list/internals/List.d.ts.map +1 -1
  21. package/build/src/md/list/internals/List.js +16 -6
  22. package/build/src/md/list/internals/List.js.map +1 -1
  23. package/build/src/md/list/internals/ListItem.d.ts +10 -1
  24. package/build/src/md/list/internals/ListItem.d.ts.map +1 -1
  25. package/build/src/md/list/internals/ListItem.js +74 -8
  26. package/build/src/md/list/internals/ListItem.js.map +1 -1
  27. package/build/src/md/list/internals/ListItem.styles.d.ts.map +1 -1
  28. package/build/src/md/list/internals/ListItem.styles.js +38 -28
  29. package/build/src/md/list/internals/ListItem.styles.js.map +1 -1
  30. package/build/src/md/menu/internal/MenuItem.js +4 -4
  31. package/build/src/md/menu/internal/MenuItem.js.map +1 -1
  32. package/build/src/md/progress/internals/CircularProgress.d.ts +93 -0
  33. package/build/src/md/progress/internals/CircularProgress.d.ts.map +1 -0
  34. package/build/src/md/progress/internals/CircularProgress.js +156 -0
  35. package/build/src/md/progress/internals/CircularProgress.js.map +1 -0
  36. package/build/src/md/progress/internals/CircularProgress.styles.d.ts +3 -0
  37. package/build/src/md/progress/internals/CircularProgress.styles.d.ts.map +1 -0
  38. package/build/src/md/progress/internals/CircularProgress.styles.js +220 -0
  39. package/build/src/md/progress/internals/CircularProgress.styles.js.map +1 -0
  40. package/build/src/md/progress/internals/Range.d.ts +6 -0
  41. package/build/src/md/progress/internals/Range.d.ts.map +1 -1
  42. package/build/src/md/progress/internals/Range.js +41 -3
  43. package/build/src/md/progress/internals/Range.js.map +1 -1
  44. package/build/src/md/progress/internals/UiProgress.d.ts +0 -7
  45. package/build/src/md/progress/internals/UiProgress.d.ts.map +1 -1
  46. package/build/src/md/progress/internals/UiProgress.js +2 -36
  47. package/build/src/md/progress/internals/UiProgress.js.map +1 -1
  48. package/build/src/md/progress/ui-circular-progress.d.ts +11 -0
  49. package/build/src/md/progress/ui-circular-progress.d.ts.map +1 -0
  50. package/build/src/md/progress/ui-circular-progress.js +27 -0
  51. package/build/src/md/progress/ui-circular-progress.js.map +1 -0
  52. package/build/src/md/select/internals/Option.js +2 -2
  53. package/build/src/md/select/internals/Option.js.map +1 -1
  54. package/build/src/md/select/internals/Option.styles.d.ts.map +1 -1
  55. package/build/src/md/select/internals/Option.styles.js +0 -127
  56. package/build/src/md/select/internals/Option.styles.js.map +1 -1
  57. package/build/src/md/select/internals/Select.d.ts +11 -1
  58. package/build/src/md/select/internals/Select.d.ts.map +1 -1
  59. package/build/src/md/select/internals/Select.js +21 -2
  60. package/build/src/md/select/internals/Select.js.map +1 -1
  61. package/demo/elements/currency/index.html +2 -2
  62. package/demo/elements/navigation/navigation-item.html +4 -0
  63. package/demo/elements/navigation/navigation-item.ts +19 -0
  64. package/demo/md/list/list.ts +9 -3
  65. package/demo/md/progress/progress.ts +24 -1
  66. package/demo/md/select/index.ts +5 -0
  67. package/demo/md/tabs/tabs.ts +0 -4
  68. package/package.json +1 -1
  69. package/src/elements/app-bar/internals/AppBar.ts +5 -5
  70. package/src/elements/currency/internals/Picker.ts +17 -16
  71. package/src/elements/data-table/DataTable.ts +2 -4
  72. package/src/elements/file-system/internals/Breadcrumbs.ts +2 -3
  73. package/src/elements/navigation/internals/NavigationItem.ts +1 -1
  74. package/src/md/list/internals/List.ts +19 -8
  75. package/src/md/list/internals/ListItem.styles.ts +38 -28
  76. package/src/md/list/internals/ListItem.ts +55 -8
  77. package/src/md/menu/internal/MenuItem.ts +4 -4
  78. package/src/md/progress/internals/CircularProgress.styles.ts +220 -0
  79. package/src/md/progress/internals/CircularProgress.ts +129 -0
  80. package/src/md/progress/internals/Range.ts +29 -1
  81. package/src/md/progress/internals/UiProgress.ts +1 -30
  82. package/src/md/progress/ui-circular-progress.ts +15 -0
  83. package/src/md/select/internals/Option.styles.ts +0 -127
  84. package/src/md/select/internals/Option.ts +2 -2
  85. package/src/md/select/internals/Select.ts +13 -1
  86. package/test/elements/currency/CurrencyPicker.accessibility.test.ts +14 -14
  87. package/test/elements/currency/CurrencyPicker.core.test.ts +6 -6
  88. package/test/elements/currency/CurrencyPicker.integration.test.ts +2 -2
  89. package/test/elements/currency/CurrencyPicker.test.ts +10 -10
  90. package/test/elements/data-table/DataTable.browser.test.ts +2 -2
  91. package/test/md/menu/MenuItem.test.ts +2 -3
  92. package/test/md/progress/UiCircularProgressElement.test.ts +481 -0
@@ -8,10 +8,12 @@ export default css`
8
8
  cursor: default;
9
9
  position: relative;
10
10
 
11
- --md-focus-ring-shape-end-end: 0px;
12
- --md-focus-ring-shape-end-start: 0px;
13
- --md-focus-ring-shape-start-end: 0px;
14
- --md-focus-ring-shape-start-start: 0px;
11
+ color: var(--md-sys-color-on-surface);
12
+
13
+ --md-focus-ring-shape-end-end: 8px;
14
+ --md-focus-ring-shape-end-start: 8px;
15
+ --md-focus-ring-shape-start-end: 8px;
16
+ --md-focus-ring-shape-start-start: 8px;
15
17
  }
16
18
 
17
19
  :host([disabled]) {
@@ -40,6 +42,8 @@ export default css`
40
42
  align-items: center;
41
43
  overflow: hidden;
42
44
  padding: 8px 16px 8px 16px;
45
+
46
+ gap: 16px;
43
47
  }
44
48
 
45
49
  .ripple {
@@ -54,8 +58,6 @@ export default css`
54
58
  }
55
59
 
56
60
  .headline {
57
- color: var(--md-sys-color-on-surface);
58
-
59
61
  font-family: var(--md-sys-typescale-body-large-font);
60
62
  font-weight: var(--md-sys-typescale-body-large-weight);
61
63
  font-size: var(--md-sys-typescale-body-large-size);
@@ -92,7 +94,6 @@ export default css`
92
94
  slot[name='end-text']::slotted(*) {
93
95
  /* this is to make up to the 24px right padding defined in the spec. */
94
96
  margin-right: 8px;
95
- margin-left: 16px;
96
97
  }
97
98
 
98
99
  :host([lines='three']) .supporting-text {
@@ -124,21 +125,34 @@ export default css`
124
125
  }
125
126
 
126
127
  .start {
128
+ display: contents;
129
+ }
130
+
131
+ .start.has-start {
127
132
  align-self: stretch;
128
133
  display: flex;
129
134
  justify-content: center;
130
135
  align-items: center;
131
136
  }
132
137
 
138
+ .end {
139
+ display: contents;
140
+ }
141
+
142
+ .end.has-end,
143
+ .end.has-end-text {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 8px;
147
+ }
148
+
133
149
  :host slot[name='end']::slotted(*) {
134
150
  color: var(--md-sys-color-on-surface-variant);
135
151
  fill: var(--md-sys-color-on-surface-variant);
136
- margin-left: 16px;
137
152
  }
138
153
 
139
154
  :host slot[name='start']::slotted(*) {
140
155
  display: block;
141
- margin-right: 16px;
142
156
  }
143
157
 
144
158
  :host([image='icon']) slot[name='start']::slotted(*) {
@@ -168,28 +182,24 @@ export default css`
168
182
  flex-direction: column;
169
183
  }
170
184
 
171
- :host(.highlight) .state {
172
- background-color: var(--md-sys-color-on-surface);
173
- opacity: var(--md-sys-state-hover-state-layer-opacity);
174
- position: absolute;
175
- inset: 0;
176
- z-index: 1;
185
+ :host(.highlight) .surface {
186
+ background-color: var(--md-sys-color-tertiary-container);
187
+ color: var(--md-sys-color-on-tertiary-container);
177
188
  }
178
189
 
179
- :host(.select) .state {
190
+ :host(.select) .surface {
180
191
  background-color: var(--md-sys-color-secondary-container);
181
192
  color: var(--md-sys-color-on-secondary-container);
182
- opacity: var(--md-sys-state-focus-state-layer-opacity);
183
- position: absolute;
184
- inset: 0;
185
- z-index: 1;
186
- }
187
-
188
- :host([static]:hover) .state {
189
- background-color: var(--md-sys-color-on-surface);
190
- opacity: var(--md-sys-state-hover-state-layer-opacity);
191
- position: absolute;
192
- inset: 0;
193
- z-index: 1;
193
+ }
194
+
195
+ [name='overline'] {
196
+ color: var(--md-sys-color-on-surface-variant);
197
+ font-family: var(--md-sys-typescale-label-small-font, var(--md-ref-typeface-plain, Roboto));
198
+ font-size: var(--md-sys-typescale-label-small-size, 0.6875rem);
199
+ font-weight: var(
200
+ --md-sys-typescale-label-small-weight,
201
+ var(--md-ref-typeface-weight-medium, 500) --md-ref-typeface-weight-medium is not defined
202
+ );
203
+ line-height: var(--md-sys-typescale-label-small-line-height, 1rem);
194
204
  }
195
205
  `
@@ -1,6 +1,6 @@
1
1
  import { html, PropertyValues, TemplateResult } from 'lit'
2
- import { property, query } from 'lit/decorators.js'
3
- import { classMap } from 'lit/directives/class-map.js'
2
+ import { property, state, query } from 'lit/decorators.js'
3
+ import { classMap, ClassInfo } from 'lit/directives/class-map.js'
4
4
  import { UiElement } from '../../UiElement.js'
5
5
  import { BeginPressConfig, EndPressConfig } from '../../../controllers/ActionController.js'
6
6
  import UiRipple from '../../ripple/internals/ripple.js'
@@ -25,6 +25,7 @@ export enum ListItemImage {
25
25
 
26
26
  /**
27
27
  * @slot
28
+ * @slot overline
28
29
  * @slot start
29
30
  * @slot end
30
31
  * @slot end-text
@@ -63,6 +64,10 @@ export default class UiListItem extends UiElement {
63
64
 
64
65
  @property({ type: Number, reflect: true }) override accessor tabIndex = -1
65
66
 
67
+ @state() accessor hasStartItem = false
68
+ @state() accessor hasEndItem = false
69
+ @state() accessor hasEndTextItem = false
70
+
66
71
  constructor() {
67
72
  super()
68
73
 
@@ -190,7 +195,33 @@ export default class UiListItem extends UiElement {
190
195
  this.removeAttribute('tabindex')
191
196
  }
192
197
 
193
- protected handleSlotChange(): void {
198
+ protected handleStartSlotChange(): void {
199
+ const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name="start"]')
200
+ if (slot) {
201
+ this.hasStartItem = slot.assignedNodes({ flatten: true }).length > 0
202
+ } else {
203
+ this.hasStartItem = false
204
+ }
205
+ this.requestUpdate()
206
+ }
207
+
208
+ protected handleEndSlotChange(): void {
209
+ const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name="end"]')
210
+ if (slot) {
211
+ this.hasEndItem = slot.assignedNodes({ flatten: true }).length > 0
212
+ } else {
213
+ this.hasEndItem = false
214
+ }
215
+ this.requestUpdate()
216
+ }
217
+
218
+ protected handleEndTextSlotChange(): void {
219
+ const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name="end-text"]')
220
+ if (slot) {
221
+ this.hasEndTextItem = slot.assignedNodes({ flatten: true }).length > 0
222
+ } else {
223
+ this.hasEndTextItem = false
224
+ }
194
225
  this.requestUpdate()
195
226
  }
196
227
 
@@ -218,16 +249,31 @@ export default class UiListItem extends UiElement {
218
249
  ></md-focus-ring>`
219
250
  }
220
251
 
252
+ protected getStartClasses(): ClassInfo {
253
+ return {
254
+ 'start': true,
255
+ 'has-start': this.hasStartItem,
256
+ }
257
+ }
258
+
221
259
  protected renderStart(): TemplateResult {
222
- return html`<div class="start">
223
- <slot name="start" @slotchange=${this.handleSlotChange}></slot>
260
+ return html`<div class="${classMap(this.getStartClasses())}">
261
+ <slot name="start" @slotchange=${this.handleStartSlotChange}></slot>
224
262
  </div>`
225
263
  }
226
264
 
265
+ protected getEndClasses(): ClassInfo {
266
+ return {
267
+ 'end': true,
268
+ 'has-end': this.hasEndItem,
269
+ 'has-end-text': this.hasEndTextItem,
270
+ }
271
+ }
272
+
227
273
  protected renderEnd(): TemplateResult {
228
- return html`<div class="end">
229
- <slot name="end" @slotchange=${this.handleSlotChange}></slot>
230
- <span class="trailing-supporting-text"><slot name="end-text"></slot></span>
274
+ return html`<div class="${classMap(this.getEndClasses())}">
275
+ <slot name="end" @slotchange=${this.handleEndSlotChange}></slot>
276
+ <slot name="end-text" class="trailing-supporting-text" @slotchange=${this.handleEndTextSlotChange}></slot>
231
277
  </div>`
232
278
  }
233
279
 
@@ -236,6 +282,7 @@ export default class UiListItem extends UiElement {
236
282
  const hasSupportingText = lines !== ListItemLines.one
237
283
  return html`
238
284
  <div class="body">
285
+ <slot name="overline"></slot>
239
286
  <span class="headline"><slot></slot></span>
240
287
  ${hasSupportingText ? html`<span class="supporting-text"><slot name="supporting-text"></slot></span>` : ''}
241
288
  </div>
@@ -191,7 +191,7 @@ export default class UiMenuItem extends UiListItem {
191
191
  this.dispatchEvent(
192
192
  new CustomEvent('submenu-open', {
193
193
  detail: { subMenu: this.subMenuElement },
194
- bubbles: true,
194
+ bubbles: false,
195
195
  composed: true,
196
196
  })
197
197
  )
@@ -227,7 +227,7 @@ export default class UiMenuItem extends UiListItem {
227
227
  this.dispatchEvent(
228
228
  new CustomEvent('select', {
229
229
  detail: e.detail,
230
- bubbles: true,
230
+ bubbles: false,
231
231
  composed: true,
232
232
  })
233
233
  )
@@ -249,7 +249,7 @@ export default class UiMenuItem extends UiListItem {
249
249
 
250
250
  protected override renderEnd(): TemplateResult {
251
251
  return html`<div class="end">
252
- <slot name="end" @slotchange=${this.handleSlotChange}></slot>
252
+ <slot name="end" @slotchange=${this.handleEndSlotChange}></slot>
253
253
  <span class="trailing-supporting-text"><slot name="end-text"></slot></span>
254
254
  ${this.hasSubMenu ? html`<ui-icon class="menu-item-arrow">arrow_right</ui-icon>` : ''}
255
255
  </div>`
@@ -260,7 +260,7 @@ export default class UiMenuItem extends UiListItem {
260
260
 
261
261
  return html`<div class="start">
262
262
  ${showCheckIcon ? html`<ui-icon class="selection-check">check</ui-icon>` : ''}
263
- <slot name="start" @slotchange=${this.handleSlotChange}></slot>
263
+ <slot name="start" @slotchange=${this.handleEndSlotChange}></slot>
264
264
  </div>`
265
265
  }
266
266
  }
@@ -0,0 +1,220 @@
1
+ import { css } from 'lit'
2
+
3
+ export default css`
4
+ :host {
5
+ --ui-circular-progress-arc-duration: 1333ms;
6
+ --ui-circular-progress-cycle-duration: calc(4 * var(--ui-circular-progress-arc-duration));
7
+ --ui-circular-progress-linear-rotate-duration: calc(var(--ui-circular-progress-arc-duration) * 360 / 306);
8
+ --ui-circular-progress-indeterminate-easing: cubic-bezier(0.4, 0, 0.2, 1);
9
+ --_container-padding: 4px;
10
+ --_size: var(--ui-circular-progress-size, 48px);
11
+
12
+ --_active-indicator-color: var(--ui-circular-progress-active-indicator-color, var(--md-sys-color-primary));
13
+ --_active-indicator-width: var(--ui-circular-progress-active-indicator-width, 10);
14
+ --_four-color-active-indicator-four-color: var(
15
+ --ui-circular-progress-four-color-active-indicator-four-color,
16
+ var(--md-sys-color-tertiary-container)
17
+ );
18
+ --_four-color-active-indicator-one-color: var(
19
+ --ui-circular-progress-four-color-active-indicator-one-color,
20
+ var(--md-sys-color-primary)
21
+ );
22
+ --_four-color-active-indicator-three-color: var(
23
+ --ui-circular-progress-four-color-active-indicator-three-color,
24
+ var(--md-sys-color-tertiary)
25
+ );
26
+ --_four-color-active-indicator-two-color: var(
27
+ --ui-circular-progress-four-color-active-indicator-two-color,
28
+ var(--md-sys-color-primary-container)
29
+ );
30
+
31
+ display: inline-flex;
32
+ vertical-align: middle;
33
+ width: var(--_size);
34
+ height: var(--_size);
35
+ position: relative;
36
+ align-items: center;
37
+ justify-content: center;
38
+
39
+ contain: strict;
40
+ content-visibility: auto;
41
+ }
42
+
43
+ .progress,
44
+ .spinner,
45
+ .left,
46
+ .right,
47
+ .circle,
48
+ svg,
49
+ .track,
50
+ .active-track {
51
+ position: absolute;
52
+ inset: 0;
53
+ }
54
+
55
+ svg {
56
+ transform: rotate(-90deg);
57
+ }
58
+
59
+ circle {
60
+ cx: 50%;
61
+ cy: 50%;
62
+ r: calc(50% * (1 - var(--_active-indicator-width) / 100));
63
+ stroke-width: calc(var(--_active-indicator-width) * 1%);
64
+ stroke-dasharray: 100;
65
+ fill: transparent;
66
+ }
67
+
68
+ .active-track {
69
+ transition: stroke-dashoffset 500ms cubic-bezier(0, 0, 0.2, 1);
70
+ stroke: var(--_active-indicator-color);
71
+ }
72
+
73
+ .track {
74
+ stroke: transparent;
75
+ }
76
+
77
+ :host([indeterminate]) {
78
+ animation: linear infinite linear-rotate;
79
+ animation-duration: var(--ui-circular-progress-linear-rotate-duration);
80
+ }
81
+
82
+ .spinner {
83
+ animation: infinite both rotate-arc;
84
+ animation-duration: var(--ui-circular-progress-cycle-duration);
85
+ animation-timing-function: var(--ui-circular-progress-indeterminate-easing);
86
+ }
87
+
88
+ .left {
89
+ overflow: hidden;
90
+ inset: 0 50% 0 0;
91
+ }
92
+
93
+ .right {
94
+ overflow: hidden;
95
+ inset: 0 0 0 50%;
96
+ }
97
+
98
+ .circle {
99
+ box-sizing: border-box;
100
+ border-radius: 50%;
101
+ --_padding-box-width: calc(var(--_size) - 2 * var(--_container-padding));
102
+ --_active-indicator-fraction: calc(var(--_active-indicator-width) / 100);
103
+ border: solid calc(var(--_active-indicator-fraction) * var(--_padding-box-width));
104
+ border-color: var(--_active-indicator-color) var(--_active-indicator-color) transparent transparent;
105
+ animation: expand-arc;
106
+ animation-iteration-count: infinite;
107
+ animation-fill-mode: both;
108
+ animation-duration: var(--ui-circular-progress-arc-duration), var(--ui-circular-progress-cycle-duration);
109
+ animation-timing-function: var(--ui-circular-progress-indeterminate-easing);
110
+ }
111
+
112
+ .left .circle {
113
+ rotate: 135deg;
114
+ inset: 0 -100% 0 0;
115
+ }
116
+
117
+ .right .circle {
118
+ rotate: 100deg;
119
+ inset: 0 0 0 -100%;
120
+ animation-delay: calc(-0.5 * var(--ui-circular-progress-arc-duration)), 0ms;
121
+ }
122
+
123
+ :host([fourcolor]) .circle {
124
+ animation-name: expand-arc, four-color;
125
+ }
126
+
127
+ @keyframes expand-arc {
128
+ 0% {
129
+ transform: rotate(265deg);
130
+ }
131
+ 50% {
132
+ transform: rotate(130deg);
133
+ }
134
+ 100% {
135
+ transform: rotate(265deg);
136
+ }
137
+ }
138
+
139
+ @keyframes rotate-arc {
140
+ 12.5% {
141
+ transform: rotate(135deg);
142
+ }
143
+ 25% {
144
+ transform: rotate(270deg);
145
+ }
146
+ 37.5% {
147
+ transform: rotate(405deg);
148
+ }
149
+ 50% {
150
+ transform: rotate(540deg);
151
+ }
152
+ 62.5% {
153
+ transform: rotate(675deg);
154
+ }
155
+ 75% {
156
+ transform: rotate(810deg);
157
+ }
158
+ 87.5% {
159
+ transform: rotate(945deg);
160
+ }
161
+ 100% {
162
+ transform: rotate(1080deg);
163
+ }
164
+ }
165
+
166
+ @keyframes linear-rotate {
167
+ to {
168
+ transform: rotate(360deg);
169
+ }
170
+ }
171
+
172
+ @keyframes four-color {
173
+ 0% {
174
+ border-top-color: var(--_four-color-active-indicator-one-color);
175
+ border-right-color: var(--_four-color-active-indicator-one-color);
176
+ }
177
+ 15% {
178
+ border-top-color: var(--_four-color-active-indicator-one-color);
179
+ border-right-color: var(--_four-color-active-indicator-one-color);
180
+ }
181
+ 25% {
182
+ border-top-color: var(--_four-color-active-indicator-two-color);
183
+ border-right-color: var(--_four-color-active-indicator-two-color);
184
+ }
185
+ 40% {
186
+ border-top-color: var(--_four-color-active-indicator-two-color);
187
+ border-right-color: var(--_four-color-active-indicator-two-color);
188
+ }
189
+ 50% {
190
+ border-top-color: var(--_four-color-active-indicator-three-color);
191
+ border-right-color: var(--_four-color-active-indicator-three-color);
192
+ }
193
+ 65% {
194
+ border-top-color: var(--_four-color-active-indicator-three-color);
195
+ border-right-color: var(--_four-color-active-indicator-three-color);
196
+ }
197
+ 75% {
198
+ border-top-color: var(--_four-color-active-indicator-four-color);
199
+ border-right-color: var(--_four-color-active-indicator-four-color);
200
+ }
201
+ 90% {
202
+ border-top-color: var(--_four-color-active-indicator-four-color);
203
+ border-right-color: var(--_four-color-active-indicator-four-color);
204
+ }
205
+ 100% {
206
+ border-top-color: var(--_four-color-active-indicator-one-color);
207
+ border-right-color: var(--_four-color-active-indicator-one-color);
208
+ }
209
+ }
210
+
211
+ @media (forced-colors: active) {
212
+ .active-track {
213
+ stroke: CanvasText;
214
+ }
215
+
216
+ .circle {
217
+ border-color: CanvasText CanvasText Canvas Canvas;
218
+ }
219
+ }
220
+ `
@@ -0,0 +1,129 @@
1
+ import { property } from 'lit/decorators.js'
2
+ import { isDisabled, setDisabled } from '../../../lib/disabled.js'
3
+ import { UiRange } from './Range.js'
4
+ import { type TemplateResult, html } from 'lit'
5
+
6
+ /**
7
+ * A circular progress indicator component that displays progress in a circular format.
8
+ *
9
+ * This component supports both determinate and indeterminate progress states:
10
+ * - **Determinate**: Shows a specific progress value with a filled arc
11
+ * - **Indeterminate**: Shows continuous animation without a specific value
12
+ *
13
+ * The component inherits from UiRange and provides additional features like:
14
+ * - Four-color animation for indeterminate state
15
+ * - Material Design 3 styling
16
+ * - Accessibility support with proper ARIA attributes
17
+ * - Customizable size and colors via CSS custom properties
18
+ *
19
+ * ## Accessibility
20
+ *
21
+ * For accessibility compliance, you must provide an accessible name for the progress indicator.
22
+ * Use the `aria-label` attribute to describe what the progress represents:
23
+ *
24
+ * @example
25
+ * ```html
26
+ * <!-- Basic determinate progress -->
27
+ * <ui-circular-progress value="50" max="100" aria-label="Upload progress"></ui-circular-progress>
28
+ *
29
+ * <!-- Indeterminate progress -->
30
+ * <ui-circular-progress indeterminate aria-label="Loading content"></ui-circular-progress>
31
+ *
32
+ * <!-- Four-color indeterminate progress -->
33
+ * <ui-circular-progress indeterminate fourcolor aria-label="Processing data"></ui-circular-progress>
34
+ * ```
35
+ *
36
+ * @fires ratiochange - Inherited from UiRange. Dispatched when the ratio computation changes.
37
+ */
38
+ export default class CircularProgress extends UiRange {
39
+ /**
40
+ * Gets the disabled state of the progress indicator.
41
+ * @returns True if the component is disabled, false otherwise.
42
+ */
43
+ get disabled(): boolean {
44
+ return isDisabled(this)
45
+ }
46
+
47
+ /**
48
+ * Sets the disabled state of the progress indicator.
49
+ * When disabled, the component may have reduced visual emphasis
50
+ * and should not respond to user interactions.
51
+ * @attribute
52
+ */
53
+ @property({ reflect: true, type: Boolean })
54
+ set disabled(value: boolean) {
55
+ const old = isDisabled(this)
56
+ setDisabled(this, value)
57
+ this.requestUpdate('disabled', old)
58
+ }
59
+
60
+ /**
61
+ * Enables four-color animation for indeterminate progress indicators.
62
+ *
63
+ * When enabled, the indeterminate progress indicator cycles between four colors:
64
+ * - Primary color (--ui-circular-progress-four-color-active-indicator-one-color)
65
+ * - Primary container (--ui-circular-progress-four-color-active-indicator-two-color)
66
+ * - Tertiary color (--ui-circular-progress-four-color-active-indicator-three-color)
67
+ * - Tertiary container (--ui-circular-progress-four-color-active-indicator-four-color)
68
+ *
69
+ * This property only affects the appearance when `indeterminate` is true.
70
+ *
71
+ * @default false
72
+ * @attribute fourcolor
73
+ */
74
+ @property({ type: Boolean, reflect: true }) accessor fourColor = false
75
+
76
+ /**
77
+ * Renders the circular progress indicator.
78
+ *
79
+ * Chooses between determinate and indeterminate rendering based on the
80
+ * `indeterminate` property inherited from UiRange.
81
+ *
82
+ * @returns The template result for the progress indicator.
83
+ */
84
+ protected override render(): TemplateResult {
85
+ if (this.indeterminate) {
86
+ return this.renderIndeterminateContainer()
87
+ }
88
+ return this.renderDeterminateContainer()
89
+ }
90
+
91
+ /**
92
+ * Renders the determinate progress indicator using SVG.
93
+ *
94
+ * Creates a circular progress bar that shows a specific progress value
95
+ * using stroke-dashoffset to control the visible portion of the circle.
96
+ * The progress is calculated based on the current value, min, and max properties.
97
+ *
98
+ * @returns SVG template with track and active track circles.
99
+ */
100
+ private renderDeterminateContainer() {
101
+ const dashOffset = (1 - this.value / this.max) * 100
102
+ return html`
103
+ <svg viewBox="0 0 4800 4800">
104
+ <circle class="track" pathLength="100"></circle>
105
+ <circle class="active-track" pathLength="100" stroke-dashoffset=${dashOffset}></circle>
106
+ </svg>
107
+ `
108
+ }
109
+
110
+ /**
111
+ * Renders the indeterminate progress indicator using CSS animations.
112
+ *
113
+ * Creates a spinning animation with two half-circles that expand and contract
114
+ * to create a continuous loading animation. The animation can cycle through
115
+ * multiple colors when the `fourColor` property is enabled.
116
+ *
117
+ * @returns HTML template with animated spinner elements.
118
+ */
119
+ private renderIndeterminateContainer() {
120
+ return html` <div class="spinner">
121
+ <div class="left">
122
+ <div class="circle"></div>
123
+ </div>
124
+ <div class="right">
125
+ <div class="circle"></div>
126
+ </div>
127
+ </div>`
128
+ }
129
+ }
@@ -44,10 +44,38 @@ export abstract class UiRange extends UiElement {
44
44
  */
45
45
  @property({ type: Number, converter: floatConverter }) accessor step = 1
46
46
 
47
+ /**
48
+ * Use an indeterminate progress indicator.
49
+ * @attribute
50
+ */
51
+ @property({ reflect: true, type: Boolean }) accessor indeterminate: boolean | undefined
52
+
53
+ override connectedCallback(): void {
54
+ super.connectedCallback()
55
+ if (!this.hasAttribute('role')) {
56
+ this.setAttribute('role', 'progressbar')
57
+ }
58
+ }
59
+
47
60
  protected override willUpdate(cp: PropertyValues<this>): void {
48
- if (cp.has('value') || cp.has('min') || cp.has('max') || cp.has('step')) {
61
+ if (cp.has('value') || cp.has('min') || cp.has('max') || cp.has('step') || cp.has('indeterminate')) {
49
62
  this.rangeChanged()
50
63
  }
64
+
65
+ if (cp.has('min')) {
66
+ this.setAttribute('aria-valuemin', String(this.min))
67
+ }
68
+ if (cp.has('max')) {
69
+ this.setAttribute('aria-valuemax', String(this.max))
70
+ }
71
+ if (cp.has('indeterminate') || cp.has('value')) {
72
+ if (this.indeterminate) {
73
+ this.removeAttribute('aria-valuenow')
74
+ } else {
75
+ this.setAttribute('aria-valuenow', String(this.value))
76
+ }
77
+ }
78
+ super.willUpdate(cp)
51
79
  }
52
80
 
53
81
  protected rangeChanged(): void {