@cfpb/cfpb-design-system 4.3.2 → 5.0.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.
Files changed (165) hide show
  1. package/CHANGELOG.md +84 -3
  2. package/dist/index.css +1 -3
  3. package/dist/index.js +3812 -53
  4. package/package.json +5 -2
  5. package/src/components/cfpb-buttons/button-link.scss +0 -1
  6. package/src/components/cfpb-forms/multiselect-model.js +2 -5
  7. package/src/components/cfpb-forms/multiselect.js +1 -1
  8. package/src/components/cfpb-layout/email-signup.scss +4 -1
  9. package/src/components/cfpb-layout/featured-content-module.scss +10 -5
  10. package/src/components/cfpb-tables/table.scss +4 -1
  11. package/src/elements/abstracts/custom-props.css +1 -1
  12. package/src/elements/abstracts/sizing-vars.scss +25 -24
  13. package/src/elements/cfpb-button/cfpb-button.scss +2 -1
  14. package/src/elements/cfpb-button/index.js +2 -4
  15. package/src/elements/cfpb-checkbox-icon/index.js +2 -4
  16. package/src/elements/cfpb-expandable/cfpb-expandable.component.scss +6 -0
  17. package/src/elements/cfpb-expandable/index.js +4 -6
  18. package/src/elements/cfpb-file-upload/cfpb-file-upload.component.scss +0 -4
  19. package/src/elements/cfpb-file-upload/index.js +2 -4
  20. package/src/elements/cfpb-flag-usa/cfpb-flag-usa.component.scss +12 -0
  21. package/src/elements/cfpb-flag-usa/index.js +14 -0
  22. package/src/elements/cfpb-form-alert/index.js +5 -7
  23. package/src/elements/cfpb-form-choice/index.js +2 -4
  24. package/src/elements/cfpb-form-search/cfpb-form-search.component.scss +10 -0
  25. package/src/elements/cfpb-form-search/index.js +12 -11
  26. package/src/elements/cfpb-form-search-input/cfpb-form-search-input.component.scss +8 -5
  27. package/src/elements/cfpb-form-search-input/index.js +16 -6
  28. package/src/elements/cfpb-icon-text/index.js +2 -4
  29. package/src/elements/cfpb-label/index.js +2 -4
  30. package/src/elements/cfpb-list/index.js +5 -6
  31. package/src/elements/cfpb-list/index.spec.js +7 -5
  32. package/src/elements/cfpb-list-item/index.js +4 -6
  33. package/src/elements/cfpb-pagination/index.js +4 -6
  34. package/src/elements/cfpb-select/cfpb-select.component.scss +34 -57
  35. package/src/elements/cfpb-select/index.js +26 -18
  36. package/src/elements/cfpb-select/multiple-select-event-proxy.js +23 -17
  37. package/src/elements/cfpb-select/single-select-event-proxy.js +8 -5
  38. package/src/elements/cfpb-tag-filter/index.js +10 -5
  39. package/src/elements/cfpb-tag-group/index.js +28 -19
  40. package/src/elements/cfpb-tag-topic/index.js +9 -4
  41. package/src/elements/cfpb-tagline/cfpb-tagline.component.scss +33 -0
  42. package/src/elements/cfpb-tagline/index.js +51 -0
  43. package/src/elements/cfpb-utilities/media-query-service.js +1 -1
  44. package/src/elements/cfpb-utilities/parse-child-data.js +4 -1
  45. package/src/elements/index.js +1 -0
  46. package/src/index.scss +1 -2
  47. package/src/tokens/abstracts/custom-props.json +2334 -1639
  48. package/src/tokens/abstracts/vars.json +1679 -1315
  49. package/src/tokens/cfpb-button/vars.json +602 -409
  50. package/src/utilities/atomic-helpers.js +2 -2
  51. package/src/utilities/behavior/behavior.js +9 -2
  52. package/src/utilities/behavior/flyout-menu.js +5 -7
  53. package/src/utilities/event-observer.js +7 -3
  54. package/src/utilities/transition/alpha-transition.js +1 -1
  55. package/src/utilities/transition/base-transition.js +1 -1
  56. package/src/utilities/transition/max-height-transition.js +2 -2
  57. package/src/utilities/transition/move-transition.js +1 -1
  58. package/src/utilities/type-checkers.js +9 -9
  59. package/src/utilities/type-checkers.spec.js +2 -3
  60. package/dist/abstracts/index.js +0 -2
  61. package/dist/abstracts/index.js.map +0 -7
  62. package/dist/base/index.css +0 -3
  63. package/dist/base/index.css.map +0 -7
  64. package/dist/base/index.js +0 -2
  65. package/dist/base/index.js.map +0 -7
  66. package/dist/components/cfpb-buttons/index.css +0 -2
  67. package/dist/components/cfpb-buttons/index.css.map +0 -7
  68. package/dist/components/cfpb-buttons/index.js +0 -2
  69. package/dist/components/cfpb-buttons/index.js.map +0 -7
  70. package/dist/components/cfpb-expandables/index.css +0 -2
  71. package/dist/components/cfpb-expandables/index.css.map +0 -7
  72. package/dist/components/cfpb-expandables/index.js +0 -2
  73. package/dist/components/cfpb-expandables/index.js.map +0 -7
  74. package/dist/components/cfpb-forms/index.css +0 -2
  75. package/dist/components/cfpb-forms/index.css.map +0 -7
  76. package/dist/components/cfpb-forms/index.js +0 -2
  77. package/dist/components/cfpb-forms/index.js.map +0 -7
  78. package/dist/components/cfpb-icons/index.css +0 -2
  79. package/dist/components/cfpb-icons/index.css.map +0 -7
  80. package/dist/components/cfpb-icons/index.js +0 -2
  81. package/dist/components/cfpb-icons/index.js.map +0 -7
  82. package/dist/components/cfpb-layout/index.css +0 -2
  83. package/dist/components/cfpb-layout/index.css.map +0 -7
  84. package/dist/components/cfpb-layout/index.js +0 -2
  85. package/dist/components/cfpb-layout/index.js.map +0 -7
  86. package/dist/components/cfpb-notifications/index.css +0 -2
  87. package/dist/components/cfpb-notifications/index.css.map +0 -7
  88. package/dist/components/cfpb-notifications/index.js +0 -2
  89. package/dist/components/cfpb-notifications/index.js.map +0 -7
  90. package/dist/components/cfpb-pagination/index.css +0 -2
  91. package/dist/components/cfpb-pagination/index.css.map +0 -7
  92. package/dist/components/cfpb-pagination/index.js +0 -2
  93. package/dist/components/cfpb-pagination/index.js.map +0 -7
  94. package/dist/components/cfpb-tables/index.css +0 -2
  95. package/dist/components/cfpb-tables/index.css.map +0 -7
  96. package/dist/components/cfpb-tables/index.js +0 -2
  97. package/dist/components/cfpb-tables/index.js.map +0 -7
  98. package/dist/components/cfpb-tooltips/index.css +0 -2
  99. package/dist/components/cfpb-tooltips/index.css.map +0 -7
  100. package/dist/components/cfpb-tooltips/index.js +0 -2
  101. package/dist/components/cfpb-tooltips/index.js.map +0 -7
  102. package/dist/components/cfpb-typography/index.css +0 -2
  103. package/dist/components/cfpb-typography/index.css.map +0 -7
  104. package/dist/components/cfpb-typography/index.js +0 -2
  105. package/dist/components/cfpb-typography/index.js.map +0 -7
  106. package/dist/elements/abstracts/index.js +0 -2
  107. package/dist/elements/abstracts/index.js.map +0 -7
  108. package/dist/elements/base/index.css +0 -3
  109. package/dist/elements/base/index.css.map +0 -7
  110. package/dist/elements/base/index.js +0 -2
  111. package/dist/elements/base/index.js.map +0 -7
  112. package/dist/elements/cfpb-button/index.js +0 -47
  113. package/dist/elements/cfpb-button/index.js.map +0 -7
  114. package/dist/elements/cfpb-checkbox-icon/index.js +0 -29
  115. package/dist/elements/cfpb-checkbox-icon/index.js.map +0 -7
  116. package/dist/elements/cfpb-expandable/index.css +0 -2
  117. package/dist/elements/cfpb-expandable/index.css.map +0 -7
  118. package/dist/elements/cfpb-expandable/index.js +0 -33
  119. package/dist/elements/cfpb-expandable/index.js.map +0 -7
  120. package/dist/elements/cfpb-file-upload/index.js +0 -47
  121. package/dist/elements/cfpb-file-upload/index.js.map +0 -7
  122. package/dist/elements/cfpb-form-alert/index.js +0 -32
  123. package/dist/elements/cfpb-form-alert/index.js.map +0 -7
  124. package/dist/elements/cfpb-form-choice/index.js +0 -46
  125. package/dist/elements/cfpb-form-choice/index.js.map +0 -7
  126. package/dist/elements/cfpb-form-search/index.js +0 -41
  127. package/dist/elements/cfpb-form-search/index.js.map +0 -7
  128. package/dist/elements/cfpb-form-search-input/index.js +0 -41
  129. package/dist/elements/cfpb-form-search-input/index.js.map +0 -7
  130. package/dist/elements/cfpb-icon-text/index.js +0 -29
  131. package/dist/elements/cfpb-icon-text/index.js.map +0 -7
  132. package/dist/elements/cfpb-label/index.js +0 -36
  133. package/dist/elements/cfpb-label/index.js.map +0 -7
  134. package/dist/elements/cfpb-list/index.js +0 -39
  135. package/dist/elements/cfpb-list/index.js.map +0 -7
  136. package/dist/elements/cfpb-list-item/index.js +0 -39
  137. package/dist/elements/cfpb-list-item/index.js.map +0 -7
  138. package/dist/elements/cfpb-multiselect/index.js +0 -48
  139. package/dist/elements/cfpb-multiselect/index.js.map +0 -7
  140. package/dist/elements/cfpb-pagination/index.js +0 -32
  141. package/dist/elements/cfpb-pagination/index.js.map +0 -7
  142. package/dist/elements/cfpb-select/index.css +0 -2
  143. package/dist/elements/cfpb-select/index.css.map +0 -7
  144. package/dist/elements/cfpb-select/index.js +0 -42
  145. package/dist/elements/cfpb-select/index.js.map +0 -7
  146. package/dist/elements/cfpb-select-list/index.js +0 -39
  147. package/dist/elements/cfpb-select-list/index.js.map +0 -7
  148. package/dist/elements/cfpb-tag-filter/index.js +0 -31
  149. package/dist/elements/cfpb-tag-filter/index.js.map +0 -7
  150. package/dist/elements/cfpb-tag-group/index.js +0 -29
  151. package/dist/elements/cfpb-tag-group/index.js.map +0 -7
  152. package/dist/elements/cfpb-tag-topic/index.js +0 -30
  153. package/dist/elements/cfpb-tag-topic/index.js.map +0 -7
  154. package/dist/elements/cfpb-utilities/index.js +0 -2
  155. package/dist/elements/cfpb-utilities/index.js.map +0 -7
  156. package/dist/elements/index.css +0 -2
  157. package/dist/elements/index.css.map +0 -7
  158. package/dist/elements/index.js +0 -53
  159. package/dist/elements/index.js.map +0 -7
  160. package/dist/index.css.map +0 -7
  161. package/dist/index.js.map +0 -7
  162. package/dist/utilities/index.css +0 -2
  163. package/dist/utilities/index.css.map +0 -7
  164. package/dist/utilities/index.js +0 -2
  165. package/dist/utilities/index.js.map +0 -7
