@api-client/ui 0.3.2 → 0.3.4

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 (85) hide show
  1. package/build/src/elements/environment/EnvironmentEditor.d.ts.map +1 -1
  2. package/build/src/elements/environment/EnvironmentEditor.js +8 -6
  3. package/build/src/elements/environment/EnvironmentEditor.js.map +1 -1
  4. package/build/src/elements/har/HarViewer.d.ts.map +1 -1
  5. package/build/src/elements/har/HarViewer.js +13 -15
  6. package/build/src/elements/har/HarViewer.js.map +1 -1
  7. package/build/src/elements/http/RequestEditor.d.ts +2 -1
  8. package/build/src/elements/http/RequestEditor.d.ts.map +1 -1
  9. package/build/src/elements/http/RequestEditor.js +17 -12
  10. package/build/src/elements/http/RequestEditor.js.map +1 -1
  11. package/build/src/elements/http/RequestLog.d.ts.map +1 -1
  12. package/build/src/elements/http/RequestLog.js +34 -8
  13. package/build/src/elements/http/RequestLog.js.map +1 -1
  14. package/build/src/md/button/internals/base.d.ts.map +1 -1
  15. package/build/src/md/button/internals/base.js +2 -6
  16. package/build/src/md/button/internals/base.js.map +1 -1
  17. package/build/src/md/button/internals/button.styles.js +4 -4
  18. package/build/src/md/button/internals/button.styles.js.map +1 -1
  19. package/build/src/md/chip/internals/Chip.d.ts +11 -15
  20. package/build/src/md/chip/internals/Chip.d.ts.map +1 -1
  21. package/build/src/md/chip/internals/Chip.js +66 -104
  22. package/build/src/md/chip/internals/Chip.js.map +1 -1
  23. package/build/src/md/chip/internals/Chip.styles.d.ts.map +1 -1
  24. package/build/src/md/chip/internals/Chip.styles.js +114 -101
  25. package/build/src/md/chip/internals/Chip.styles.js.map +1 -1
  26. package/build/src/md/chip/internals/ChipSet.d.ts +16 -0
  27. package/build/src/md/chip/internals/ChipSet.d.ts.map +1 -0
  28. package/build/src/md/chip/internals/ChipSet.js +138 -0
  29. package/build/src/md/chip/internals/ChipSet.js.map +1 -0
  30. package/build/src/md/chip/internals/ChipSet.styles.d.ts +3 -0
  31. package/build/src/md/chip/internals/ChipSet.styles.d.ts.map +1 -0
  32. package/build/src/md/chip/internals/ChipSet.styles.js +9 -0
  33. package/build/src/md/chip/internals/ChipSet.styles.js.map +1 -0
  34. package/build/src/md/chip/ui-chip-set.d.ts +11 -0
  35. package/build/src/md/chip/ui-chip-set.d.ts.map +1 -0
  36. package/build/src/md/chip/ui-chip-set.js +27 -0
  37. package/build/src/md/chip/ui-chip-set.js.map +1 -0
  38. package/build/src/md/motion/animation.d.ts +5 -3
  39. package/build/src/md/motion/animation.d.ts.map +1 -1
  40. package/build/src/md/motion/animation.js +4 -2
  41. package/build/src/md/motion/animation.js.map +1 -1
  42. package/build/src/md/ripple/internals/ripple.styles.d.ts.map +1 -1
  43. package/build/src/md/ripple/internals/ripple.styles.js +20 -8
  44. package/build/src/md/ripple/internals/ripple.styles.js.map +1 -1
  45. package/build/src/md/switch/internals/Switch.styles.js +1 -1
  46. package/build/src/md/switch/internals/Switch.styles.js.map +1 -1
  47. package/build/src/md/tabs/internals/Tab.d.ts +25 -9
  48. package/build/src/md/tabs/internals/Tab.d.ts.map +1 -1
  49. package/build/src/md/tabs/internals/Tab.js +122 -53
  50. package/build/src/md/tabs/internals/Tab.js.map +1 -1
  51. package/build/src/md/tabs/internals/Tab.styles.d.ts.map +1 -1
  52. package/build/src/md/tabs/internals/Tab.styles.js +69 -64
  53. package/build/src/md/tabs/internals/Tab.styles.js.map +1 -1
  54. package/build/src/md/tabs/internals/Tabs.d.ts +52 -54
  55. package/build/src/md/tabs/internals/Tabs.d.ts.map +1 -1
  56. package/build/src/md/tabs/internals/Tabs.js +270 -330
  57. package/build/src/md/tabs/internals/Tabs.js.map +1 -1
  58. package/build/src/md/tabs/internals/Tabs.styles.d.ts.map +1 -1
  59. package/build/src/md/tabs/internals/Tabs.styles.js +13 -17
  60. package/build/src/md/tabs/internals/Tabs.styles.js.map +1 -1
  61. package/demo/md/chip/chip.html +33 -6
  62. package/demo/md/chip/chip.ts +111 -56
  63. package/demo/md/tabs/tabs.html +19 -0
  64. package/demo/md/tabs/tabs.ts +133 -83
  65. package/package.json +1 -1
  66. package/src/elements/environment/EnvironmentEditor.ts +8 -6
  67. package/src/elements/har/HarViewer.ts +13 -15
  68. package/src/elements/http/RequestEditor.ts +18 -13
  69. package/src/elements/http/RequestLog.ts +34 -8
  70. package/src/md/button/internals/base.ts +2 -6
  71. package/src/md/button/internals/button.styles.ts +4 -4
  72. package/src/md/chip/internals/Chip.styles.ts +114 -101
  73. package/src/md/chip/internals/Chip.ts +58 -88
  74. package/src/md/chip/internals/ChipSet.styles.ts +9 -0
  75. package/src/md/chip/internals/ChipSet.ts +142 -0
  76. package/src/md/chip/ui-chip-set.ts +15 -0
  77. package/src/md/motion/animation.ts +4 -2
  78. package/src/md/ripple/internals/ripple.styles.ts +20 -8
  79. package/src/md/switch/internals/Switch.styles.ts +1 -1
  80. package/src/md/tabs/internals/Tab.styles.ts +69 -64
  81. package/src/md/tabs/internals/Tab.ts +126 -43
  82. package/src/md/tabs/internals/Tabs.styles.ts +13 -17
  83. package/src/md/tabs/internals/Tabs.ts +259 -305
  84. package/test/elements/har/HarViewerElement.test.ts +1 -55
  85. package/test/ui/chip/UiChip.test.ts +18 -67
