playbook_ui 18.2.0.pre.alpha.PLAY3330requiredtypeaheadvalidationbug20501 → 18.2.0.pre.alpha.PLAY3330requiredtypeaheadvalidationbug20513

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7dbf0a3b9ab274f14a2649da9a731e7ecd620059ecd14bfb6ca48f5348a5e9aa
4
- data.tar.gz: c4bfe24249c642c27e8b8872ffefbd39732536f1ef268511f9a48aa9f465d384
3
+ metadata.gz: f9a07c8fc62bc1d10aa56006d9eeeb75205676445028b7bd80aef17e361c8973
4
+ data.tar.gz: 0314b68de5cd6b14230b1879806806078a120fec39ed60489c1523b489347b93
5
5
  SHA512:
6
- metadata.gz: 9c5ec95ec8876bec1bb49e151e24c3f0d39c3c5e5b9c6a764809432f1648b1ddfbb93a3339becb7f30164ea9776fb914baf05e6f45d70d9741cbf27ab0bd1183
7
- data.tar.gz: 50e2c8cf11b1222b06142994a0f7d83970b2e746331bd6fd478d6f402caecc0ea13f1fc8f60fa1ef62d2246bd56a3581e399076248667a995f1cdc29b9fb3c5b
6
+ metadata.gz: 3b122cf616f2f43a735c51aafb13ca40fe5ad44e639dae5289abb33c0de7a7afbf679338889ee2ef3c397551c41bfacc5801b610709138ae4dd7c148df7830e9
7
+ data.tar.gz: ce7c196c03bfca844e78e3651e1be09804b7373890a69cfeceb671592b75bed78c3d976b3e5978e8ff81459d23c3d6a010faf88044ae4e0119b12550ea30202c
@@ -630,3 +630,129 @@ test(':set calls onChange but does not dispatch result-option-select', async ()
630
630
  selectHandler,
631
631
  )
632
632
  })
633
+
634
+ test('required Typeahead in validated form shows its own error and stays mounted after selection', async () => {
635
+ render(
636
+ <form data-pb-form-validation="true">
637
+ <Typeahead
638
+ data={{ testid: 'required-validated-typeahead' }}
639
+ isMulti={false}
640
+ name="favorite_color"
641
+ options={options}
642
+ required
643
+ validation={{ message: 'Please select a color' }}
644
+ />
645
+ </form>
646
+ )
647
+
648
+ const kit = screen.getByTestId('required-validated-typeahead')
649
+ const requiredInput = kit.querySelector('input[required]')
650
+ expect(requiredInput).toBeInTheDocument()
651
+
652
+ act(() => {
653
+ requiredInput.dispatchEvent(new Event('invalid', { bubbles: false, cancelable: true }))
654
+ })
655
+
656
+ await waitFor(() => {
657
+ expect(kit.querySelector('.pb_body_kit_negative')).toHaveTextContent('Please select a color')
658
+ })
659
+
660
+ // PbFormValidation must not own this input — no injected sibling error on the
661
+ // react-select RequiredInput parent (that path caused removeChild crashes).
662
+ const injectedSibling = Array.from(requiredInput.parentElement.children).find(
663
+ (child) => child.classList?.contains('pb_body_kit_negative')
664
+ )
665
+ expect(injectedSibling).toBeUndefined()
666
+
667
+ const control = kit.querySelector('.typeahead-kit-select__control')
668
+ fireEvent.mouseDown(control)
669
+
670
+ await waitFor(() => {
671
+ expect(document.querySelector('.typeahead-kit-select__option')).toBeInTheDocument()
672
+ })
673
+
674
+ fireEvent.click(screen.getByText('Orange'))
675
+
676
+ await waitFor(() => {
677
+ expect(screen.getByTestId('required-validated-typeahead')).toBeInTheDocument()
678
+ expect(
679
+ screen.getByTestId('required-validated-typeahead').querySelector('.pb_body_kit_negative')
680
+ ).not.toBeInTheDocument()
681
+ })
682
+ })
683
+
684
+ test('required Typeahead restores validation error after clear and resubmit', async () => {
685
+ render(
686
+ <form data-pb-form-validation="true">
687
+ <Typeahead
688
+ data={{ testid: 'required-clear-resubmit-typeahead' }}
689
+ defaultValue={options[0]}
690
+ isMulti={false}
691
+ name="favorite_color"
692
+ options={options}
693
+ required
694
+ validation={{ message: 'Please select a color' }}
695
+ />
696
+ </form>
697
+ )
698
+
699
+ const kit = screen.getByTestId('required-clear-resubmit-typeahead')
700
+ const clearIndicator = kit.querySelector('.typeahead-kit-select__clear-indicator')
701
+ expect(clearIndicator).toBeInTheDocument()
702
+ fireEvent.mouseDown(clearIndicator)
703
+
704
+ await waitFor(() => {
705
+ expect(kit.querySelector('input[required]')).toBeInTheDocument()
706
+ })
707
+
708
+ const requiredInput = kit.querySelector('input[required]')
709
+ act(() => {
710
+ requiredInput.dispatchEvent(new Event('invalid', { bubbles: false, cancelable: true }))
711
+ })
712
+
713
+ await waitFor(() => {
714
+ expect(kit.querySelector('.pb_body_kit_negative')).toHaveTextContent('Please select a color')
715
+ })
716
+ })
717
+
718
+ test('required Typeahead suppresses native invalid UI only inside validated Playbook forms', () => {
719
+ const { unmount } = render(
720
+ <form data-pb-form-validation="true">
721
+ <Typeahead
722
+ data={{ testid: 'native-invalid-validated' }}
723
+ isMulti={false}
724
+ name="color"
725
+ options={options}
726
+ required
727
+ />
728
+ </form>
729
+ )
730
+
731
+ let requiredInput = screen.getByTestId('native-invalid-validated').querySelector('input[required]')
732
+ const validatedEvent = new Event('invalid', { bubbles: false, cancelable: true })
733
+ act(() => {
734
+ requiredInput.dispatchEvent(validatedEvent)
735
+ })
736
+ expect(validatedEvent.defaultPrevented).toBe(true)
737
+
738
+ unmount()
739
+
740
+ render(
741
+ <form>
742
+ <Typeahead
743
+ data={{ testid: 'native-invalid-unvalidated' }}
744
+ isMulti={false}
745
+ name="color"
746
+ options={options}
747
+ required
748
+ />
749
+ </form>
750
+ )
751
+
752
+ requiredInput = screen.getByTestId('native-invalid-unvalidated').querySelector('input[required]')
753
+ const unvalidatedEvent = new Event('invalid', { bubbles: false, cancelable: true })
754
+ act(() => {
755
+ requiredInput.dispatchEvent(unvalidatedEvent)
756
+ })
757
+ expect(unvalidatedEvent.defaultPrevented).toBe(false)
758
+ })
@@ -541,20 +541,25 @@ const resolvedLoadOptions =
541
541
 
