@cfpb/cfpb-design-system 4.3.2 → 4.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +53 -3
  2. package/dist/components/cfpb-buttons/index.css +1 -1
  3. package/dist/components/cfpb-buttons/index.css.map +2 -2
  4. package/dist/components/cfpb-buttons/index.js +1 -1
  5. package/dist/components/cfpb-buttons/index.js.map +1 -1
  6. package/dist/components/cfpb-expandables/index.css +1 -1
  7. package/dist/components/cfpb-expandables/index.css.map +2 -2
  8. package/dist/components/cfpb-expandables/index.js +1 -1
  9. package/dist/components/cfpb-expandables/index.js.map +2 -2
  10. package/dist/components/cfpb-forms/index.css +1 -1
  11. package/dist/components/cfpb-forms/index.css.map +2 -2
  12. package/dist/components/cfpb-forms/index.js +1 -1
  13. package/dist/components/cfpb-forms/index.js.map +1 -1
  14. package/dist/components/cfpb-icons/index.css +1 -1
  15. package/dist/components/cfpb-icons/index.css.map +2 -2
  16. package/dist/components/cfpb-icons/index.js +1 -1
  17. package/dist/components/cfpb-icons/index.js.map +1 -1
  18. package/dist/components/cfpb-layout/index.css +1 -1
  19. package/dist/components/cfpb-layout/index.css.map +2 -2
  20. package/dist/components/cfpb-layout/index.js +1 -1
  21. package/dist/components/cfpb-layout/index.js.map +1 -1
  22. package/dist/components/cfpb-notifications/index.css +1 -1
  23. package/dist/components/cfpb-notifications/index.css.map +2 -2
  24. package/dist/components/cfpb-notifications/index.js +1 -1
  25. package/dist/components/cfpb-notifications/index.js.map +1 -1
  26. package/dist/components/cfpb-pagination/index.css +1 -1
  27. package/dist/components/cfpb-pagination/index.css.map +2 -2
  28. package/dist/components/cfpb-pagination/index.js +1 -1
  29. package/dist/components/cfpb-pagination/index.js.map +1 -1
  30. package/dist/components/cfpb-tables/index.css +1 -1
  31. package/dist/components/cfpb-tables/index.css.map +2 -2
  32. package/dist/components/cfpb-tables/index.js.map +1 -1
  33. package/dist/components/cfpb-tooltips/index.css +1 -1
  34. package/dist/components/cfpb-tooltips/index.css.map +2 -2
  35. package/dist/components/cfpb-tooltips/index.js +1 -1
  36. package/dist/components/cfpb-tooltips/index.js.map +1 -1
  37. package/dist/components/cfpb-typography/index.css +1 -1
  38. package/dist/components/cfpb-typography/index.css.map +2 -2
  39. package/dist/components/cfpb-typography/index.js +1 -1
  40. package/dist/components/cfpb-typography/index.js.map +1 -1
  41. package/dist/elements/base/index.css +1 -1
  42. package/dist/elements/base/index.css.map +2 -2
  43. package/dist/elements/base/index.js +1 -1
  44. package/dist/elements/base/index.js.map +1 -1
  45. package/dist/elements/cfpb-button/index.js +4 -4
  46. package/dist/elements/cfpb-button/index.js.map +2 -2
  47. package/dist/elements/cfpb-checkbox-icon/index.js +3 -3
  48. package/dist/elements/cfpb-checkbox-icon/index.js.map +2 -2
  49. package/dist/elements/cfpb-expandable/index.css +1 -1
  50. package/dist/elements/cfpb-expandable/index.css.map +2 -2
  51. package/dist/elements/cfpb-expandable/index.js +4 -4
  52. package/dist/elements/cfpb-expandable/index.js.map +2 -2
  53. package/dist/elements/cfpb-file-upload/index.js +4 -4
  54. package/dist/elements/cfpb-file-upload/index.js.map +2 -2
  55. package/dist/elements/cfpb-form-alert/index.js +3 -3
  56. package/dist/elements/cfpb-form-alert/index.js.map +2 -2
  57. package/dist/elements/cfpb-form-choice/index.js +3 -3
  58. package/dist/elements/cfpb-form-choice/index.js.map +2 -2
  59. package/dist/elements/cfpb-form-search/index.js +2 -2
  60. package/dist/elements/cfpb-form-search/index.js.map +2 -2
  61. package/dist/elements/cfpb-form-search-input/index.js +3 -3
  62. package/dist/elements/cfpb-form-search-input/index.js.map +2 -2
  63. package/dist/elements/cfpb-icon-text/index.js +2 -2
  64. package/dist/elements/cfpb-icon-text/index.js.map +2 -2
  65. package/dist/elements/cfpb-label/index.js +2 -2
  66. package/dist/elements/cfpb-label/index.js.map +2 -2
  67. package/dist/elements/cfpb-list/index.js +3 -3
  68. package/dist/elements/cfpb-list/index.js.map +2 -2
  69. package/dist/elements/cfpb-list-item/index.js +3 -3
  70. package/dist/elements/cfpb-list-item/index.js.map +2 -2
  71. package/dist/elements/cfpb-pagination/index.js +3 -3
  72. package/dist/elements/cfpb-pagination/index.js.map +2 -2
  73. package/dist/elements/cfpb-select/index.css +1 -1
  74. package/dist/elements/cfpb-select/index.css.map +2 -2
  75. package/dist/elements/cfpb-select/index.js +4 -4
  76. package/dist/elements/cfpb-select/index.js.map +3 -3
  77. package/dist/elements/cfpb-tag-filter/index.js +3 -3
  78. package/dist/elements/cfpb-tag-filter/index.js.map +3 -3
  79. package/dist/elements/cfpb-tag-group/index.js +3 -3
  80. package/dist/elements/cfpb-tag-group/index.js.map +3 -3
  81. package/dist/elements/cfpb-tag-topic/index.js +4 -4
  82. package/dist/elements/cfpb-tag-topic/index.js.map +3 -3
  83. package/dist/elements/cfpb-utilities/index.js.map +1 -1
  84. package/dist/elements/index.css +1 -1
  85. package/dist/elements/index.css.map +2 -2
  86. package/dist/elements/index.js +7 -7
  87. package/dist/elements/index.js.map +3 -3
  88. package/dist/index.css +1 -1
  89. package/dist/index.css.map +2 -2
  90. package/dist/index.js +7 -7
  91. package/dist/index.js.map +3 -3
  92. package/dist/utilities/index.css +1 -1
  93. package/dist/utilities/index.css.map +2 -2
  94. package/dist/utilities/index.js +1 -1
  95. package/dist/utilities/index.js.map +2 -2
  96. package/package.json +1 -1
  97. package/src/components/cfpb-tables/table.scss +4 -1
  98. package/src/elements/cfpb-form-search/cfpb-form-search.component.scss +10 -0
  99. package/src/elements/cfpb-form-search/index.js +1 -3
  100. package/src/elements/cfpb-form-search-input/cfpb-form-search-input.component.scss +8 -5
  101. package/src/elements/cfpb-list/index.js +2 -2
  102. package/src/elements/cfpb-list/index.spec.js +5 -5
  103. package/src/elements/cfpb-list-item/index.js +2 -2
  104. package/src/elements/cfpb-select/cfpb-select.component.scss +34 -57
  105. package/src/elements/cfpb-select/index.js +22 -12
  106. package/src/elements/cfpb-select/multiple-select-event-proxy.js +23 -17
  107. package/src/elements/cfpb-select/single-select-event-proxy.js +8 -5
  108. package/src/elements/cfpb-tag-filter/index.js +7 -0
  109. package/src/elements/cfpb-tag-group/index.js +9 -0
  110. package/src/elements/cfpb-tag-topic/index.js +7 -0
  111. package/src/elements/cfpb-utilities/media-query-service.js +1 -1
  112. package/src/elements/cfpb-utilities/parse-child-data.js +1 -1
  113. package/src/tokens/abstracts/custom-props.json +2334 -1639
  114. package/src/tokens/abstracts/vars.json +1679 -1315
  115. package/src/tokens/cfpb-button/vars.json +602 -409
  116. package/src/utilities/transition/max-height-transition.js +1 -1
  117. package/dist/elements/cfpb-select-list/index.js +0 -39
  118. package/dist/elements/cfpb-select-list/index.js.map +0 -7