@@ -0,0 +1,142 @@
1
+ import { html, LitElement } from 'lit'
2
+ import { queryAssignedElements } from 'lit/decorators.js'
3
+
4
+ import Chip from './Chip.js'
5
+
6
+ /**
7
+ * A chip set component.
8
+ */
9
+ export default class ChipSet extends LitElement {
10
+ get chips() {
11
+ return this.childElements.filter((child): child is Chip => child instanceof Chip)
12
+ }
13
+
14
+ @queryAssignedElements() private accessor childElements!: HTMLElement[]
15
+ private readonly internals = this.attachInternals()
16
+
17
+ constructor() {
18
+ super()
19
+ this.addEventListener('focusin', this.updateTabIndices.bind(this))
20
+ this.addEventListener('update-focus', this.updateTabIndices.bind(this))
21
+ this.addEventListener('keydown', this.handleKeyDown.bind(this))
22
+ this.internals.role = 'toolbar'
23
+ }
24
+
25
+ override connectedCallback(): void {
26
+ super.connectedCallback()
27
+ if (!this.hasAttribute('role')) {
28
+ this.setAttribute('role', 'toolbar')
29
+ }
30
+ }
31
+
32
+ protected override render() {
33
+ return html`<slot @slotchange=${this.updateTabIndices}></slot>`
34
+ }
35
+
36
+ private handleKeyDown(event: KeyboardEvent) {
37
+ const isLeft = event.key === 'ArrowLeft'
38
+ const isRight = event.key === 'ArrowRight'
39
+ const isHome = event.key === 'Home'
40
+ const isEnd = event.key === 'End'
41
+ // Ignore non-navigation keys
42
+ if (!isLeft && !isRight && !isHome && !isEnd) {
43
+ return
44
+ }
45
+
46
+ const { chips } = this as { chips: MaybeMultiActionChip[] }
47
+ // Don't try to select another chip if there aren't any.
48
+ if (chips.length < 2) {
49
+ return
50
+ }
51
+
52
+ // Prevent default interactions, such as scrolling.
53
+ event.preventDefault()
54
+
55
+ if (isHome || isEnd) {
56
+ const index = isHome ? 0 : chips.length - 1
57
+ chips[index].focus({ trailing: isEnd })
58
+ this.updateTabIndices()
59
+ return
60
+ }
61
+
62
+ // Check if moving forwards or backwards
63
+ const isRtl = getComputedStyle(this).direction === 'rtl'
64
+ const forwards = isRtl ? isLeft : isRight
65
+ const focusedChip = chips.find((chip) => chip.matches(':focus-within'))
66
+ if (!focusedChip) {
67
+ // If there is not already a chip focused, select the first or last chip
68
+ // based on the direction we're traveling.
69
+ const nextChip = forwards ? chips[0] : chips[chips.length - 1]
70
+ nextChip.focus({ trailing: !forwards })
71
+ this.updateTabIndices()
72
+ return
73
+ }
74
+
75
+ const currentIndex = chips.indexOf(focusedChip)
76
+ let nextIndex = forwards ? currentIndex + 1 : currentIndex - 1
77
+ // Search for the next sibling that is not disabled to select.
78
+ // If we return to the host index, there is nothing to select.
79
+ while (nextIndex !== currentIndex) {
80
+ if (nextIndex >= chips.length) {
81
+ // Return to start if moving past the last item.
82
+ nextIndex = 0
83
+ } else if (nextIndex < 0) {
84
+ // Go to end if moving before the first item.
85
+ nextIndex = chips.length - 1
86
+ }
87
+
88
+ // Check if the next sibling is disabled. If so,
89
+ // move the index and continue searching.
90
+ //
91
+ // Some toolbar items may be focusable when disabled for increased
92
+ // visibility.
93
+ const nextChip = chips[nextIndex]
94
+ if (nextChip.disabled) {
95
+ if (forwards) {
96
+ nextIndex++
97
+ } else {
98
+ nextIndex--
99
+ }
100
+
101
+ continue
102
+ }
103
+
104
+ nextChip.focus({ trailing: !forwards })
105
+ this.updateTabIndices()
106
+ break
107
+ }
108
+ }
109
+
110
+ private updateTabIndices() {
111
+ // The chip that should be focusable is either the chip that currently has
112
+ // focus or the first chip that can be focused.
113
+ const { chips } = this
114
+ let chipToFocus: Chip | undefined
115
+ for (const chip of chips) {
116
+ const isChipFocusable = !chip.disabled
117
+ const chipIsFocused = chip.matches(':focus-within')
118
+ if (chipIsFocused && isChipFocusable) {
119
+ // Found the first chip that is actively focused. This overrides the
120
+ // first focusable chip found.
121
+ chipToFocus = chip
122
+ continue
123
+ }
124
+
125
+ if (isChipFocusable && !chipToFocus) {
126
+ chipToFocus = chip
127
+ }
128
+
129
+ // Disable non-focused chips. If we disable all of them, we'll grant focus
130
+ // to the first focusable child that was found.
131
+ chip.tabIndex = -1
132
+ }
133
+
134
+ if (chipToFocus) {
135
+ chipToFocus.tabIndex = 0
136
+ }
137
+ }
138
+ }
139
+
140
+ interface MaybeMultiActionChip extends Chip {
141
+ focus(options?: FocusOptions & { trailing?: boolean }): void
142
+ }
@@ -0,0 +1,15 @@
1
+ import type { CSSResultOrNative } from 'lit'
2
+ import { customElement } from 'lit/decorators.js'
3
+ import Element from './internals/ChipSet.js'
4
+ import styles from './internals/ChipSet.styles.js'
5
+
6
+ @customElement('ui-chip-set')
7
+ export class UiChipSetElement extends Element {
8
+ static override styles: CSSResultOrNative[] = [styles]
9
+ }
10
+
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ 'ui-chip-set': UiChipSetElement
14
+ }
15
+ }
@@ -9,9 +9,11 @@
9
9
  */
