playbook_ui 16.2.0.pre.rc.1 → 16.2.0.pre.rc.2

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/Components/RegularTableView.tsx +12 -2
  3. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +33 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.jsx +71 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_column_styling_background_custom.md +4 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +1 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/index.js +2 -1
  8. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +1 -1
  9. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +17 -0
  10. data/app/pb_kits/playbook/pb_checkbox/checkbox.html.erb +10 -1
  11. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -0
  12. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.html.erb +6 -0
  13. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.jsx +17 -0
  14. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_required_indicator.md +3 -0
  15. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  16. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  17. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +46 -11
  18. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +6 -3
  19. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +1 -0
  20. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +3 -1
  21. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +10 -4
  22. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +9 -0
  23. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +7 -2
  24. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +4 -0
  25. data/app/pb_kits/playbook/pb_dropdown/index.js +125 -73
  26. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +16 -0
  27. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +6 -0
  28. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +5 -3
  29. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +7 -0
  30. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +638 -549
  31. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.html.erb +3 -3
  32. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.jsx +4 -7
  33. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_label.md +3 -0
  34. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +4 -4
  35. data/app/pb_kits/playbook/pb_passphrase/_passphrase.tsx +20 -2
  36. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +51 -16
  37. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.jsx +44 -0
  38. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_label.md +1 -0
  39. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +1 -0
  40. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.jsx +28 -0
  41. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_label.md +1 -0
  42. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +1 -0
  43. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +2 -0
  44. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
  45. data/app/pb_kits/playbook/pb_table/index.ts +29 -27
  46. data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +10 -10
  47. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +410 -323
  48. data/app/pb_kits/playbook/pb_typeahead/components/Control.tsx +2 -0
  49. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.html.erb +16 -0
  50. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.jsx +23 -0
  51. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_required_indicator.md +3 -0
  52. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  53. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +22 -21
  54. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +3 -2
  55. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +3 -1
  56. data/dist/chunks/{_pb_line_graph-BgKF_zz1.js → _pb_line_graph-DuJNCf7N.js} +1 -1
  57. data/dist/chunks/_typeahead-BKSzddAX.js +1 -0
  58. data/dist/chunks/{globalProps-BhVYCqRf.js → globalProps-Bc-FVsRt.js} +1 -1
  59. data/dist/chunks/lib-BwX82vim.js +29 -0
  60. data/dist/chunks/vendor.js +3 -3
  61. data/dist/menu.yml +1 -1
  62. data/dist/playbook-rails-react-bindings.js +1 -1
  63. data/dist/playbook-rails.js +1 -1
  64. data/dist/playbook.css +1 -1
  65. data/lib/playbook/forms/builder/form_field_builder.rb +1 -1
  66. data/lib/playbook/forms/builder/typeahead_field.rb +15 -1
  67. data/lib/playbook/forms/builder.rb +2 -2
  68. data/lib/playbook/version.rb +1 -1
  69. metadata +19 -6
  70. data/dist/chunks/_typeahead-CWA5wlah.js +0 -1
  71. data/dist/chunks/lib-DD34ZrWL.js +0 -29
@@ -14,6 +14,7 @@ const DROPDOWN_INPUT = "#dropdown-selected-option";
14
14
  const SEARCH_INPUT_SELECTOR = "[data-dropdown-autocomplete]";
15
15
  const SEARCH_BAR_SELECTOR = "[data-dropdown-search]";
16
16
  const CLEAR_ICON_SELECTOR = "#dropdown_clear_icon";
17
+ const LABEL_SELECTOR = '[data-dropdown="pb-dropdown-label"]';
17
18
 
