playbook_ui 16.2.0.pre.rc.3 → 16.2.0.pre.rc.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_sort.md +1 -1
  3. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
  4. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
  5. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
  6. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
  7. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
  8. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
  9. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
  10. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  11. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  12. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +46 -11
  13. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display_rails.html.erb +1 -1
  14. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  15. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  16. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  17. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +11 -5
  18. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +9 -0
  19. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +15 -10
  20. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  21. data/app/pb_kits/playbook/pb_dropdown/index.js +132 -79
  22. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +16 -0
  23. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  24. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +5 -3
  25. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  26. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
  27. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  28. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  29. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  30. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  31. data/app/pb_kits/playbook/pb_nav/_item.tsx +5 -3
  32. data/app/pb_kits/playbook/pb_nav/_nav.scss +82 -3
  33. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav_disabled_item.html.erb +24 -0
  34. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav_disabled_item.jsx +87 -0
  35. data/app/pb_kits/playbook/pb_nav/docs/example.yml +2 -6
  36. data/app/pb_kits/playbook/pb_nav/docs/index.js +2 -1
  37. data/app/pb_kits/playbook/pb_nav/item.html.erb +1 -1
  38. data/app/pb_kits/playbook/pb_nav/item.rb +1 -1
  39. data/app/pb_kits/playbook/pb_nav/navTypes.ts +1 -0
  40. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  41. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +29 -1
  42. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +411 -323
  43. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  44. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  45. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  46. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  47. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  48. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  49. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  50. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
  51. data/app/pb_kits/playbook/tokens/_colors.scss +3 -0
  52. data/app/pb_kits/playbook/tokens/_colors_accessible.scss +250 -0
  53. data/app/pb_kits/playbook/tokens/exports/_colors.module.scss +10 -0
  54. data/dist/chunks/{_pb_line_graph-CG2X7d4a.js → _pb_line_graph-CC2Ywwix.js} +1 -1
  55. data/dist/chunks/_typeahead-CmMqokN8.js +1 -0
  56. data/dist/chunks/{globalProps-B_OY_vR9.js → globalProps-DYr2qrIf.js} +1 -1
  57. data/dist/chunks/lib-DgqmX9CF.js +29 -0
  58. data/dist/chunks/vendor.js +2 -2
  59. data/dist/playbook-rails-react-bindings.js +1 -1
  60. data/dist/playbook-rails.js +1 -1
  61. data/dist/playbook.css +2 -2
  62. data/dist/reset.css +1 -1
  63. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  64. data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
  65. data/lib/playbook/forms/builder.rb +2 -2
  66. data/lib/playbook/version.rb +1 -1
  67. metadata +16 -6
  68. data/dist/chunks/_typeahead-DjDiMPdY.js +0 -1
  69. data/dist/chunks/lib-9vEH4omL.js +0 -29
@@ -4,16 +4,18 @@ import { PbDropdownKeyboard } from "./keyboard_accessibility";
4
4
  const DROPDOWN_SELECTOR = "[data-pb-dropdown]";
5
5
  const TRIGGER_SELECTOR = "[data-dropdown-trigger]";
6
6
  const CONTAINER_SELECTOR = "[data-dropdown-container]";
7
- const DOWN_ARROW_SELECTOR = "#dropdown_open_icon";
8
- const UP_ARROW_SELECTOR = "#dropdown_close_icon";
7
+ const DOWN_ARROW_SELECTOR = "[data-dropdown-open-icon]";
8
+ const UP_ARROW_SELECTOR = "[data-dropdown-close-icon]";
9
9
  const OPTION_SELECTOR = "[data-dropdown-option-label]";
10
10
  const CUSTOM_DISPLAY_SELECTOR = "[data-dropdown-custom-trigger]";
11
- const DROPDOWN_TRIGGER_DISPLAY = "#dropdown_trigger_display";
11
+ const DROPDOWN_TRIGGER_DISPLAY = "[data-dropdown-trigger-display]";
12
12
  const DROPDOWN_PLACEHOLDER = "[data-dropdown-placeholder]";