10
10
  export enum Easing {
11
11
  STANDARD = 'cubic-bezier(0.2, 0, 0, 1)',
12
- ACCELERATION = 'cubic-bezier(0.4, 0, 1, 1)',
13
- DECELERATION = 'cubic-bezier(0, 0, 0.2, 1)',
12
+ ACCELERATION = 'cubic-bezier(0.3, 0, 1, 1)',
13
+ DECELERATION = 'cubic-bezier(0, 0, 0, 1)',
14
14
  SHARP = 'cubic-bezier(0.4, 0, 0.6, 1)',
15
+ EMPHASIZED_ACCELERATE = 'cubic-bezier(0.3, 0, 0.8, 0.15)',
16
+ EMPHASIZED_DECELERATE = 'cubic-bezier(0.05, 0.7, 0.1, 1)',
15
17
  }
16
18
 
17
19
  /**
@@ -22,7 +22,8 @@ export default css`
22
22
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
23
23
  }
24
24
 
25
- .surface::before {
25
+ .surface::before,
26
+ .surface::after {
26
27
  position: absolute;
27
28
  opacity: 0;
28
29
  pointer-events: none;
@@ -37,10 +38,19 @@ export default css`
37
38
  inset: 0;
38
39
  }
39
40
 
41
+ .surface::after {
42
+ transition: opacity 375ms linear;
43
+ transform-origin: center center;
44
+ }
45
+
40
46
  .focused::before {
41
47
  transition-duration: 75ms;
42
48
  }
43
49
 
50
+ .pressed::after {
51
+ transition-duration: 105ms;
52
+ }
53
+
44
54
  .surface {
45
55
  border-radius: var(--md-ripple-state-layer-shape, 0);
46
56
  }
@@ -49,6 +59,14 @@ export default css`
49
59
  background-color: var(--md-ripple-hover-state-layer-color, var(--md-sys-color-primary));
50
60
  }
51
61
 
62
+ .surface::after {
63
+ background: radial-gradient(
64
+ closest-side,
65
+ var(--md-ripple-pressed-state-layer-color, var(--md-sys-color-primary)) max(100% - 70px, 65%),
66
+ transparent 100%
67
+ );
68
+ }
69
+
52
70
  .surface.hovered::before {
53
71
  opacity: var(--md-ripple-hover-state-layer-opacity, var(--md-sys-state-hover-state-layer-opacity));
54
72
  background-color: var(--md-ripple-hover-state-layer-color, var(--md-sys-color-primary));
@@ -59,14 +77,8 @@ export default css`
59
77
  background-color: var(--md-ripple-focus-state-layer-color, var(--md-sys-color-primary));
60
78
  }
61
79
 
62
- .surface.pressed::before {
80
+ .surface.pressed::after {
63
81
  opacity: var(--md-ripple-pressed-state-layer-opacity, var(--md-sys-state-pressed-state-layer-opacity));
64
- /* background-color: var(--md-ripple-pressed-state-layer-color, var(--md-sys-color-primary)); */
65
- background: radial-gradient(
66
- closest-side,
67
- var(--md-ripple-pressed-state-layer-color, var(--md-sys-color-primary)) max(100% - 70px, 65%),
68
- transparent 100%
69
- );
70
82
  }