@@ -80,7 +80,10 @@
80
80
  margin-top: 0;
81
81
  margin-bottom: math.div(5px, $base-font-size-px) + em;
82
82
  content: attr(data-label);
83
- line-height: 1.83333333;
83
+
84
+ // Yeilds 1.83333333
85
+ // TODO: Research alternative to this magic number
86
+ line-height: math.div(11,6);
84
87
 
85
88
  // h5 size.
86
89
  font-size: math.div($size-v, $base-font-size-px) + em;
@@ -45,6 +45,16 @@
45
45
  border: 1px solid var(--pacific);
46
46
  border-top: 0;
47
47
  width: calc(100% - 2px);
48
+
49
+ &::after {
50
+ position: absolute;
51
+ content: '';
52
+ display: block;
53
+ width: 100%;
54
+ height: 1px;
55
+ bottom: -1px;
56
+ border-bottom: 1px solid var(--pacific);
57
+ }
48
58
  }
49
59
 
50
60
  .popup.show {
@@ -131,8 +131,6 @@ export class CfpbFormSearch extends LitElement {
131
131
  evt.preventDefault();
132
132
  if (this.disabled) return;
133
133
 
134
- console.log(this.value);
135
-
136
134
  if (this.value !== '') {
137
135
  this.#internals.setFormValue(this.value);
138
136
  this.#internals.form?.requestSubmit();
@@ -148,7 +146,7 @@ export class CfpbFormSearch extends LitElement {
148
146
  <div class="container">
149
147
  <cfpb-form-search-input
150
148
  ?name=${this.name}
151
- ?value=${this.value}
149
+ .value=${this.value}
152
150
  ?placeholder=${this.placeholder}
153
151
  title=${this.title}
154
152
  ?maxlength=${this.maxlength}
@@ -14,11 +14,14 @@
14
14
  // Private vars
15
15
  --input-border: var(--input-border-default);
16
16
 
17
+ display: flex;
18
+
17
19
  .o-search-input {
18
20
  position: relative;
19
21
  display: flex;
20
22
  width: initial;
21
23
  flex: 0 1 100%;
24
+ align-items: center;
22
25
 
23
26
  // Add a min-width for the default width of the text input + clear button,
24
27
  // so that when the clear button appears it doesn't bump the input width.
@@ -33,12 +36,7 @@
33
36
  label {
34
37
  position: absolute;
35
38
  left: 10px;
36
- align-self: center;
37
39
  cursor: pointer;
38
- line-height: math.div(
39
- 14.4px,
40
- $base-font-size-px
41
- ); // 9px vertically centers icon.
42
40
  }
43
41
 
44
42
  // This line-height settings is taken from base.scss.
@@ -175,6 +173,11 @@
175
173
  .o-search-input {
176
174
  border-color: transparent;
177
175
 
176
+ input {
177
+ padding-top: math.div(5px, $base-font-size-px) + em;
178
+ padding-bottom: math.div(5px, $base-font-size-px) + em;
179
+ }
180
+
178
181
  &:hover,
179
182
  &:focus-within {
180
183
  border-color: transparent;
@@ -139,7 +139,7 @@ export class CfpbList extends LitElement {
139
139
 
140
140
  // Remove prior listener if present.
141
141
  const prev = this.#clickListeners.get(item);
142
- if (prev) item.removeEventListener('click-item', prev);
142
+ if (prev) item.removeEventListener('item-click', prev);
143
143
 
144
144
  // Listener that toggles the item before handling.
145
145
  const listener = (evt) => {
@@ -148,7 +148,7 @@ export class CfpbList extends LitElement {
148
148
  this.#handleToggle(item, item.checked, index);
149
149
  };
150
150
 
151
- item.addEventListener('click-item', listener);
151
+ item.addEventListener('item-click', listener);
152
152
  this.#clickListeners.set(item, listener);
153
153
 
154
154
  // Track focus index.
@@ -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
  });
@@ -49,7 +49,7 @@ export class CfpbListItem extends LitElement {
49
49
  this.addEventListener('keydown', this.#onKeyDown);
50
50
  }
51
51
 
52
- this.addEventListener('click', this.#onClick);
52
+ this.addEventListener('pointerdown', this.#onClick);
53
53
  }
54
54
 
55
55
  connectedCallback() {
@@ -102,7 +102,7 @@ export class CfpbListItem extends LitElement {
102
102
  // … Otherwise, toggle the checked state.
103
103
  this.checked = !this.checked;
104
104
  this.dispatchEvent(
105
- new CustomEvent('click-item', {
105
+ new CustomEvent('item-click', {
106
106
  detail: { checked: this.checked, value: this.value },
107
107
  bubbles: true,
108
108
  composed: true,
@@ -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;
@@ -207,12 +174,26 @@
207
174
  }
208
175
  }
209
176
  }
177
+
178
+ @media print {
179
+ // Hide the interactive select cues when printing
180
+ &__cues[aria-expanded='true'] &__cue-close,
181
+ &__cues[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
+ }
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
  }
@@ -82,6 +82,10 @@ export class CfpbSelect extends LitElement {
82
82
  this.#eventProxy.onFocus();
83
83
  });
84
84
 
85
+ this.addEventListener('blur', () => {
86
+ this.#eventProxy.onBlur(this);
87
+ });
88
+
85
89
  this.addEventListener('keydown', (evt) => {
86
90
  this.#eventProxy.onKeyDown(evt, this);
87
91
  });
@@ -160,7 +164,12 @@ export class CfpbSelect extends LitElement {
160
164
  }
161
165
 
162
166
  #onClear() {
167
+ this.#flyoutMenu.suspend();
168
+ this.isExpanded = false;
163
169
  this.#list.value.showAllItems();
170
+ this.#noResults = false;
171
+ this.requestUpdate();
172
+ this.#flyoutMenu.resume();
164
173
  }
165
174
 
166
175
  #initFlyoutMenu() {
@@ -264,7 +273,7 @@ export class CfpbSelect extends LitElement {
264
273
  }
265
274
 
266
275
  #onTagClick(evt) {
267
- this.#eventProxy?.onTagClick(evt, this);
276
+ this.#eventProxy?.onTagClick(evt, this, this.#tagGroup.value);
268
277
  }
269
278
 
270
279
  render() {
@@ -288,27 +297,27 @@ export class CfpbSelect extends LitElement {
288
297
  : nothing}
289
298
 
290
299
  <div
291
- class="o-select o-select--border"
300
+ class="o-select"
292
301
  data-js-hook="behavior_flyout-menu"
293
302
  ${ref(this.#root)}
294
303
  >
295
304
  ${this.#renderInput()}
296
305
 
297
306
  <button
298
- class="o-select__header"
307
+ tabindex=${this.multiple ? '-1' : '0'}
308
+ class="o-select__cues"
299
309
  title="Expand content"
300
310
  data-js-hook="behavior_flyout-menu_trigger"
301
311
  ${ref(this.#headerDom)}
312
+ @click=${this.#onClick}
302
313
  >
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>
314
+ <span class="o-select__cue-open" role="img" aria-label="Show">
315
+ ${unsafeSVG(expandIcon)}
316
+ <span class="u-visually-hidden">Show</span>
317
+ </span>
318
+ <span class="o-select__cue-close" role="img" aria-label="Hide">
319
+ ${unsafeSVG(collapseIcon)}
320
+ <span class="u-visually-hidden">Hide</span>
312
321
  </span>
313
322
  </button>
314
323
  <div
@@ -317,6 +326,7 @@ export class CfpbSelect extends LitElement {
317
326
  ${ref(this.#contentDom)}
318
327
  >
319
328
  <cfpb-list
329
+ tabindex=${this.#noResults ? '-1' : '0'}
320
330
  @item-click=${this.#onItemClick}
321
331
  ?multiple=${this.multiple}
322
332
  .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
  }
@@ -28,6 +28,13 @@ export class CfpbTagFilter extends LitElement {
28
28
  this.value = '';
29
29
  }
30
30
 
31
+ async focus() {
32
+ // Wait for UI to settle.
33
+ await this.updateComplete;
34
+
35
+ this.shadowRoot.querySelector('button').focus();
36
+ }
37
+
31
38
  #onClick() {
32
39
  this.dispatchEvent(
33
40
  new CustomEvent('tag-click', {
@@ -78,6 +78,14 @@ export class CfpbTagGroup extends LitElement {
78
78
  }
79
79
  }
80
80
 
81
+ async focus() {
82
+ // Wait for tagList to update.
83
+ await this.updateComplete;
84
+
85
+ const firstChild = this.tagList[0];
86
+ if (firstChild) firstChild.focus();
87
+ }
88
+
81
89
  #renderTagsFromData(arr) {
82
90
  if (!Array.isArray(arr)) return;
83
91
 
@@ -315,6 +323,7 @@ export class CfpbTagGroup extends LitElement {
315
323
  );
316
324
 
317
325
  this.#refreshTagList();
326
+ this.focus();
318
327
  }
319
328
 
320
329
  /**
@@ -34,6 +34,13 @@ export class CfpbTagTopic extends LitElement {
34
34
  this.siblingOfJumpLink = false;
35
35
  }
36
36
 
37
+ async focus() {
38
+ // Wait for UI to settle.
39
+ await this.updateComplete;
40
+
41
+ this.shadowRoot.querySelector('.a-tag-topic').focus();
42
+ }
43
+
37
44
  get #tagClass() {
38
45
  let tagClass = 'a-tag-topic';
39
46
 
@@ -12,7 +12,7 @@ export class MediaQueryService extends EventTarget {
12
12
 
13
13
  /**
14
14
  *
15
- * @param {Record<string, {min: number, max?: number}} [breakpoints]
15
+ * @param {Record<string, {min: number, max?: number}>} [breakpoints]
16
16
  * A map of breakpoint name -> { min: px, optional max: px }.
17
17
  * If not provided, default breakpoints are used.
18
18
  */
@@ -4,7 +4,7 @@
4
4
  * - JS arrays
5
5
  * - JSON strings
6
6
  * - JSON-like strings with single quotes
7
- * @param {Array | string} input
7
+ * @param {Array | string} input - An array or JSON string.
8
8
  * @param {object} options - optional settings.
9
9
  * @param {boolean} options.allowSingleQuotes - default true.
10
10
  * @returns {Array|null} Parsed array/string, or null if invalid.