13
- const DROPDOWN_INPUT = "#dropdown-selected-option";
13
+ const DROPDOWN_INPUT = "[data-dropdown-selected-option]";
14
14
  const SEARCH_INPUT_SELECTOR = "[data-dropdown-autocomplete]";
15
15
  const SEARCH_BAR_SELECTOR = "[data-dropdown-search]";
16
- const CLEAR_ICON_SELECTOR = "#dropdown_clear_icon";
16
+ const CLEAR_ICON_SELECTOR = "[data-dropdown-clear-icon]";
17
+ const LABEL_SELECTOR = '[data-dropdown="pb-dropdown-label"]';
18
+
17
19
 
18
20
  export default class PbDropdown extends PbEnhancedElement {
19
21
  static get selector() {
@@ -30,14 +32,15 @@ export default class PbDropdown extends PbEnhancedElement {
30
32
  connect() {
31
33
  // Store instance on element for DatePicker sync
32
34
  this.element._pbDropdownInstance = this;
33
-
35
+
34
36
  this.keyboardHandler = new PbDropdownKeyboard(this);
35
37
  this.isMultiSelect = this.element.dataset.pbDropdownMultiSelect === "true";
36
38
  this.formPillProps = this.element.dataset.formPillProps
37
39
  ? JSON.parse(this.element.dataset.formPillProps)
38
40
  : {};
39
41
  const baseInput = this.element.querySelector(DROPDOWN_INPUT);
40
- this.wasOriginallyRequired = baseInput && baseInput.hasAttribute("required");
42
+ this.wasOriginallyRequired =
43
+ baseInput && baseInput.hasAttribute("required");
41
44
  this.setDefaultValue();
42
45
  this.bindEventListeners();
43
46
  this.bindSearchInput();
@@ -75,15 +78,24 @@ export default class PbDropdown extends PbEnhancedElement {
75
78
  bindEventListeners() {
76
79
  const customTrigger =
77
80
  this.element.querySelector(CUSTOM_DISPLAY_SELECTOR) || this.element;
78
- customTrigger.addEventListener("click", () =>
79
- this.toggleElement(this.target)
80
- );
81
+ customTrigger.addEventListener("click", (e) => {
82
+ const label = e.target.closest(LABEL_SELECTOR);
83
+ if (label && label.htmlFor) {
84
+ const trigger = this.element.querySelector(
85
+ `#${CSS.escape(label.htmlFor)}`,
86
+ );
87
+ if (trigger) {
88
+ trigger.focus();
89
+ }
90
+ }
91
+ this.toggleElement(this.target);
92
+ });
81
93
 
82
94
  this.target.addEventListener("click", this.handleOptionClick.bind(this));
83
95
  document.addEventListener(
84
96
  "click",
85
97
  this.handleDocumentClick.bind(this),
86
- true
98
+ true,
87
99
  );
88
100
  }
89
101
 
@@ -92,7 +104,7 @@ export default class PbDropdown extends PbEnhancedElement {
92
104
  if (!this.searchBar) return;
93
105
 
94
106
  this.searchBar.addEventListener("input", (e) =>
95
- this.handleSearch(e.target.value)
107
+ this.handleSearch(e.target.value),
96
108
  );
97
109
  }
98
110
 
@@ -107,7 +119,7 @@ export default class PbDropdown extends PbEnhancedElement {
107
119
 
108
120
  // Live filter
109
121
  this.searchInput.addEventListener("input", (e) =>
110
- this.handleSearch(e.target.value)
122
+ this.handleSearch(e.target.value),
111
123
  );
112
124
  }
113
125
 
@@ -161,28 +173,31 @@ export default class PbDropdown extends PbEnhancedElement {
161
173
 
162
174
  handleSearch(term = "") {
163
175
  const lcTerm = term.toLowerCase();
164
- let hasMatch = false
176
+ let hasMatch = false;
165
177
  this.element.querySelectorAll(OPTION_SELECTOR).forEach((opt) => {
166
178
  //make it so that if the option is selected, it will not show up in the search results
167
- if (this.isMultiSelect && this.selectedOptions.has(opt.dataset.dropdownOptionLabel)) {
168
- opt.style.display = "none";
169
- return;
170
- }
179
+ if (
180
+ this.isMultiSelect &&
181
+ this.selectedOptions.has(opt.dataset.dropdownOptionLabel)
182
+ ) {
183
+ opt.style.display = "none";
184
+ return;
185
+ }
171
186
  const label = JSON.parse(opt.dataset.dropdownOptionLabel)
172
187
  .label.toString()
173
188
  .toLowerCase();
174
189
 
175
- // hide or show option
190
+ // hide or show option
176
191
  const match = label.includes(lcTerm);
177
192
  opt.style.display = match ? "" : "none";
178
- if (match) hasMatch = true
193
+ if (match) hasMatch = true;
179
194
  });
180
195
 
181
196
  this.adjustDropdownHeight();
182
197
 
183
- this.removeNoOptionsMessage()
198
+ this.removeNoOptionsMessage();
184
199
  if (!hasMatch) {
185
- this.showNoOptionsMessage()
200
+ this.showNoOptionsMessage();
186
201
  }
187
202
  }
188
203
 
@@ -190,7 +205,8 @@ export default class PbDropdown extends PbEnhancedElement {
190
205
  if (this.element.querySelector(".dropdown_no_options")) return;
191
206
 
192
207
  const noOptionElement = document.createElement("div");
193
- noOptionElement.className = "pb_body_kit_light dropdown_no_options pb_item_kit p_xs display_flex justify_content_center";
208
+ noOptionElement.className =
209
+ "pb_body_kit_light dropdown_no_options pb_item_kit p_xs display_flex justify_content_center";
194
210
  noOptionElement.textContent = "no option";
195
211
 
196
212
  this.target.appendChild(noOptionElement);
@@ -241,6 +257,8 @@ export default class PbDropdown extends PbEnhancedElement {
241
257
  }
242
258
 
243
259
  isClickOutside(event) {
260
+ const label = event.target.closest(LABEL_SELECTOR);
261
+ if (label && this.element.contains(label)) return false;
244
262
  const customTrigger = this.element.querySelector(CUSTOM_DISPLAY_SELECTOR);
245
263
  if (customTrigger) {
246
264
  return !customTrigger.contains(event.target);
@@ -271,8 +289,8 @@ export default class PbDropdown extends PbEnhancedElement {
271
289
  ? JSON.parse(
272
290
  this.element.querySelector(
273
291
  OPTION_SELECTOR +
274
- `[data-dropdown-option-label*='"id":"${hiddenInput.value}"']`
275
- ).dataset.dropdownOptionLabel
292
+ `[data-dropdown-option-label*='"id":"${hiddenInput.value}"']`,
293
+ ).dataset.dropdownOptionLabel,
276
294
  )
277
295
  : null;
278
296
  }
@@ -281,14 +299,14 @@ export default class PbDropdown extends PbEnhancedElement {
281
299
  new CustomEvent("pb:dropdown:selected", {
282
300
  detail,
283
301
  bubbles: true,
284
- })
302
+ }),
285
303
  );
286
304
  }
287
305
 
288
306
  onOptionSelected(value, selectedOption) {
289
307
  const triggerElement = this.element.querySelector(DROPDOWN_TRIGGER_DISPLAY);
290
308
  const customDisplayElement = this.element.querySelector(
291
- "#dropdown_trigger_custom_display"
309
+ '[data-dropdown-trigger-custom-display]',
292
310
  );
293
311
 
294
312
  if (triggerElement) {
@@ -296,36 +314,46 @@ export default class PbDropdown extends PbEnhancedElement {
296
314
  const selectedLabel = JSON.parse(value).label;
297
315
  triggerElement.textContent = selectedLabel;
298
316
  this.emitSelectionChange();
299
-
317
+
300
318
  // Handle quickpick variant: populate start/end date hidden inputs
301
319
  const optionData = JSON.parse(value);
302
320
  const startDateId = this.element.dataset.startDateId;
303
321
  const endDateId = this.element.dataset.endDateId;
304
322
  const controlsStartId = this.element.dataset.controlsStartId;
305
323
  const controlsEndId = this.element.dataset.controlsEndId;
306
-
324
+
307
325
  if (optionData.formatted_start_date && optionData.formatted_end_date) {
308
326
  // Populate date inputs when option has date fields
309
327
  if (startDateId) {
310
328
  const startDateInput = document.getElementById(startDateId);
311
- if (startDateInput) startDateInput.value = optionData.formatted_start_date;
329
+ if (startDateInput)
330
+ startDateInput.value = optionData.formatted_start_date;
312
331
  }
313
-
332
+
314
333
  if (endDateId) {
315
334
  const endDateInput = document.getElementById(endDateId);
316
- if (endDateInput) endDateInput.value = optionData.formatted_end_date;
335
+ if (endDateInput)
336
+ endDateInput.value = optionData.formatted_end_date;
317
337
  }
318
-
338
+
319
339
  // Sync with DatePickers if controlsStartId/controlsEndId are present
320
340
  if (controlsStartId) {
321
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
341
+ const startPicker = document.querySelector(
342
+ `#${controlsStartId}`,
343
+ )?._flatpickr;
322
344
  if (startPicker) {
323
- startPicker.setDate(optionData.formatted_start_date, true, "m/d/Y");
345
+ startPicker.setDate(
346
+ optionData.formatted_start_date,
347
+ true,
348
+ "m/d/Y",
349
+ );
324
350
  }
325
351
  }
326
-
352
+
327
353
  if (controlsEndId) {
328
- const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
354
+ const endPicker = document.querySelector(
355
+ `#${controlsEndId}`,
356
+ )?._flatpickr;
329
357
  if (endPicker) {
330
358
  endPicker.setDate(optionData.formatted_end_date, true, "m/d/Y");
331
359
  }
@@ -336,22 +364,26 @@ export default class PbDropdown extends PbEnhancedElement {
336
364
  const startDateInput = document.getElementById(startDateId);
337
365
  if (startDateInput) startDateInput.value = "";
338
366
  }
339
-
367
+
340
368
  if (endDateId) {
341
369
  const endDateInput = document.getElementById(endDateId);
342
370
  if (endDateInput) endDateInput.value = "";
343
371
  }
344
-
372
+
345
373
  // Clear DatePickers as well
346
374
  if (controlsStartId) {
347
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
375
+ const startPicker = document.querySelector(
376
+ `#${controlsStartId}`,
377
+ )?._flatpickr;
348
378
  if (startPicker) {
349
379
  startPicker.clear();
350
380
  }
351
381
  }
352
-
382
+
353
383
  if (controlsEndId) {
354
- const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
384
+ const endPicker = document.querySelector(
385
+ `#${controlsEndId}`,
386
+ )?._flatpickr;
355
387
  if (endPicker) {
356
388
  endPicker.clear();
357
389
  }
@@ -391,7 +423,9 @@ export default class PbDropdown extends PbEnhancedElement {
391
423
  this.adjustDropdownHeight();
392
424
  }
393
425
  });
394
- this.element.querySelector(DROPDOWN_INPUT).value = Array.from(this.selectedOptions)
426
+ this.element.querySelector(DROPDOWN_INPUT).value = Array.from(
427
+ this.selectedOptions,
428
+ )
395
429
  .map((opt) => JSON.parse(opt).id)
396
430
  .join(",");
397
431
  } else {
@@ -437,7 +471,7 @@ export default class PbDropdown extends PbEnhancedElement {
437
471
  this.keyboardHandler.focusedOptionIndex = -1;
438
472
  const options = this.element.querySelectorAll(OPTION_SELECTOR);
439
473
  options.forEach((option) =>
440
- option.classList.remove("pb_dropdown_option_focused")
474
+ option.classList.remove("pb_dropdown_option_focused"),
441
475
  );
442
476
  }
443
477
  }
@@ -472,7 +506,7 @@ export default class PbDropdown extends PbEnhancedElement {
472
506
  hiddenInput.closest(".dropdown_wrapper").classList.add("error");
473
507
  }
474
508
  },
475
- true
509
+ true,
476
510
  );
477
511
  }
478
512
 
@@ -482,7 +516,7 @@ export default class PbDropdown extends PbEnhancedElement {
482
516
  const dropdownWrapperElement = input.closest(".dropdown_wrapper");
483
517
  dropdownWrapperElement.classList.remove("error");
484
518
  const errorLabelElement = dropdownWrapperElement.querySelector(
485
- ".pb_body_kit_negative"
519
+ ".pb_body_kit_negative",
486
520
  );
487
521
  if (errorLabelElement) {
488
522
  errorLabelElement.remove();
@@ -490,13 +524,13 @@ export default class PbDropdown extends PbEnhancedElement {
490
524
  return;
491
525
  }
492
526
  }
493
-
527
+
494
528
  if (input.checkValidity()) {
495
529
  const dropdownWrapperElement = input.closest(".dropdown_wrapper");
496
530
  dropdownWrapperElement.classList.remove("error");
497
531
 
498
532
  const errorLabelElement = dropdownWrapperElement.querySelector(
499
- ".pb_body_kit_negative"
533
+ ".pb_body_kit_negative",
500
534
  );
501
535
  if (errorLabelElement) {
502
536
  errorLabelElement.remove();
@@ -507,7 +541,7 @@ export default class PbDropdown extends PbEnhancedElement {
507
541
  setDefaultValue() {
508
542
  const hiddenInput = this.element.querySelector(DROPDOWN_INPUT);
509
543
  const optionEls = Array.from(
510
- this.element.querySelectorAll(OPTION_SELECTOR)
544
+ this.element.querySelectorAll(OPTION_SELECTOR),
511
545
  );
512
546
  const defaultValue = hiddenInput.dataset.defaultValue || "";
513
547
  if (!defaultValue) return;
@@ -553,44 +587,53 @@ export default class PbDropdown extends PbEnhancedElement {
553
587
  selectedOption.classList.add("pb_dropdown_option_selected");
554
588
  const optionData = JSON.parse(selectedOption.dataset.dropdownOptionLabel);
555
589
  this.setTriggerElementText(optionData.label);
556
-
590
+
557
591
  // Handle quickpick variant: populate start/end date hidden inputs and sync DatePickers
558
592
  if (optionData.formatted_start_date && optionData.formatted_end_date) {
559
593
  const startDateId = this.element.dataset.startDateId;
560
594
  const endDateId = this.element.dataset.endDateId;
561
595
  const controlsStartId = this.element.dataset.controlsStartId;
562
596
  const controlsEndId = this.element.dataset.controlsEndId;
563
-
597
+
564
598
  if (startDateId) {
565
599
  const startDateInput = document.getElementById(startDateId);
566
- if (startDateInput) startDateInput.value = optionData.formatted_start_date;
600
+ if (startDateInput)
601
+ startDateInput.value = optionData.formatted_start_date;
567
602
  }
568
-
603
+
569
604
  if (endDateId) {
570
605
  const endDateInput = document.getElementById(endDateId);
571
606
  if (endDateInput) endDateInput.value = optionData.formatted_end_date;
572
607
  }
573
-
608
+
574
609
  // Sync with DatePickers - retry with delays to ensure DatePickers are initialized
575
610
  const syncDatePickers = () => {
576
611
  if (controlsStartId) {
577
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
612
+ const startPicker = document.querySelector(
613
+ `#${controlsStartId}`,
614
+ )?._flatpickr;
578
615
  if (startPicker) {
579
- startPicker.setDate(optionData.formatted_start_date, true, "m/d/Y");
616
+ startPicker.setDate(
617
+ optionData.formatted_start_date,
618
+ true,
619
+ "m/d/Y",
620
+ );
580
621
  }
581
622
  }
582
-
623
+
583
624
  if (controlsEndId) {
584
- const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
625
+ const endPicker = document.querySelector(
626
+ `#${controlsEndId}`,
627
+ )?._flatpickr;
585
628
  if (endPicker) {
586
629
  endPicker.setDate(optionData.formatted_end_date, true, "m/d/Y");
587
630
  }
588
631
  }
589
632
  };
590
-
633
+
591
634
  // Try immediately
592
635
  syncDatePickers();
593
-
636
+
594
637
  // Retry after short delay in case DatePickers aren't ready yet
595
638
  setTimeout(syncDatePickers, 100);
596
639
  setTimeout(syncDatePickers, 300);
@@ -651,9 +694,9 @@ export default class PbDropdown extends PbEnhancedElement {
651
694
  updatePills() {
652
695
  if (!this.isMultiSelect) return;
653
696
 
654
- const wrapper = this.element.querySelector("#dropdown_pills_wrapper");
697
+ const wrapper = this.element.querySelector('[data-dropdown-pills-wrapper]');
655
698
  const placeholder = this.element.querySelector(
656
- "#dropdown_trigger_display_multi_select"
699
+ '[data-dropdown-trigger-display-multi-select]',
657
700
  );
658
701
  if (!wrapper) return;
659
702
 
@@ -671,7 +714,12 @@ export default class PbDropdown extends PbEnhancedElement {
671
714
  // Create a form pill for each selected option
672
715
  const pill = document.createElement("div");
673
716
  const color = this.formPillProps.color || "primary";
674
- pill.classList.add("pb_form_pill_kit", `pb_form_pill_${color}`, "pb_form_pill_none", "mr_xs");
717
+ pill.classList.add(
718
+ "pb_form_pill_kit",
719
+ `pb_form_pill_${color}`,
720
+ "pb_form_pill_none",
721
+ "mr_xs",
722
+ );
675
723
  if (this.formPillProps.size === "small") {
676
724
  pill.classList.add("pb_form_pill_small");
677
725
  }
@@ -696,8 +744,8 @@ export default class PbDropdown extends PbEnhancedElement {
696
744
 
697
745
  const optEl = this.element.querySelector(
698
746
  `${OPTION_SELECTOR}[data-dropdown-option-label*='"id":${JSON.stringify(
699
- id
700
- )}']`
747
+ id,
748
+ )}']`,
701
749
  );
702
750
  if (optEl) {
703
751
  optEl.style.display = "";
@@ -726,18 +774,18 @@ export default class PbDropdown extends PbEnhancedElement {
726
774
  }
727
775
  }
728
776
  const customDisplay = this.element.querySelector(
729
- "#dropdown_trigger_custom_display"
777
+ '[data-dropdown-trigger-custom-display]',
730
778
  );
731
779
  if (customDisplay) {
732
780
  customDisplay.style.display = "none";
733
781
  }
734
-
782
+
735
783
  // Clear quickpick hidden inputs
736
784
  const startDateId = this.element.dataset.startDateId;
737
785
  const endDateId = this.element.dataset.endDateId;
738
786
  const controlsStartId = this.element.dataset.controlsStartId;
739
787
  const controlsEndId = this.element.dataset.controlsEndId;
740
-
788
+
741
789
  if (startDateId) {
742
790
  const startDateInput = document.getElementById(startDateId);
743
791
  if (startDateInput) startDateInput.value = "";
@@ -746,22 +794,24 @@ export default class PbDropdown extends PbEnhancedElement {
746
794
  const endDateInput = document.getElementById(endDateId);
747
795
  if (endDateInput) endDateInput.value = "";
748
796
  }
749
-
797
+
750
798
  // Clear linked DatePickers if controlsStartId/controlsEndId are present
751
799
  if (controlsStartId) {
752
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
800
+ const startPicker = document.querySelector(
801
+ `#${controlsStartId}`,
802
+ )?._flatpickr;
753
803
  if (startPicker) {
754
804
  startPicker.clear();
755
805
  }
756
806
  }
757
-
807
+
758
808
  if (controlsEndId) {
759
809
  const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
760
810
  if (endPicker) {
761
811
  endPicker.clear();
762
812
  }
763
813
  }
764
-
814
+
765
815
  this.resetDropdownValue();
766
816
  this.updatePills();
767
817
  this.updateClearButton();
@@ -772,21 +822,24 @@ export default class PbDropdown extends PbEnhancedElement {
772
822
  // Method for DatePicker sync - only clears the dropdown, not the DatePickers
773
823
  clearSelected() {
774
824
  // Only clear if this is a single-select quickpick variant
775
- if (this.element.dataset.pbDropdownVariant !== "quickpick" || this.isMultiSelect) {
825
+ if (
826
+ this.element.dataset.pbDropdownVariant !== "quickpick" ||
827
+ this.isMultiSelect
828
+ ) {
776
829
  return;
777
830
  }
778
-
831
+
779
832
  const customDisplay = this.element.querySelector(
780
- "#dropdown_trigger_custom_display"
833
+ '[data-dropdown-trigger-custom-display]',
781
834
  );
782
835
  if (customDisplay) {
783
836
  customDisplay.style.display = "none";
784
837
  }
785
-
838
+
786
839
  // Clear quickpick hidden inputs only (not the DatePickers)
787
840
  const startDateId = this.element.dataset.startDateId;
788
841
  const endDateId = this.element.dataset.endDateId;
789
-
842
+
790
843
  if (startDateId) {
791
844
  const startDateInput = document.getElementById(startDateId);
792
845
  if (startDateInput) startDateInput.value = "";
@@ -795,7 +848,7 @@ export default class PbDropdown extends PbEnhancedElement {
795
848
  const endDateInput = document.getElementById(endDateId);
796
849
  if (endDateInput) endDateInput.value = "";
797
850
  }
798
-
851
+
799
852
  this.resetDropdownValue();
800
853
  this.updateClearButton();
801
854
  this.emitSelectionChange();
@@ -822,7 +875,7 @@ export default class PbDropdown extends PbEnhancedElement {
822
875
  inp.dataset.generated = "true";
823
876
  baseInput.insertAdjacentElement("afterend", inp);
824
877
  });
825
-
878
+
826
879
  // For multi-select, remove required from base input when there are selections
827
880
  // The generated inputs handle the form submission with actual values
828
881
  // Restore required attribute when there are no selections (if it was originally required)
@@ -45,6 +45,8 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
45
45
  const {
46
46
  autocomplete,
47
47
  clearable,
48
+ error,
49
+ errorId,
48
50
  filterItem,
49
51
  handleBackspace,
50
52
  handleChange,
@@ -53,8 +55,10 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
53
55
  inputWrapperRef,
54
56
  isDropDownClosed,
55
57
  isInputFocused,
58
+ label: contextLabel,
56
59
  multiSelect,
57
60
  selected,
61
+ selectId,
58
62
  setIsInputFocused,
59
63
  toggleDropdown,
60
64
  } = useContext(DropdownContext);
@@ -104,6 +108,10 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
104
108
  ? placeholder
105
109
  : "Select...";
106
110
 
111
+ const triggerAriaLabel = contextLabel
112
+ ? (children ? contextLabel : `${contextLabel}, ${defaultDisplayPlaceholder}`)
113
+ : undefined;
114
+
107
115
  return (
108
116
  <div {...ariaProps}
109
117
  {...dataProps}
@@ -114,6 +122,10 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
114
122
  {
115
123
  children ? (
116
124
  <div
125
+ aria-describedby={errorId}
126
+ aria-invalid={!!error}
127
+ aria-label={triggerAriaLabel}
128
+ id={selectId}
117
129
  onClick={() => toggleDropdown()}
118
130
  onKeyDown= {handleKeyDown}
119
131
  ref={inputWrapperRef}
@@ -130,6 +142,10 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
130
142
  className={triggerWrapperClasses}
131
143
  cursor={`${autocomplete ? "text" : "pointer"}`}
132
144
  htmlOptions={{
145
+ "aria-describedby": errorId,
146
+ "aria-invalid": !!error,
147
+ "aria-label": triggerAriaLabel,
148
+ id: selectId,
133
149
  onClick: () => handleWrapperClick(),
134
150
  onKeyDown: handleKeyDown,
135
151
  tabIndex: "0",
@@ -25,6 +25,12 @@ export const handleClickOutside =
25
25
  ) {
26
26
  shouldClose = false;
27
27
  }
28
+ // Target dropdown container to open dropdown
29
+ if (
30
+ targetElement.getAttribute("data-dropdown") === "pb-dropdown-label"
31
+ ) {
32
+ shouldClose = false;
33
+ }
28
34
  targetElement = targetElement.parentElement as HTMLElement;
29
35
  }
30
36
  if (
@@ -1,14 +1,16 @@
1
1
  <%= pb_form_with(scope: :example, url: "", method: :get, validate: true) do |form| %>
2
+ <%= form.typeahead :example_typeahead_field, props: { label: true, required: true, required_indicator: true } %>
2
3
  <%= form.text_field :example_text_field, props: { label: true, required: true, required_indicator: true } %>
3
4
  <%= form.text_field :example_text_field_2, props: { label: "Text Field Custom Label", required: true, required_indicator: true } %>
4
- <%= form.text_area :example_text_area, props: { label: true, required: true, required_indicator: true } %>
5
- <%= form.text_area :example_text_area_2, props: { label: "Textarea Custom Label", required: true, required_indicator: true } %>
5
+ <%= form.phone_number_field :example_phone_number_field, props: { label: true, required: true, required_indicator: true } %>
6
6
  <%= form.email_field :example_email_field, props: { label: true, required: true, required_indicator: true } %>
7
7
  <%= form.number_field :example_number_field, props: { label: true, required: true, required_indicator: true } %>
8
8
  <%= form.search_field :example_search_field, props: { label: true, required: true, required_indicator: true } %>
9
9
  <%= form.password_field :example_password_field, props: { label: true, required: true, required_indicator: true } %>
10
10
  <%= form.url_field :example_url_field, props: { label: true, required: true, required_indicator: true } %>
11
- <%= form.phone_number_field :example_phone_number_field, props: { label: true, required: true, required_indicator: true } %>
11
+ <%= form.text_area :example_text_area, props: { label: true, required: true, required_indicator: true } %>
12
+ <%= form.text_area :example_text_area_2, props: { label: "Textarea Custom Label", required: true, required_indicator: true } %>
13
+ <%# <%= form.check_box :example_checkbox, props: { label: true, text: "Checkbox Label", required: true, required_indicator: true } %>
12
14
  <%= form.time_picker :example_time_picker_required_indicator, props: { label: true, required: true, required_indicator: true } %>
13
15
 
14
16
  <%= form.actions do |action| %>
@@ -39,6 +39,13 @@
39
39
  display: flex;
40
40
  align-items: center;
41
41
  justify-content: space-between;
42
+ @include transition_default;
43
+
44
+ &:focus-within {
45
+ border-color: $primary;
46
+ background-color: $focus_input_light;
47
+ }
48
+
42
49
  .input_inner_container {
43
50
  width: 100%;
44
51
  }