71
83
 
72
84
  .surface.unbounded {
@@ -35,7 +35,7 @@ export default css`
35
35
 
36
36
  border: 2px var(--md-sys-color-outline) solid;
37
37
  border-radius: var(--md-sys-shape-corner-extra-large);
38
- background-color: var(--md-sys-color-surface-variant);
38
+ background-color: var(--md-sys-color-surface-container-highest);
39
39
 
40
40
  transition:
41
41
  opacity 90ms cubic-bezier(0.4, 0, 0.2, 1),
@@ -2,9 +2,15 @@ import { css } from 'lit'
2
2
 
3
3
  export default css`
4
4
  :host {
5
- display: inline-block;
6
- vertical-align: top;
5
+ display: inline-flex;
6
+ vertical-align: middle;
7
+ align-items: center;
8
+ justify-content: center;
9
+
10
+ position: relative;
7
11
  box-sizing: content-box;
12
+ padding: 0 16px;
13
+
8
14
  outline: none;
9
15
  text-rendering: auto;
10
16
  cursor: default;
@@ -18,55 +24,48 @@ export default css`
18
24
  letter-spacing: var(--md-sys-typescale-title-small-tracking);
19
25
  line-height: var(--md-sys-typescale-title-small-height);
20
26
 
21
- --md-ripple-hover-state-layer-color: var(--md-sys-color-primary);
22
- --md-ripple-focus-state-layer-color: var(--md-sys-color-primary);
23
- --md-ripple-pressed-state-layer-color: var(--md-sys-color-primary);
24
- /* background-color: var(--md-sys-color-surface); */
25
- color: var(--md-sys-color-on-surface);
27
+ --md-ripple-hover-state-layer-color: currentColor;
28
+ --md-ripple-focus-state-layer-color: currentColor;
29
+ --md-ripple-pressed-state-layer-color: currentColor;
30
+
31
+ --_background-color: var(--md-sys-color-surface);
32
+ --_color: var(--md-sys-color-on-surface-variant);
33
+ --_container-height: 48px;
34
+ --_active-indicator-color: var(--md-sys-color-primary);
35
+ --_active-indicator-shape: var(--md-primary-tab-active-indicator-shape, 3px 3px 0px 0px);
36
+ --_active-indicator-height: 2px;
37
+ --_active-indicator-opacity: 0;
38
+ --_with-icon-and-label-text-container-height: var(--md-primary-tab-with-icon-and-label-text-container-height, 64px);
39
+ --_icon-size: var(--md-secondary-tab-icon-size, 24px);
40
+
41
+ background-color: var(--_background-color);
42
+ color: var(--_color);
26
43
  }
27
44
 
28
45
  :host([hidden]) {
29
46
  display: none;
30
47
  }
31
48
 
32
- .surface {
33
- width: inherit;
34
- height: inherit;
35
- position: relative;
36
- border-radius: inherit;
37
- display: flex;
38
- justify-content: center;
39
- align-items: center;
40
- height: 48px;
49
+ .ripple.activated {
50
+ z-index: 1;
41
51
  }
42
52
 
43
- .surface.withIcon {
44
- height: 64px;
53
+ .focus-ring {
54
+ border-radius: var(--md-sys-shape-corner-small);
55
+ transition: margin-bottom var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
45
56
  }
46
57
 
47
- .container {
48
- position: absolute;
49
- inset: 0;
50
- z-index: 1;
51
- }
52
-
53
- .content {
54
- z-index: 2;
55
- display: flex;
56
- flex-direction: column;
57
- justify-content: center;
58
- align-items: center;
59
- padding: 0px 20px;
58
+ :host([selected]) .focus-ring {
59
+ margin-bottom: calc(var(--_active-indicator-height) + 1px);
60
60
  }
61
61
 
62
62
  .tab-content {
63
63
  display: flex;
64
64
  align-items: center;
65
- }
66
-
67
- .ripple {
68
- border-radius: inherit;
69
- z-index: 3;
65
+ justify-content: center;
66
+ flex-direction: row;
67
+ gap: 8px;
68
+ height: var(--_container-height);
70
69
  }
71
70
 
72
71
  .icon ::slotted(*) {
@@ -82,54 +81,60 @@ export default css`
82
81
  }
83
82
 
84
83
  :host([selected]) {
85
- color: var(--md-sys-color-primary);
84
+ --_active-indicator-opacity: 1;
86
85
  }
87
86
 
88
- .indicator {
89
- position: absolute;
90
- bottom: 0px;
91
- left: 0px;
92
- right: 0px;
93
- height: 2px;
94
- display: flex;
95
- justify-content: center;
87
+ :host([priority='primary']) {
88
+ --_color: var(--md-primary-tab-label-text-color, var(--md-sys-color-on-surface-variant));
96
89
  }
97
90
 
98
- .indicator.primary {
99
- height: 3px;
91
+ :host([priority='secondary']) {
92
+ --_color: var(--md-secondary-tab-label-text-color, var(--md-sys-color-on-surface-variant));
100
93
  }
101
94
 
102
- .pointer {
103
- height: inherit;
104
- display: block;
105
- background-color: var(--md-sys-color-primary);
95
+ :host([selected][priority='primary']) {
96
+ --_color: var(--md-primary-tab-active-label-text-color, var(--md-sys-color-primary));
106
97
  }
107
98
 
108
- .indicator.primary .pointer {
109
- border-radius: 3px 3px 0px 0px;
110
- width: 40px;
99
+ :host([selected][priority='secondary']) {
100
+ --_color: var(--md-secondary-tab-active-label-text-color, var(--md-sys-color-on-surface));
111
101
  }
112
102
 
113
- .indicator:not(.primary) .pointer {
114
- flex: 1;
103
+ .surface {
104
+ position: relative;
105
+ }
106
+
107
+ .indicator {
108
+ position: absolute;
109
+ box-sizing: border-box;
110
+ transform-origin: bottom left;
111
+ background: var(--_active-indicator-color);
112
+ border-radius: var(--_active-indicator-shape);
113
+ height: var(--_active-indicator-height);
114
+ inset: auto 0 0 0;
115
+ opacity: var(--_active-indicator-opacity);
116
+ }
117
+
118
+ :host([priority='primary']) {
119
+ --_active-indicator-height: 3px;
120
+ --_active-indicator-shape: 3px 3px 0px 0px;
115
121
  }
116
122
 
117
123
  :host([disabled]) {
118
124
  pointer-events: none;
119
125
  }
120
126
 
121
- :host([disabled]) .content {
122
- color: var(--md-sys-color-on-surface);
127
+ :host([disabled]) {
128
+ --_color: var(--md-sys-color-on-surface);
123
129
  opacity: 0.38;
124
130
  }
125
131
 
126
- :host(:hover) .container {
127
- background-color: var(--md-sys-color-primary);
128
- opacity: var(--md-sys-state-hover-state-layer-opacity);
132
+ .stacked .tab-content {
133
+ flex-direction: column;
134
+ gap: 2px;
129
135
  }
130
136
 
131
- :host(:focus) .container {
132
- background-color: var(--md-sys-color-primary);
133
- opacity: var(--md-sys-state-focus-state-layer-opacity);
137
+ .stacked.has-icon.has-label .tab-content {
138
+ height: var(--_with-icon-and-label-text-container-height);
134
139
  }
135
140
  `
@@ -1,13 +1,11 @@
1
- import { html, nothing, TemplateResult } from 'lit'
2
- import { property, queryAsync, state } from 'lit/decorators.js'
1
+ import { html, nothing, PropertyValues, TemplateResult } from 'lit'
2
+ import { property, query, state } from 'lit/decorators.js'
3
3
  import { ClassInfo, classMap } from 'lit/directives/class-map.js'
4
- import { when } from 'lit/directives/when.js'
5
- import { EndPressConfig } from '../../../controllers/ActionController.js'
6
- import UiRipple from '../../ripple/internals/ripple.js'
7
- import { ripple } from '../../effects/rippleDirective.js'
4
+ import type { BeginPressConfig, EndPressConfig } from '../../../controllers/ActionController.js'
5
+ import type UiRipple from '../../ripple/internals/ripple.js'
8
6
  import { isDisabled, setDisabled } from '../../../lib/disabled.js'
9
7
  import { UiElement } from '../../UiElement.js'
10
- import type { TabsPriority } from './Tabs.js'
8
+ import type { SizingInfo, TabsPriority } from './Tabs.js'
11
9
  import { Easing } from '../../motion/animation.js'
12
10
  import '../../ripple/ui-ripple.js'
13
11
 
@@ -32,31 +30,38 @@ export default class UiTab extends UiElement {
32
30
  * in the `ui-tabs`. This is only to render the tab in the selected state.
33
31
  * @attribute
34
32
  */
35
- @property({ reflect: true, type: Boolean }) accessor selected = false
33
+ @property({ reflect: true, type: Boolean }) accessor selected
36
34
 
37
35
  /**
38
36
  * @attribute
39
37
  */
40
- @property({ reflect: true, type: Boolean }) accessor priority: TabsPriority = 'primary'
38
+ @property({ reflect: true, type: String }) accessor priority: TabsPriority
41
39
 
42
40
  /**
43
41
  * @attribute
44
42
  */
45
- @property({ reflect: true, type: Boolean }) accessor indicated = false
43
+ @property({ reflect: true, type: Boolean }) accessor indicated
46
44
 
47
- @state() protected accessor hasIcon = false
48
-
49
- @state() protected accessor showRipple = false
45
+ /**
46
+ * Indicates whether the tab has an icon.
47
+ * This is set automatically when the "icon" slot is populated.
48
+ */
49
+ @state() protected accessor hasIcon
50
+ /**
51
+ * Indicates whether the tab only has an icon and no text.
52
+ * This is set automatically when the default slot is populated with only an icon.
53
+ */
54
+ @state() protected accessor iconOnly
50
55
 
51
- @queryAsync('ui-ripple') protected accessor ripple!: Promise<UiRipple | null>
52
-
53
- protected readonly getRipple = (): Promise<UiRipple | null> => {
54
- this.showRipple = true
55
- return this.ripple
56
- }
56
+ @query('ui-ripple') protected accessor ripple!: UiRipple | null
57
57
 
58
58
  constructor() {
59
59
  super()
60
+ this.priority = 'primary'
61
+ this.selected = false
62
+ this.indicated = false
63
+ this.hasIcon = false
64
+ this.iconOnly = true
60
65
  this.actionController.cancelKeyboardEvents = true
61
66
  this.addEventListener('keydown', this.handleKeyDown.bind(this))
62
67
  this.addEventListener('keyup', this.handleKeyUp.bind(this))
@@ -65,6 +70,7 @@ export default class UiTab extends UiElement {
65
70
  this.addEventListener('pointerup', this.handlePointerUp.bind(this))
66
71
  this.addEventListener('pointercancel', this.handlePointerCancel.bind(this))
67
72
  this.addEventListener('pointerleave', this.handlePointerLeave.bind(this))
73
+ this.addEventListener('pointerenter', this.handlePointerEnter.bind(this))
68
74
  this.addEventListener('contextmenu', this.handleContextMenu.bind(this))
69
75
  }
70
76
 
@@ -78,8 +84,23 @@ export default class UiTab extends UiElement {
78
84
  }
79
85
  }
80
86
 
87
+ protected override willUpdate(cp: PropertyValues<this>): void {
88
+ super.willUpdate(cp)
89
+ if (cp.has('selected')) {
90
+ this.setAttribute('aria-selected', String(this.selected))
91
+ }
92
+ }
93
+
94
+ override beginPress(options: BeginPressConfig): void {
95
+ super.beginPress(options)
96
+ this.classList.add('pressed')
97
+ this.ripple?.beginPress(options.positionEvent)
98
+ }
99
+
81
100
  override endPress(config: EndPressConfig): void {
82
101
  super.endPress(config)
102
+ this.classList.remove('pressed')
103
+ this.ripple?.endPress()
83
104
  const { cancelled, reason } = config
84
105
  if (cancelled) {
85
106
  return
@@ -89,18 +110,77 @@ export default class UiTab extends UiElement {
89
110
  }
90
111
  }
91
112
 
113
+ override handleClick(e: MouseEvent): void {
114
+ super.handleClick(e)
115
+ if (this.disabled) {
116
+ e.preventDefault()
117
+ e.stopPropagation()
118
+ return
119
+ }
120
+ this.endPress({ cancelled: false, actionData: { event: e } })
121
+ }
122
+
123
+ override handlePointerEnter(e: PointerEvent): void {
124
+ super.handlePointerEnter(e)
125
+ if (this.ripple) {
126
+ this.ripple.beginHover(e)
127
+ }
128
+ }
129
+
130
+ override handlePointerLeave(e: PointerEvent): void {
131
+ super.handlePointerLeave(e)
132
+ if (this.ripple) {
133
+ this.ripple.endHover()
134
+ }
135
+ }
136
+
137
+ override handleKeyUp(e: KeyboardEvent): void {
138
+ super.handleKeyUp(e)
139
+ if (this.disabled) {
140
+ e.preventDefault()
141
+ e.stopPropagation()
142
+ return
143
+ }
144
+ }
145
+
92
146
  /**
93
147
  * Sets the `_hasIcon` state property when the "icon" slot change event is dispatched.
94
148
  */
95
- protected handleIconChange(e: Event): void {
149
+ protected handleIconSlotChange(e: Event): void {
96
150
  const slot = e.target as HTMLSlotElement
97
151
  this.hasIcon = !!slot.assignedNodes().length
98
152
  }
99
153
 
154
+ protected handleSlotChange(e: Event): void {
155
+ const slot = e.target as HTMLSlotElement
156
+ // Check if there's any label text or elements. If not, then there is only
157
+ // an icon.
158
+ for (const node of slot.assignedNodes()) {
159
+ const hasTextContent = node.nodeType === Node.TEXT_NODE && !!(node as Text).wholeText.match(/\S/)
160
+ if (node.nodeType === Node.ELEMENT_NODE || hasTextContent) {
161
+ this.iconOnly = false
162
+ return
163
+ }
164
+ }
165
+ this.iconOnly = true
166
+ }
167
+
168
+ public getIndicatorSizing(): SizingInfo {
169
+ const element = this.priority === 'primary' ? this.shadowRoot?.querySelector('.tab-content') : this
170
+ if (!element) {
171
+ return { width: 0, left: 0 }
172
+ }
173
+ const rect = element.getBoundingClientRect()
174
+ return {
175
+ width: rect.width,
176
+ left: rect.left,
177
+ }
178
+ }
179
+
100
180
  /**
101
181
  * When `indicated` is `true` it animates the indicator to highlight the position of the tab.
102
182
  */
103
- highlight(): void {
183
+ public highlight(): void {
104
184
  if (!this.indicated) {
105
185
  return
106
186
  }
@@ -128,36 +208,41 @@ export default class UiTab extends UiElement {
128
208
  }
129
209
 
130
210
  protected override render(): TemplateResult {
131
- const { pressed = false } = this
211
+ const isPrimary = this.priority === 'primary'
132
212
  const containerClasses = classMap({
133
- surface: true,
134
- withIcon: this.hasIcon,
135
- pressed,
213
+ 'surface': true,
214
+ 'has-icon': this.hasIcon,
215
+ 'has-label': !this.iconOnly,
216
+ 'stacked': isPrimary,
136
217
  })
137
218
  return html`
138
- <div class="${containerClasses}" ${ripple(this.getRipple)}>
139
- <div class="container"></div>
140
- ${when(this.showRipple, this.renderRipple)}
141
- <div class="content">
219
+ ${this.renderFocusRing()} ${this.renderRipple()}
220
+ <div class="${containerClasses}">
221
+ <div class="tab-content">
142
222
  ${this.renderIcon()}
143
- <div class="tab-content"><slot></slot></div>
223
+ <slot @slotchange="${this.handleSlotChange}"></slot>
144
224
  </div>
145
- ${this.renderIndicator()}
225
+ ${isPrimary ? this.renderIndicator() : nothing}
146
226
  </div>
227
+ ${!isPrimary ? this.renderIndicator() : nothing}
147
228
  `
148
229
  }
149
230
 
150
- protected renderIcon(): TemplateResult {
151
- return html`
152
- <div class="icon">
153
- <slot name="icon" @slotchange="${this.handleIconChange}"></slot>
154
- </div>
155
- `
231
+ protected renderFocusRing(): TemplateResult {
232
+ return html`<md-focus-ring
233
+ part="focus-ring"
234
+ class="focus-ring"
235
+ inward
236
+ .control="${this as HTMLElement}"
237
+ ></md-focus-ring>`
156
238
  }
157
239
 
158
- protected renderRipple = (): TemplateResult => {
159
- const { disabled } = this
160
- return html`<ui-ripple class="ripple" ?disabled="${disabled}"></ui-ripple>`
240
+ protected renderRipple(): TemplateResult {
241
+ return html`<ui-ripple class="ripple" ?disabled="${this.disabled}"></ui-ripple>`
242
+ }
243
+
244
+ protected renderIcon(): TemplateResult {
245
+ return html` <slot name="icon" @slotchange="${this.handleIconSlotChange}"></slot> `
161
246
  }
162
247
 
163
248
  protected renderIndicator(): TemplateResult | typeof nothing {
@@ -169,8 +254,6 @@ export default class UiTab extends UiElement {
169
254
  indicator: true,
170
255
  primary: priority === 'primary',
171
256
  }
172
- return html`<div class="${classMap(classes)}">
173
- <span role="presentation" class="pointer"></span>
174
- </div>`
257
+ return html`<div class="${classMap(classes)}"></div>`
175
258
  }
176
259
  }