542
542
  const [contextValue, setContextValue] = useState("")
543
543
 
544
- // Add listener for form validation to track when validation should be shown (needed for react rendered rails kit)
544
+ // Track form submit validation for react-rendered Typeahead (own error UI).
545
+ // When inside pb_form_with validate: true, suppress the browser's native
546
+ // invalid UI — PbFormValidation skips this kit so it must not double-show.
545
547
  useEffect(() => {
546
548
  const handleInvalid = (event: Event) => {
547
549
  const target = event.target as HTMLInputElement
548
- const typeaheadContainer = target.closest(
549
- '[data-pb-react-component="Typeahead"]',
550
- )
550
+ if (!kitContainerRef.current?.contains(target)) return
551
551
 
552
- if (typeaheadContainer) {
553
- // Check if this invalid event is specifically for our typeahead by comparing names so we do not have to require ids
554
- const invalidInputName = target.name || target.getAttribute("name")
555
- if (invalidInputName === name) {
556
- setFormSubmitted(true)
557
- }
552
+ // Match by name so we do not have to require ids
553
+ const invalidInputName = target.name || target.getAttribute("name")
554
+ if (invalidInputName !== name) return
555
+
556
+ setFormSubmitted(true)
557
+
558
+ const validatedForm = target.closest(
559
+ 'form[data-pb-form-validation="true"]',
560
+ )
561
+ if (validatedForm) {
562
+ event.preventDefault()
558
563
  }
559
564
  }
560
565
  document.addEventListener("invalid", handleInvalid, true)
data/dist/ai/forms.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "metadataVersion": 1,
3
- "version": "18.2.0-alpha.PLAY3330requiredtypeaheadvalidationbug20501",
3
+ "version": "18.2.0-alpha.PLAY3330requiredtypeaheadvalidationbug20513",
4
4
  "builder": {
5
5
  "class": "Playbook::Forms::Builder",
6
6
  "helper": "pb_form_with",
data/dist/ai/index.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "18.2.0-alpha.PLAY3330requiredtypeaheadvalidationbug20501",
2
+ "version": "18.2.0-alpha.PLAY3330requiredtypeaheadvalidationbug20513",
3
3
  "metadataVersion": 1,
4
4
  "forms": "forms.json",
5
- "generated": "2026-10-26T17:28:46.243Z",
5
+ "generated": "2026-10-26T18:47:24.653Z",
6
6
  "schemas": {
7
7
  "globalProps": "global-props.schema.json",
8
8
  "globalEventProps": "global-event-props.schema.json",