@@ -47,7 +47,7 @@ describe('<cfpb-list> tests', () => {
47
47
  expect(list.checkedItems).toContain(list.items[0]);
48
48
  expect(list.checkedItems).toContain(list.items[1]);
49
49
 
50
- const event = new CustomEvent('click-item', {
50
+ const event = new CustomEvent('item-click', {
51
51
  bubbles: true,
52
52
  composed: true,
53
53
  });
@@ -57,7 +57,7 @@ describe('<cfpb-list> tests', () => {
57
57
  expect(list.checkedItems).toEqual([list.items[1]]);
58
58
  });
59
59
 
60
- test('click-item toggles single selection', async () => {
60
+ test('item-click toggles single selection', async () => {
61
61
  list.childData = JSON.stringify([{ value: 'A' }, { value: 'B' }]);
62
62
  await list.updateComplete;
63
63
 
@@ -66,7 +66,7 @@ describe('<cfpb-list> tests', () => {
66
66
 
67
67
  list.items[0].checked = true;
68
68
  list.items[0].dispatchEvent(
69
- new CustomEvent('click-item', { bubbles: true, composed: true }),
69
+ new CustomEvent('item-click', { bubbles: true, composed: true }),
70
70
  );
71
71
 
72
72
  expect(list.checkedItems).toEqual([list.items[0]]);
@@ -74,7 +74,7 @@ describe('<cfpb-list> tests', () => {
74
74
 
75
75
  list.items[0].checked = false;
76
76
  list.items[0].dispatchEvent(
77
- new CustomEvent('click-item', { bubbles: true, composed: true }),
77
+ new CustomEvent('item-click', { bubbles: true, composed: true }),
78
78
  );
79
79
  expect(list.checkedItems).toEqual([]);
80
80
  });
@@ -86,7 +86,7 @@ describe('<cfpb-list> tests', () => {
86
86
  const listenerSpy = jest.fn();
87
87
  list.addEventListener('item-click', listenerSpy);
88
88
 
89
- const event = new CustomEvent('click-item', {
89
+ const event = new CustomEvent('item-click', {
90
90
  bubbles: true,
91
91
  composed: true,
92
92
  });
@@ -157,9 +157,11 @@ describe('<cfpb-list> tests', () => {
157
157
  });
158
158
 
159
159
  test('invalid childData logs error', async () => {
160
+ // eslint-disable-next-line no-console
160
161
  console.error = jest.fn();
161
162
  list.childData = 'not-json';
162
163
  await list.updateComplete;
164
+ // eslint-disable-next-line no-console
163
165
  expect(console.error).toHaveBeenCalled();
164
166
  });
165
167
 
@@ -1,4 +1,4 @@
1
- import { html, LitElement, css, unsafeCSS } from 'lit';
1
+ import { html, LitElement } from 'lit';
2
2
  import styles from './cfpb-list-item.component.scss';
3
3
  import { ref, createRef } from 'lit/directives/ref.js';
4
4
  import { CfpbCheckboxIcon } from '../cfpb-checkbox-icon';
@@ -8,9 +8,7 @@ import { CfpbCheckboxIcon } from '../cfpb-checkbox-icon';
8
8
  * @slot - The text for the list item.
9
9
  */
10
10
  export class CfpbListItem extends LitElement {
11
- static styles = css`
12
- ${unsafeCSS(styles)}
13
- `;
11
+ static styles = styles;
14
12
 
15
13
  #checkboxIcon = createRef();
16
14
  #value;
@@ -49,7 +47,7 @@ export class CfpbListItem extends LitElement {
49
47
  this.addEventListener('keydown', this.#onKeyDown);
50
48
  }
51
49
 
52
- this.addEventListener('click', this.#onClick);
50
+ this.addEventListener('pointerdown', this.#onClick);
53
51
  }
54
52
 
55
53
  connectedCallback() {
@@ -102,7 +100,7 @@ export class CfpbListItem extends LitElement {
102
100
  // … Otherwise, toggle the checked state.
103
101
  this.checked = !this.checked;
104
102
  this.dispatchEvent(
105
- new CustomEvent('click-item', {
103
+ new CustomEvent('item-click', {
106
104
  detail: { checked: this.checked, value: this.value },
107
105
  bubbles: true,
108
106
  composed: true,
@@ -1,8 +1,8 @@
1
- import { html, LitElement, css, unsafeCSS } from 'lit';
1
+ import { html, LitElement } from 'lit';
2
2
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
3
3
  import styles from './cfpb-pagination.component.scss';
4
- import leftIcon from '../../components/cfpb-icons/icons/left.svg';
5
- import rightIcon from '../../components/cfpb-icons/icons/right.svg';
4
+ import leftIcon from '../../components/cfpb-icons/icons/left.svg?raw';
5
+ import rightIcon from '../../components/cfpb-icons/icons/right.svg?raw';
6
6
  import { I18nService, MediaQueryService } from '../cfpb-utilities/';
7
7
 
8
8
  /**
@@ -15,9 +15,7 @@ export class CfpbPagination extends LitElement {
15
15
  #isMobile;
16
16
  #i18n;
17
17
 
18
- static styles = css`
19
- ${unsafeCSS(styles)}
20
- `;
18
+ static styles = styles;
21
19
 
22
20
  /**
23
21
  * @property {number} currentPage - The currently selected page.
@@ -64,7 +64,11 @@
64
64
  .o-select {
65
65
  position: relative;
66
66
  display: grid;
67
- grid-template-columns: 1fr 38px;
67
+ grid-template-columns: 1fr 35px;
68
+
69
+ // Height should be 35px (33px + 1px top and bottom border).
70
+ min-height: 33px;
71
+ border: 1px solid var(--select-border);
68
72
 
69
73
  &:hover {
70
74
  border-color: var(--select-border-hover);
@@ -83,25 +87,15 @@
83
87
  // Header
84
88
  //
85
89
 
86
- &__header {
90
+ &__cues {
87
91
  padding: 0;
88
92
  border: 0;
89
93
  background-color: transparent;
90
94
  cursor: pointer;
91
95
 
92
96
  display: flex;
93
-
94
- // Single select.
95
- &:has(.o-select__label) {
96
- justify-content: space-between;
97
- align-items: center;
98
- gap: 10px;
99
- }
100
-
101
- // Multiselect.
102
- cfpb-form-search-input {
103
- width: 100%;
104
- }
97
+ justify-content: space-evenly;
98
+ align-items: center;
105
99
 
106
100
  .o-select__cue-close,
107
101
  .o-select__cue-open {
@@ -110,10 +104,12 @@
110
104
 
111
105
  &[aria-expanded='false'] .o-select__cue-open {
112
106
  display: block;
107
+ margin-top: -1px;
113
108
  }
114
109
 
115
110
  &[aria-expanded='true'] .o-select__cue-close {
116
111
  display: block;
112
+ margin-top: -5px;
117
113
  }
118
114
  }
119
115
 
@@ -121,11 +117,11 @@
121
117
  padding: 6.5px 10px;
122
118
  }
123
119
 
124
- // Using the button element with .o-select__header requires setting
120
+ // Using the button element with .o-select__cues requires setting
125
121
  // an explicit width.
126
- button.o-select__header {
127
- width: 100%;
128
- text-align: left;
122
+ button.o-select__cues {
123
+ background: var(--select-icon-bg-default);
124
+ border-left: 1px solid var(--select-border-default);
129
125
  }
130
126
 
131
127
  //
@@ -133,22 +129,15 @@
133
129
  //
134
130
 
135
131
  &__label {
136
- // Grow to available width.
137
- flex-grow: 1;
138
- padding: math.div(7px, $base-font-size-px) + em
139
- math.div(10px, $base-font-size-px) + em;
132
+ display: flex;
133
+ justify-content: space-between;
134
+ align-items: center;
135
+ gap: 10px;
136
+ padding-left: math.div(10px, $base-font-size-px) + em;
140
137
 
141
138
  cursor: pointer;
142
139
  }
143
140
 
144
- &__cues {
145
- align-self: center;
146
- background: var(--select-icon-bg-default);
147
- border-left: 1px solid var(--select-border-default);
148
- padding: math.div(7px, $base-font-size-px) + em
149
- math.div(10px, $base-font-size-px) + em;
150
- }
151
-
152
141
  &__content {
153
142
  box-sizing: border-box;
154
143
  overflow-x: hidden;
@@ -167,28 +156,6 @@
167
156
  }
168
157
  }
169
158
 
170
- //
171
- // select with a border modifier
172
- //
173
-
174
- &--border {
175
- border: 1px solid var(--select-border);
176
- }
177
-
178
- @media print {
179
- // Hide the interactive select cues when printing
180
- &__header[aria-expanded='true'] &__cue-close,
181
- &__header[aria-expanded='false'] &__cue-open {
182
- display: none;
183
- } // Ensure all selects are expanded when printing.
184
- // To accommodate print stylesheets that display the raw URL after links,
185
- // set an enormous max height to accommodate selects that have a lot of links.
186
- &__content[aria-expanded='false'] {
187
- display: block;
188
- max-height: 99999px !important;
189
- }
190
- }
191
-
192
159
  &--down {
193
160
  &::before {
194
161
  bottom: -1px;
@@ -206,13 +173,27 @@
206
173
  top: unset;
207
174
  }
208
175
  }
176
+
177
+ @media print {
178
+ // Hide the interactive select cues when printing
179
+ &__cues[aria-expanded='true'] &__cue-close,
180
+ &__cues[aria-expanded='false'] &__cue-open {
181
+ display: none;
182
+ } // Ensure all selects are expanded when printing.
183
+ // To accommodate print stylesheets that display the raw URL after links,
184
+ // set an enormous max height to accommodate selects that have a lot of links.
185
+ &__content[aria-expanded='false'] {
186
+ display: block;
187
+ max-height: 99999px !important;
188
+ }
189
+ }
209
190
  }
210
191
  }
211
192
 
212
193
  // The drop-down is expanded.
213
194
  :host([open]) {
214
195
  .o-select {
215
- // The divider between __header and __content.
196
+ // The divider between input and content.
216
197
  &::before {
217
198
  position: absolute;
218
199
  z-index: 11;
@@ -229,10 +210,6 @@
229
210
  html[lang='ar'] {
230
211
  :host {
231
212
  .o-select {
232
- &__header {
233
- text-align: right;
234
- }
235
-
236
213
  &__cues {
237
214
  text-align: left;
238
215
  }
@@ -1,9 +1,9 @@
1
- import { html, LitElement, css, unsafeCSS, nothing } from 'lit';
1
+ import { html, LitElement, nothing } from 'lit';
2
2
  import { ref, createRef } from 'lit/directives/ref.js';
3
3
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
4
4
  import styles from './cfpb-select.component.scss';
5
- import expandIcon from '../../components/cfpb-icons/icons/down.svg';
6
- import collapseIcon from '../../components/cfpb-icons/icons/up.svg';
5
+ import expandIcon from '../../components/cfpb-icons/icons/down.svg?raw';
6
+ import collapseIcon from '../../components/cfpb-icons/icons/up.svg?raw';
7
7
  import { CfpbFormSearchInput } from '../cfpb-form-search-input';
8
8
  import { SearchService } from '../cfpb-utilities/search-service.js';
9
9
  import { MaxHeightTransition } from '../../utilities/transition/max-height-transition';
@@ -20,9 +20,7 @@ import { MultipleSelectEventProxy } from './multiple-select-event-proxy.js';
20
20
  * @slot - The main content for the select.
21
21
  */
22
22
  export class CfpbSelect extends LitElement {
23
- static styles = css`
24
- ${unsafeCSS(styles)}
25
- `;
23
+ static styles = styles;
26
24
 
27
25
  #eventProxy;
28
26
  #flyoutMenu;
@@ -82,6 +80,10 @@ export class CfpbSelect extends LitElement {
82
80
  this.#eventProxy.onFocus();
83
81
  });
84
82
 
83
+ this.addEventListener('blur', () => {
84
+ this.#eventProxy.onBlur(this);
85
+ });
86
+
85
87
  this.addEventListener('keydown', (evt) => {
86
88
  this.#eventProxy.onKeyDown(evt, this);
87
89
  });
@@ -160,7 +162,12 @@ export class CfpbSelect extends LitElement {
160
162
  }
161
163
 
162
164
  #onClear() {
165
+ this.#flyoutMenu.suspend();
166
+ this.isExpanded = false;
163
167
  this.#list.value.showAllItems();
168
+ this.#noResults = false;
169
+ this.requestUpdate();
170
+ this.#flyoutMenu.resume();
164
171
  }
165
172
 
166
173
  #initFlyoutMenu() {
@@ -264,7 +271,7 @@ export class CfpbSelect extends LitElement {
264
271
  }
265
272
 
266
273
  #onTagClick(evt) {
267
- this.#eventProxy?.onTagClick(evt, this);
274
+ this.#eventProxy?.onTagClick(evt, this, this.#tagGroup.value);
268
275
  }
269
276
 
270
277
  render() {
@@ -288,27 +295,27 @@ export class CfpbSelect extends LitElement {
288
295
  : nothing}
289
296
 
290
297
  <div
291
- class="o-select o-select--border"
298
+ class="o-select"
292
299
  data-js-hook="behavior_flyout-menu"
293
300
  ${ref(this.#root)}
294
301
  >
295
302
  ${this.#renderInput()}
296
303
 
297
304
  <button
298
- class="o-select__header"
305
+ tabindex=${this.multiple ? '-1' : '0'}
306
+ class="o-select__cues"
299
307
  title="Expand content"
300
308
  data-js-hook="behavior_flyout-menu_trigger"
301
309
  ${ref(this.#headerDom)}
310
+ @click=${this.#onClick}
302
311
  >
303
- <span class="o-select__cues" @click=${this.#onClick}>
304
- <span class="o-select__cue-open" role="img" aria-label="Show">
305
- ${unsafeSVG(expandIcon)}
306
- <span class="u-visually-hidden">Show</span>
307
- </span>
308
- <span class="o-select__cue-close" role="img" aria-label="Hide">
309
- ${unsafeSVG(collapseIcon)}
310
- <span class="u-visually-hidden">Hide</span>
311
- </span>
312
+ <span class="o-select__cue-open" role="img" aria-label="Show">
313
+ ${unsafeSVG(expandIcon)}
314
+ <span class="u-visually-hidden">Show</span>
315
+ </span>
316
+ <span class="o-select__cue-close" role="img" aria-label="Hide">
317
+ ${unsafeSVG(collapseIcon)}
318
+ <span class="u-visually-hidden">Hide</span>
312
319
  </span>
313
320
  </button>
314
321
  <div
@@ -317,6 +324,7 @@ export class CfpbSelect extends LitElement {
317
324
  ${ref(this.#contentDom)}
318
325
  >
319
326
  <cfpb-list
327
+ tabindex=${this.#noResults ? '-1' : '0'}
320
328
  @item-click=${this.#onItemClick}
321
329
  ?multiple=${this.multiple}
322
330
  .childData=${this.optionList}
@@ -6,6 +6,15 @@ export class MultipleSelectEventProxy {
6
6
  this.flyout = flyout;
7
7
  }
8
8
 
9
+ onFocus() {
10
+ this.input.focus();
11
+ }
12
+
13
+ onBlur() {
14
+ // In multiselect, blur happens when inside the list, so we ignore any
15
+ // blur actions.
16
+ }
17
+
9
18
  onClick(evt, host) {
10
19
  const target = evt.currentTarget;
11
20
 
@@ -19,10 +28,9 @@ export class MultipleSelectEventProxy {
19
28
  onItemClick(evt, host) {
20
29
  host.optionList = this.list.childData ?? [];
21
30
  evt.currentTarget.focusItemAt(evt.detail.index);
22
- //host.requestUpdate();
23
31
  }
24
32
 
25
- onTagClick(evt, host) {
33
+ async onTagClick(evt, host, tagGroup) {
26
34
  const remaining =
27
35
  this.tagGroup.tagList.filter((tag) => tag !== evt.detail.target) ?? [];
28
36
 
@@ -31,7 +39,10 @@ export class MultipleSelectEventProxy {
31
39
  checked: remaining.some((tag) => tag.value === item.value),
32
40
  }));
33
41
 
34
- //host.requestUpdate();
42
+ // Wait for tagGroup items to refresh.
43
+ await host.updateComplete;
44
+
45
+ tagGroup.focus();
35
46
  }
36
47
 
37
48
  onKeyDown(evt, host) {
@@ -40,13 +51,15 @@ export class MultipleSelectEventProxy {
40
51
  if (focused === 'CFPB-FORM-SEARCH-INPUT') {
41
52
  switch (evt.key) {
42
53
  case 'Tab':
43
- evt.preventDefault();
44
54
  if (evt.shiftKey) {
45
- if (host.isExpanded) host.isExpanded = false;
46
- this.input.focus();
47
- } else {
48
- if (!host.isExpanded) host.isExpanded = true;
49
- this.list.focusItemAt(0);
55
+ if (host.isExpanded) {
56
+ evt.preventDefault();
57
+ host.isExpanded = false;
58
+ this.input.focus();
59
+ } else if (this.tagGroup.tagList.length > 0) {
60
+ evt.preventDefault();
61
+ this.tagGroup.focus();
62
+ }
50
63
  }
51
64
  break;
52
65
  case 'ArrowDown':
@@ -59,14 +72,11 @@ export class MultipleSelectEventProxy {
59
72
  host.isExpanded = !host.isExpanded;
60
73
  break;
61
74
  }
62
- }
63
-
64
- if (focused === 'CFPB-LIST-ITEM') {
75
+ } else if (focused === 'CFPB-LIST-ITEM') {
65
76
  switch (evt.key) {
66
77
  case 'Tab':
67
78
  if (evt.shiftKey) {
68
79
  if (this.list.focusedIndex === 0) {
69
- //host.shadowRoot.activeElement.blur();
70
80
  evt.preventDefault();
71
81
  this.list.focusItemAt(-1);
72
82
  this.input.focus();
@@ -81,8 +91,4 @@ export class MultipleSelectEventProxy {
81
91
  host.isExpanded = false;
82
92
  }
83
93
  }
84
-
85
- onFocus() {
86
- this.input.focus();
87
- }
88
94
  }
@@ -5,6 +5,14 @@ export class SingleSelectEventProxy {
5
5
  this.header = header;
6
6
  }
7
7
 
8
+ onFocus() {
9
+ this.header.focus();
10
+ }
11
+
12
+ onBlur(host) {
13
+ host.isExpanded = false;
14
+ }
15
+
8
16
  onClick(evt, host) {
9
17
  if (evt.currentTarget.classList.contains('o-select__label')) {
10
18
  this.header.focus();
@@ -24,7 +32,6 @@ export class SingleSelectEventProxy {
24
32
  checked: item.value === selected,
25
33
  }));
26
34
 
27
- //host.requestUpdate();
28
35
  host.isExpanded = false;
29
36
  }
30
37
 
@@ -40,8 +47,4 @@ export class SingleSelectEventProxy {
40
47
  }
41
48
  }
42
49
  }
43
-
44
- onFocus() {
45
- // Additional actions done on focus can be added here.
46
- }
47
50
  }
@@ -1,7 +1,7 @@
1
- import { html, LitElement, css, unsafeCSS } from 'lit';
1
+ import { html, LitElement } from 'lit';
2
2
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
3
3
  import styles from './cfpb-tag-filter.component.scss';
4
- import icon from '../../components/cfpb-icons/icons/error.svg';
4
+ import icon from '../../components/cfpb-icons/icons/error.svg?raw';
5
5
 
6
6
  /**
7
7
  *
@@ -9,9 +9,7 @@ import icon from '../../components/cfpb-icons/icons/error.svg';
9
9
  * @slot - The content for the filter tag.
10
10
  */
11
11
  export class CfpbTagFilter extends LitElement {
12
- static styles = css`
13
- ${unsafeCSS(styles)}
14
- `;
12
+ static styles = styles;
15
13
 
16
14
  /**
17
15
  * @property {string} for - Associate the label with an ID elsewhere.
@@ -28,6 +26,13 @@ export class CfpbTagFilter extends LitElement {
28
26
  this.value = '';
29
27
  }
30
28
 
29
+ async focus() {
30
+ // Wait for UI to settle.
31
+ await this.updateComplete;
32
+
33
+ this.shadowRoot.querySelector('button').focus();
34
+ }
35
+
31
36
  #onClick() {
32
37
  this.dispatchEvent(
33
38
  new CustomEvent('tag-click', {
@@ -1,4 +1,4 @@
1
- import { html, LitElement, css, unsafeCSS } from 'lit';
1
+ import { html, LitElement } from 'lit';
2
2
  import styles from './cfpb-tag-group.component.scss';
3
3
  import { parseChildData } from '../cfpb-utilities/parse-child-data';
4
4
 
@@ -12,14 +12,12 @@ const SUPPORTED_TAG_LIST = ['CFPB-TAG-FILTER', 'CFPB-TAG-TOPIC'];
12
12
  * The tag group has a list of tags in the lightDOM that gets re-written
13
13
  * inside an unordered list in the shadowDOM so that it is read out
14
14
  * as a list of items in VoiceOver.
15
- * @attribute {string} lang - The element's language.
16
- * @fires addtag - A tag was added to the group.
17
- * @fires removetag - A tag was removed from the group.
15
+ * @fires CfpbTagGroup#event:"tag-added" - A tag was added to the group.
16
+ * @fires CfpbTagGroup#event:"tag-click" - A tag was clicked.
17
+ * @fires CfpbTagGroup#event:"tag-removed" - A tag was removed to the group.
18
18
  */
19
19
  export class CfpbTagGroup extends LitElement {
20
- static styles = css`
21
- ${unsafeCSS(styles)}
22
- `;
20
+ static styles = styles;
23
21
 
24
22
  /**
25
23
  * @property {string} childData - Structure data to create child components.
@@ -78,6 +76,14 @@ export class CfpbTagGroup extends LitElement {
78
76
  }
79
77
  }
80
78
 
79
+ async focus() {
80
+ // Wait for tagList to update.
81
+ await this.updateComplete;
82
+
83
+ const firstChild = this.tagList[0];
84
+ if (firstChild) firstChild.focus();
85
+ }
86
+
81
87
  #renderTagsFromData(arr) {
82
88
  if (!Array.isArray(arr)) return;
83
89
 
@@ -192,7 +198,7 @@ export class CfpbTagGroup extends LitElement {
192
198
 
193
199
  /**
194
200
  * Add a tag to the light and dark DOM.
195
- * @param {*} tag - The tag to add.
201
+ * @param {HTMLElement} tag - The tag to add.
196
202
  * @param {number} index - The position at which to add the tag.
197
203
  * @returns {boolean} false if the tag is already in the light DOM.
198
204
  */
@@ -207,11 +213,12 @@ export class CfpbTagGroup extends LitElement {
207
213
  this.#insertIntoShadowDom(tag, index);
208
214
 
209
215
  this.#refreshTagList();
216
+ return true;
210
217
  }
211
218
 
212
219
  /**
213
220
  * Add a tag to the light DOM.
214
- * @param {*} tag - The tag to add.
221
+ * @param {HTMLElement} tag - The tag to add.
215
222
  * @param {number} index - The position at which to add the tag.
216
223
  */
217
224
  #insertIntoLightDom(tag, index) {
@@ -224,7 +231,7 @@ export class CfpbTagGroup extends LitElement {
224
231
 
225
232
  /**
226
233
  * Add a tag to the shadow DOM.
227
- * @param {*} tag - The tag to add.
234
+ * @param {HTMLElement} tag - The tag to add.
228
235
  * @param {number} index - The position at which to add the tag.
229
236
  */
230
237
  #insertIntoShadowDom(tag, index) {
@@ -267,7 +274,7 @@ export class CfpbTagGroup extends LitElement {
267
274
  }
268
275
 
269
276
  /**
270
- * @param {*} tag - The tag to add.
277
+ * @param {HTMLElement} tag - The tag to add.
271
278
  * @returns {string} A unique ID.
272
279
  */
273
280
  #tagIdentifier(tag) {
@@ -277,7 +284,7 @@ export class CfpbTagGroup extends LitElement {
277
284
  /**
278
285
  * Remove a filter tag from the light DOM.
279
286
  * This is private because it's called by the mutation observer.
280
- * @param {*} tag - The tag to remove.
287
+ * @param {HTMLElement} tag - The tag to remove.
281
288
  * @returns {boolean} false if the wrapped tag was not found.
282
289
  */
283
290
  #removeTagNode(tag) {
@@ -315,11 +322,13 @@ export class CfpbTagGroup extends LitElement {
315
322
  );
316
323
 
317
324
  this.#refreshTagList();
325
+ this.focus();
326
+ return true;
318
327
  }
319
328
 
320
329
  /**
321
330
  * Remove a filter tag from the light and dark DOM.
322
- * @param {*} tag - The tag to remove.
331
+ * @param {HTMLElement} tag - The tag to remove.
323
332
  */
324
333
  removeTag(tag) {
325
334
  // Support passing in either light DOM <tag> or shadow DOM <li> if needed
@@ -332,18 +341,18 @@ export class CfpbTagGroup extends LitElement {
332
341
 
333
342
  /**
334
343
  * Get light and dark DOM.
335
- * @param {Node} node - The tag to remove.
336
- * @returns {Node|null} The tag node.
344
+ * @param {HTMLElement} tag - The tag to remove.
345
+ * @returns {HTMLElement|null} The tag node.
337
346
  */
338
- #getLightDomTag(node) {
347
+ #getLightDomTag(tag) {
339
348
  // If node is a wrapped shadow DOM <li>, get the orignal tag inside it.
340
- if (node.tagName === 'LI' && node.shadowRoot) {
349
+ if (tag.tagName === 'LI' && tag.shadowRoot) {
341
350
  // unlikely scenario if you don't expose shadow nodes externally.
342
- return node.querySelector('cfpb-tag-filter');
351
+ return tag.querySelector('cfpb-tag-filter');
343
352
  }
344
353
 
345
354
  // If node is already a light DOM tag or child <cfpb-tag-group>, return it.
346
- if (this.contains(node)) return node;
355
+ if (this.contains(tag)) return tag;
347
356
 
348
357
  return null;
349
358
  }
@@ -1,4 +1,4 @@
1
- import { html, LitElement, css, unsafeCSS } from 'lit';
1
+ import { html, LitElement } from 'lit';
2
2
  import styles from './cfpb-tag-topic.component.scss';
3
3
 
4
4
  /**
@@ -7,9 +7,7 @@ import styles from './cfpb-tag-topic.component.scss';
7
7
  * @slot - The content for the topic tag.
8
8
  */
9
9
  export class CfpbTagTopic extends LitElement {
10
- static styles = css`
11
- ${unsafeCSS(styles)}
12
- `;
10
+ static styles = styles;
13
11
 
14
12
  /**
15
13
  * @property {string} href - href attribute, if this is a topic link.
@@ -34,6 +32,13 @@ export class CfpbTagTopic extends LitElement {
34
32
  this.siblingOfJumpLink = false;
35
33
  }
36
34
 
35
+ async focus() {
36
+ // Wait for UI to settle.
37
+ await this.updateComplete;
38
+
39
+ this.shadowRoot.querySelector('.a-tag-topic').focus();
40
+ }
41
+
37
42
  get #tagClass() {
38
43
  let tagClass = 'a-tag-topic';
39
44