18
19
  export default class PbDropdown extends PbEnhancedElement {
19
20
  static get selector() {
@@ -30,14 +31,15 @@ export default class PbDropdown extends PbEnhancedElement {
30
31
  connect() {
31
32
  // Store instance on element for DatePicker sync
32
33
  this.element._pbDropdownInstance = this;
33
-
34
+
34
35
  this.keyboardHandler = new PbDropdownKeyboard(this);
35
36
  this.isMultiSelect = this.element.dataset.pbDropdownMultiSelect === "true";
36
37
  this.formPillProps = this.element.dataset.formPillProps
37
38
  ? JSON.parse(this.element.dataset.formPillProps)
38
39
  : {};
39
40
  const baseInput = this.element.querySelector(DROPDOWN_INPUT);
40
- this.wasOriginallyRequired = baseInput && baseInput.hasAttribute("required");
41
+ this.wasOriginallyRequired =
42
+ baseInput && baseInput.hasAttribute("required");
41
43
  this.setDefaultValue();
42
44
  this.bindEventListeners();
43
45
  this.bindSearchInput();
@@ -75,15 +77,24 @@ export default class PbDropdown extends PbEnhancedElement {
75
77
  bindEventListeners() {
76
78
  const customTrigger =
77
79
  this.element.querySelector(CUSTOM_DISPLAY_SELECTOR) || this.element;
78
- customTrigger.addEventListener("click", () =>
79
- this.toggleElement(this.target)
80
- );
80
+ customTrigger.addEventListener("click", (e) => {
81
+ const label = e.target.closest(LABEL_SELECTOR);
82
+ if (label && label.htmlFor) {
83
+ const trigger = this.element.querySelector(
84
+ `#${CSS.escape(label.htmlFor)}`,
85
+ );
86
+ if (trigger) {
87
+ trigger.focus();
88
+ }
89
+ }
90
+ this.toggleElement(this.target);
91
+ });
81
92
 
82
93
  this.target.addEventListener("click", this.handleOptionClick.bind(this));
83
94
  document.addEventListener(
84
95
  "click",
85
96
  this.handleDocumentClick.bind(this),
86
- true
97
+ true,
87
98
  );
88
99
  }
89
100
 
@@ -92,7 +103,7 @@ export default class PbDropdown extends PbEnhancedElement {
92
103
  if (!this.searchBar) return;
93
104
 
94
105
  this.searchBar.addEventListener("input", (e) =>
95
- this.handleSearch(e.target.value)
106
+ this.handleSearch(e.target.value),
96
107
  );
97
108
  }
98
109
 
@@ -107,7 +118,7 @@ export default class PbDropdown extends PbEnhancedElement {
107
118
 
108
119
  // Live filter
109
120
  this.searchInput.addEventListener("input", (e) =>
110
- this.handleSearch(e.target.value)
121
+ this.handleSearch(e.target.value),
111
122
  );
112
123
  }
113
124
 
@@ -161,28 +172,31 @@ export default class PbDropdown extends PbEnhancedElement {
161
172
 
162
173
  handleSearch(term = "") {
163
174
  const lcTerm = term.toLowerCase();
164
- let hasMatch = false
175
+ let hasMatch = false;
165
176
  this.element.querySelectorAll(OPTION_SELECTOR).forEach((opt) => {
166
177
  //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
- }
178
+ if (
179
+ this.isMultiSelect &&
180
+ this.selectedOptions.has(opt.dataset.dropdownOptionLabel)
181
+ ) {
182
+ opt.style.display = "none";
183
+ return;
184
+ }
171
185
  const label = JSON.parse(opt.dataset.dropdownOptionLabel)
172
186
  .label.toString()
173
187
  .toLowerCase();
174
188
 
175
- // hide or show option
189
+ // hide or show option
176
190
  const match = label.includes(lcTerm);
177
191
  opt.style.display = match ? "" : "none";
178
- if (match) hasMatch = true
192
+ if (match) hasMatch = true;
179
193
  });
180
194
 
181
195
  this.adjustDropdownHeight();
182
196
 
183
- this.removeNoOptionsMessage()
197
+ this.removeNoOptionsMessage();
184
198
  if (!hasMatch) {
185
- this.showNoOptionsMessage()
199
+ this.showNoOptionsMessage();
186
200
  }
187
201
  }
188
202
 
@@ -190,7 +204,8 @@ export default class PbDropdown extends PbEnhancedElement {
190
204
  if (this.element.querySelector(".dropdown_no_options")) return;
191
205
 
192
206
  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";
207
+ noOptionElement.className =
208
+ "pb_body_kit_light dropdown_no_options pb_item_kit p_xs display_flex justify_content_center";
194
209
  noOptionElement.textContent = "no option";
195
210
 
196
211
  this.target.appendChild(noOptionElement);
@@ -241,6 +256,8 @@ export default class PbDropdown extends PbEnhancedElement {
241
256
  }
242
257
 
243
258
  isClickOutside(event) {
259
+ const label = event.target.closest(LABEL_SELECTOR);
260
+ if (label && this.element.contains(label)) return false;
244
261
  const customTrigger = this.element.querySelector(CUSTOM_DISPLAY_SELECTOR);
245
262
  if (customTrigger) {
246
263
  return !customTrigger.contains(event.target);
@@ -271,8 +288,8 @@ export default class PbDropdown extends PbEnhancedElement {
271
288
  ? JSON.parse(
272
289
  this.element.querySelector(
273
290
  OPTION_SELECTOR +
274
- `[data-dropdown-option-label*='"id":"${hiddenInput.value}"']`
275
- ).dataset.dropdownOptionLabel
291
+ `[data-dropdown-option-label*='"id":"${hiddenInput.value}"']`,
292
+ ).dataset.dropdownOptionLabel,
276
293
  )
277
294
  : null;
278
295
  }
@@ -281,14 +298,14 @@ export default class PbDropdown extends PbEnhancedElement {
281
298
  new CustomEvent("pb:dropdown:selected", {
282
299
  detail,
283
300
  bubbles: true,
284
- })
301
+ }),
285
302
  );
286
303
  }
287
304
 
288
305
  onOptionSelected(value, selectedOption) {
289
306
  const triggerElement = this.element.querySelector(DROPDOWN_TRIGGER_DISPLAY);
290
307
  const customDisplayElement = this.element.querySelector(
291
- "#dropdown_trigger_custom_display"
308
+ "#dropdown_trigger_custom_display",
292
309
  );
293
310
 
294
311
  if (triggerElement) {
@@ -296,36 +313,46 @@ export default class PbDropdown extends PbEnhancedElement {
296
313
  const selectedLabel = JSON.parse(value).label;
297
314
  triggerElement.textContent = selectedLabel;
298
315
  this.emitSelectionChange();
299
-
316
+
300
317
  // Handle quickpick variant: populate start/end date hidden inputs
301
318
  const optionData = JSON.parse(value);
302
319
  const startDateId = this.element.dataset.startDateId;
303
320
  const endDateId = this.element.dataset.endDateId;
304
321
  const controlsStartId = this.element.dataset.controlsStartId;
305
322
  const controlsEndId = this.element.dataset.controlsEndId;
306
-
323
+
307
324
  if (optionData.formatted_start_date && optionData.formatted_end_date) {
308
325
  // Populate date inputs when option has date fields
309
326
  if (startDateId) {
310
327
  const startDateInput = document.getElementById(startDateId);
311
- if (startDateInput) startDateInput.value = optionData.formatted_start_date;
328
+ if (startDateInput)
329
+ startDateInput.value = optionData.formatted_start_date;
312
330
  }
313
-
331
+
314
332
  if (endDateId) {
315
333
  const endDateInput = document.getElementById(endDateId);
316
- if (endDateInput) endDateInput.value = optionData.formatted_end_date;
334
+ if (endDateInput)
335
+ endDateInput.value = optionData.formatted_end_date;
317
336
  }
318
-
337
+
319
338
  // Sync with DatePickers if controlsStartId/controlsEndId are present
320
339
  if (controlsStartId) {
321
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
340
+ const startPicker = document.querySelector(
341
+ `#${controlsStartId}`,
342
+ )?._flatpickr;
322
343
  if (startPicker) {
323
- startPicker.setDate(optionData.formatted_start_date, true, "m/d/Y");
344
+ startPicker.setDate(
345
+ optionData.formatted_start_date,
346
+ true,
347
+ "m/d/Y",
348
+ );
324
349
  }
325
350
  }
326
-
351
+
327
352
  if (controlsEndId) {
328
- const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
353
+ const endPicker = document.querySelector(
354
+ `#${controlsEndId}`,
355
+ )?._flatpickr;
329
356
  if (endPicker) {
330
357
  endPicker.setDate(optionData.formatted_end_date, true, "m/d/Y");
331
358
  }
@@ -336,22 +363,26 @@ export default class PbDropdown extends PbEnhancedElement {
336
363
  const startDateInput = document.getElementById(startDateId);
337
364
  if (startDateInput) startDateInput.value = "";
338
365
  }
339
-
366
+
340
367
  if (endDateId) {
341
368
  const endDateInput = document.getElementById(endDateId);
342
369
  if (endDateInput) endDateInput.value = "";
343
370
  }
344
-
371
+
345
372
  // Clear DatePickers as well
346
373
  if (controlsStartId) {
347
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
374
+ const startPicker = document.querySelector(
375
+ `#${controlsStartId}`,
376
+ )?._flatpickr;
348
377
  if (startPicker) {
349
378
  startPicker.clear();
350
379
  }
351
380
  }
352
-
381
+
353
382
  if (controlsEndId) {
354
- const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
383
+ const endPicker = document.querySelector(
384
+ `#${controlsEndId}`,
385
+ )?._flatpickr;
355
386
  if (endPicker) {
356
387
  endPicker.clear();
357
388
  }
@@ -391,7 +422,9 @@ export default class PbDropdown extends PbEnhancedElement {
391
422
  this.adjustDropdownHeight();
392
423
  }
393
424
  });
394
- this.element.querySelector(DROPDOWN_INPUT).value = Array.from(this.selectedOptions)
425
+ this.element.querySelector(DROPDOWN_INPUT).value = Array.from(
426
+ this.selectedOptions,
427
+ )
395
428
  .map((opt) => JSON.parse(opt).id)
396
429
  .join(",");
397
430
  } else {
@@ -437,7 +470,7 @@ export default class PbDropdown extends PbEnhancedElement {
437
470
  this.keyboardHandler.focusedOptionIndex = -1;
438
471
  const options = this.element.querySelectorAll(OPTION_SELECTOR);
439
472
  options.forEach((option) =>
440
- option.classList.remove("pb_dropdown_option_focused")
473
+ option.classList.remove("pb_dropdown_option_focused"),
441
474
  );
442
475
  }
443
476
  }
@@ -472,7 +505,7 @@ export default class PbDropdown extends PbEnhancedElement {
472
505
  hiddenInput.closest(".dropdown_wrapper").classList.add("error");
473
506
  }
474
507
  },
475
- true
508
+ true,
476
509
  );
477
510
  }
478
511
 
@@ -482,7 +515,7 @@ export default class PbDropdown extends PbEnhancedElement {
482
515
  const dropdownWrapperElement = input.closest(".dropdown_wrapper");
483
516
  dropdownWrapperElement.classList.remove("error");
484
517
  const errorLabelElement = dropdownWrapperElement.querySelector(
485
- ".pb_body_kit_negative"
518
+ ".pb_body_kit_negative",
486
519
  );
487
520
  if (errorLabelElement) {
488
521
  errorLabelElement.remove();
@@ -490,13 +523,13 @@ export default class PbDropdown extends PbEnhancedElement {
490
523
  return;
491
524
  }
492
525
  }
493
-
526
+
494
527
  if (input.checkValidity()) {
495
528
  const dropdownWrapperElement = input.closest(".dropdown_wrapper");
496
529
  dropdownWrapperElement.classList.remove("error");
497
530
 
498
531
  const errorLabelElement = dropdownWrapperElement.querySelector(
499
- ".pb_body_kit_negative"
532
+ ".pb_body_kit_negative",
500
533
  );
501
534
  if (errorLabelElement) {
502
535
  errorLabelElement.remove();
@@ -507,7 +540,7 @@ export default class PbDropdown extends PbEnhancedElement {
507
540
  setDefaultValue() {
508
541
  const hiddenInput = this.element.querySelector(DROPDOWN_INPUT);
509
542
  const optionEls = Array.from(
510
- this.element.querySelectorAll(OPTION_SELECTOR)
543
+ this.element.querySelectorAll(OPTION_SELECTOR),
511
544
  );
512
545
  const defaultValue = hiddenInput.dataset.defaultValue || "";
513
546
  if (!defaultValue) return;
@@ -553,44 +586,53 @@ export default class PbDropdown extends PbEnhancedElement {
553
586
  selectedOption.classList.add("pb_dropdown_option_selected");
554
587
  const optionData = JSON.parse(selectedOption.dataset.dropdownOptionLabel);
555
588
  this.setTriggerElementText(optionData.label);
556
-
589
+
557
590
  // Handle quickpick variant: populate start/end date hidden inputs and sync DatePickers
558
591
  if (optionData.formatted_start_date && optionData.formatted_end_date) {
559
592
  const startDateId = this.element.dataset.startDateId;
560
593
  const endDateId = this.element.dataset.endDateId;
561
594
  const controlsStartId = this.element.dataset.controlsStartId;
562
595
  const controlsEndId = this.element.dataset.controlsEndId;
563
-
596
+
564
597
  if (startDateId) {
565
598
  const startDateInput = document.getElementById(startDateId);
566
- if (startDateInput) startDateInput.value = optionData.formatted_start_date;
599
+ if (startDateInput)
600
+ startDateInput.value = optionData.formatted_start_date;
567
601
  }
568
-
602
+
569
603
  if (endDateId) {
570
604
  const endDateInput = document.getElementById(endDateId);
571
605
  if (endDateInput) endDateInput.value = optionData.formatted_end_date;
572
606
  }
573
-
607
+
574
608
  // Sync with DatePickers - retry with delays to ensure DatePickers are initialized
575
609
  const syncDatePickers = () => {
576
610
  if (controlsStartId) {
577
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
611
+ const startPicker = document.querySelector(
612
+ `#${controlsStartId}`,
613
+ )?._flatpickr;
578
614
  if (startPicker) {
579
- startPicker.setDate(optionData.formatted_start_date, true, "m/d/Y");
615
+ startPicker.setDate(
616
+ optionData.formatted_start_date,
617
+ true,
618
+ "m/d/Y",
619
+ );
580
620
  }
581
621
  }
582
-
622
+
583
623
  if (controlsEndId) {
584
- const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
624
+ const endPicker = document.querySelector(
625
+ `#${controlsEndId}`,
626
+ )?._flatpickr;
585
627
  if (endPicker) {
586
628
  endPicker.setDate(optionData.formatted_end_date, true, "m/d/Y");
587
629
  }
588
630
  }
589
631
  };
590
-
632
+
591
633
  // Try immediately
592
634
  syncDatePickers();
593
-
635
+
594
636
  // Retry after short delay in case DatePickers aren't ready yet
595
637
  setTimeout(syncDatePickers, 100);
596
638
  setTimeout(syncDatePickers, 300);
@@ -653,7 +695,7 @@ export default class PbDropdown extends PbEnhancedElement {
653
695
 
654
696
  const wrapper = this.element.querySelector("#dropdown_pills_wrapper");
655
697
  const placeholder = this.element.querySelector(
656
- "#dropdown_trigger_display_multi_select"
698
+ "#dropdown_trigger_display_multi_select",
657
699
  );
658
700
  if (!wrapper) return;
659
701
 
@@ -671,7 +713,12 @@ export default class PbDropdown extends PbEnhancedElement {
671
713
  // Create a form pill for each selected option
672
714
  const pill = document.createElement("div");
673
715
  const color = this.formPillProps.color || "primary";
674
- pill.classList.add("pb_form_pill_kit", `pb_form_pill_${color}`, "pb_form_pill_none", "mr_xs");
716
+ pill.classList.add(
717
+ "pb_form_pill_kit",
718
+ `pb_form_pill_${color}`,
719
+ "pb_form_pill_none",
720
+ "mr_xs",
721
+ );
675
722
  if (this.formPillProps.size === "small") {
676
723
  pill.classList.add("pb_form_pill_small");
677
724
  }
@@ -696,8 +743,8 @@ export default class PbDropdown extends PbEnhancedElement {
696
743
 
697
744
  const optEl = this.element.querySelector(
698
745
  `${OPTION_SELECTOR}[data-dropdown-option-label*='"id":${JSON.stringify(
699
- id
700
- )}']`
746
+ id,
747
+ )}']`,
701
748
  );
702
749
  if (optEl) {
703
750
  optEl.style.display = "";
@@ -726,18 +773,18 @@ export default class PbDropdown extends PbEnhancedElement {
726
773
  }
727
774
  }
728
775
  const customDisplay = this.element.querySelector(
729
- "#dropdown_trigger_custom_display"
776
+ "#dropdown_trigger_custom_display",
730
777
  );
731
778
  if (customDisplay) {
732
779
  customDisplay.style.display = "none";
733
780
  }
734
-
781
+
735
782
  // Clear quickpick hidden inputs
736
783
  const startDateId = this.element.dataset.startDateId;
737
784
  const endDateId = this.element.dataset.endDateId;
738
785
  const controlsStartId = this.element.dataset.controlsStartId;
739
786
  const controlsEndId = this.element.dataset.controlsEndId;
740
-
787
+
741
788
  if (startDateId) {
742
789
  const startDateInput = document.getElementById(startDateId);
743
790
  if (startDateInput) startDateInput.value = "";
@@ -746,22 +793,24 @@ export default class PbDropdown extends PbEnhancedElement {
746
793
  const endDateInput = document.getElementById(endDateId);
747
794
  if (endDateInput) endDateInput.value = "";
748
795
  }
749
-
796
+
750
797
  // Clear linked DatePickers if controlsStartId/controlsEndId are present
751
798
  if (controlsStartId) {
752
- const startPicker = document.querySelector(`#${controlsStartId}`)?._flatpickr;
799
+ const startPicker = document.querySelector(
800
+ `#${controlsStartId}`,
801
+ )?._flatpickr;
753
802
  if (startPicker) {
754
803
  startPicker.clear();
755
804
  }
756
805
  }
757
-
806
+
758
807
  if (controlsEndId) {
759
808
  const endPicker = document.querySelector(`#${controlsEndId}`)?._flatpickr;
760
809
  if (endPicker) {
761
810
  endPicker.clear();
762
811
  }
763
812
  }
764
-
813
+
765
814
  this.resetDropdownValue();
766
815
  this.updatePills();
767
816
  this.updateClearButton();
@@ -772,21 +821,24 @@ export default class PbDropdown extends PbEnhancedElement {
772
821
  // Method for DatePicker sync - only clears the dropdown, not the DatePickers
773
822
  clearSelected() {
774
823
  // Only clear if this is a single-select quickpick variant
775
- if (this.element.dataset.pbDropdownVariant !== "quickpick" || this.isMultiSelect) {
824
+ if (
825
+ this.element.dataset.pbDropdownVariant !== "quickpick" ||
826
+ this.isMultiSelect
827
+ ) {
776
828
  return;
777
829
  }
778
-
830
+
779
831
  const customDisplay = this.element.querySelector(
780
- "#dropdown_trigger_custom_display"
832
+ "#dropdown_trigger_custom_display",
781
833
  );
782
834
  if (customDisplay) {
783
835
  customDisplay.style.display = "none";
784
836
  }
785
-
837
+
786
838
  // Clear quickpick hidden inputs only (not the DatePickers)
787
839
  const startDateId = this.element.dataset.startDateId;
788
840
  const endDateId = this.element.dataset.endDateId;
789
-
841
+
790
842
  if (startDateId) {
791
843
  const startDateInput = document.getElementById(startDateId);
792
844
  if (startDateInput) startDateInput.value = "";
@@ -795,7 +847,7 @@ export default class PbDropdown extends PbEnhancedElement {
795
847
  const endDateInput = document.getElementById(endDateId);
796
848
  if (endDateInput) endDateInput.value = "";
797
849
  }
798
-
850
+
799
851
  this.resetDropdownValue();
800
852
  this.updateClearButton();
801
853
  this.emitSelectionChange();
@@ -822,7 +874,7 @@ export default class PbDropdown extends PbEnhancedElement {
822
874
  inp.dataset.generated = "true";
823
875
  baseInput.insertAdjacentElement("afterend", inp);
824
876
  });
825
-
877
+
826
878
  // For multi-select, remove required from base input when there are selections
827
879
  // The generated inputs handle the form submission with actual values
828
880
  // 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
  }