@sharedex/ui 0.9.0 → 0.10.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.
- package/README.md +37 -2
- package/dist/backgrounds/hosts/CssHost.d.ts +2 -6
- package/dist/backgrounds/hosts/CssHost.js +3 -17
- package/dist/backgrounds/hosts/styleHref.d.ts +6 -0
- package/dist/backgrounds/hosts/styleHref.js +20 -0
- package/dist/buttons/pick.d.ts +9 -3
- package/dist/buttons/pick.js +4 -1
- package/dist/forms/base.d.ts +1 -0
- package/dist/forms/base.js +224 -0
- package/dist/forms/buddy.d.ts +6 -0
- package/dist/forms/buddy.js +150 -0
- package/dist/forms/chips.d.ts +1 -0
- package/dist/forms/chips.js +140 -0
- package/dist/forms/choice.d.ts +1 -0
- package/dist/forms/choice.js +208 -0
- package/dist/forms/controls/CheckGroup.d.ts +65 -0
- package/dist/forms/controls/CheckGroup.js +99 -0
- package/dist/forms/controls/Checkbox.d.ts +43 -0
- package/dist/forms/controls/Checkbox.js +67 -0
- package/dist/forms/controls/ChipGroup.d.ts +50 -0
- package/dist/forms/controls/ChipGroup.js +56 -0
- package/dist/forms/controls/Combobox.d.ts +75 -0
- package/dist/forms/controls/Combobox.js +249 -0
- package/dist/forms/controls/ErrorSummary.d.ts +29 -0
- package/dist/forms/controls/ErrorSummary.js +79 -0
- package/dist/forms/controls/Field.d.ts +92 -0
- package/dist/forms/controls/Field.js +219 -0
- package/dist/forms/controls/FileDrop.d.ts +81 -0
- package/dist/forms/controls/FileDrop.js +328 -0
- package/dist/forms/controls/FormBuddy.d.ts +22 -0
- package/dist/forms/controls/FormBuddy.js +143 -0
- package/dist/forms/controls/FormReveal.d.ts +21 -0
- package/dist/forms/controls/FormReveal.js +19 -0
- package/dist/forms/controls/FormSteps.d.ts +75 -0
- package/dist/forms/controls/FormSteps.js +205 -0
- package/dist/forms/controls/FormSwap.d.ts +27 -0
- package/dist/forms/controls/FormSwap.js +38 -0
- package/dist/forms/controls/InlineEdit.d.ts +50 -0
- package/dist/forms/controls/InlineEdit.js +136 -0
- package/dist/forms/controls/NumberField.d.ts +43 -0
- package/dist/forms/controls/NumberField.js +205 -0
- package/dist/forms/controls/OtpField.d.ts +46 -0
- package/dist/forms/controls/OtpField.js +195 -0
- package/dist/forms/controls/PasswordRules.d.ts +31 -0
- package/dist/forms/controls/PasswordRules.js +85 -0
- package/dist/forms/controls/RadioGroup.d.ts +69 -0
- package/dist/forms/controls/RadioGroup.js +95 -0
- package/dist/forms/controls/Range.d.ts +60 -0
- package/dist/forms/controls/Range.js +169 -0
- package/dist/forms/controls/SaveStatus.d.ts +25 -0
- package/dist/forms/controls/SaveStatus.js +88 -0
- package/dist/forms/controls/Select.d.ts +46 -0
- package/dist/forms/controls/Select.js +35 -0
- package/dist/forms/controls/Switch.d.ts +54 -0
- package/dist/forms/controls/Switch.js +91 -0
- package/dist/forms/controls/TagsField.d.ts +68 -0
- package/dist/forms/controls/TagsField.js +0 -0
- package/dist/forms/controls/choiceChecks.d.ts +11 -0
- package/dist/forms/controls/choiceChecks.js +19 -0
- package/dist/forms/controls/choiceParts.d.ts +40 -0
- package/dist/forms/controls/choiceParts.js +49 -0
- package/dist/forms/controls/fieldChecks.d.ts +17 -0
- package/dist/forms/controls/fieldChecks.js +34 -0
- package/dist/forms/controls/fieldTools.d.ts +62 -0
- package/dist/forms/controls/fieldTools.js +54 -0
- package/dist/forms/controls/pickerChecks.d.ts +10 -0
- package/dist/forms/controls/pickerChecks.js +27 -0
- package/dist/forms/controls/rangeChecks.d.ts +11 -0
- package/dist/forms/controls/rangeChecks.js +23 -0
- package/dist/forms/controls/shared.d.ts +8 -0
- package/dist/forms/controls/shared.js +18 -0
- package/dist/forms/effects/cards.d.ts +11 -0
- package/dist/forms/effects/cards.js +52 -0
- package/dist/forms/effects/check/checkCard.d.ts +10 -0
- package/dist/forms/effects/check/checkCard.js +15 -0
- package/dist/forms/effects/check/checkCard.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkCard.schema.js +9 -0
- package/dist/forms/effects/check/checkConfetti.d.ts +18 -0
- package/dist/forms/effects/check/checkConfetti.js +38 -0
- package/dist/forms/effects/check/checkConfetti.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkConfetti.schema.js +13 -0
- package/dist/forms/effects/check/checkDraw.d.ts +7 -0
- package/dist/forms/effects/check/checkDraw.js +12 -0
- package/dist/forms/effects/check/checkDraw.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkDraw.schema.js +9 -0
- package/dist/forms/effects/check/checkHeart.d.ts +9 -0
- package/dist/forms/effects/check/checkHeart.js +84 -0
- package/dist/forms/effects/check/checkHeart.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkHeart.schema.js +10 -0
- package/dist/forms/effects/check/checkJelly.d.ts +9 -0
- package/dist/forms/effects/check/checkJelly.js +23 -0
- package/dist/forms/effects/check/checkJelly.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkJelly.schema.js +10 -0
- package/dist/forms/effects/check/checkMerge.d.ts +9 -0
- package/dist/forms/effects/check/checkMerge.js +37 -0
- package/dist/forms/effects/check/checkMerge.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkMerge.schema.js +10 -0
- package/dist/forms/effects/check/checkMixed.d.ts +7 -0
- package/dist/forms/effects/check/checkMixed.js +25 -0
- package/dist/forms/effects/check/checkMixed.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkMixed.schema.js +9 -0
- package/dist/forms/effects/check/checkMorph.d.ts +9 -0
- package/dist/forms/effects/check/checkMorph.js +53 -0
- package/dist/forms/effects/check/checkMorph.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkMorph.schema.js +10 -0
- package/dist/forms/effects/check/checkPop.d.ts +9 -0
- package/dist/forms/effects/check/checkPop.js +27 -0
- package/dist/forms/effects/check/checkPop.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkPop.schema.js +10 -0
- package/dist/forms/effects/check/checkRipple.d.ts +7 -0
- package/dist/forms/effects/check/checkRipple.js +33 -0
- package/dist/forms/effects/check/checkRipple.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkRipple.schema.js +9 -0
- package/dist/forms/effects/check/checkScribble.d.ts +11 -0
- package/dist/forms/effects/check/checkScribble.js +42 -0
- package/dist/forms/effects/check/checkScribble.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkScribble.schema.js +11 -0
- package/dist/forms/effects/check/checkStrike.d.ts +9 -0
- package/dist/forms/effects/check/checkStrike.js +54 -0
- package/dist/forms/effects/check/checkStrike.schema.d.ts +1 -0
- package/dist/forms/effects/check/checkStrike.schema.js +10 -0
- package/dist/forms/effects/chip/chipCheck.d.ts +7 -0
- package/dist/forms/effects/chip/chipCheck.js +26 -0
- package/dist/forms/effects/chip/chipCheck.schema.d.ts +1 -0
- package/dist/forms/effects/chip/chipCheck.schema.js +9 -0
- package/dist/forms/effects/chip/chipMerge.d.ts +9 -0
- package/dist/forms/effects/chip/chipMerge.js +63 -0
- package/dist/forms/effects/chip/chipMerge.schema.d.ts +1 -0
- package/dist/forms/effects/chip/chipMerge.schema.js +10 -0
- package/dist/forms/effects/file/fileButton.d.ts +7 -0
- package/dist/forms/effects/file/fileButton.js +28 -0
- package/dist/forms/effects/file/fileButton.schema.d.ts +1 -0
- package/dist/forms/effects/file/fileButton.schema.js +9 -0
- package/dist/forms/effects/file/fileCards.d.ts +7 -0
- package/dist/forms/effects/file/fileCards.js +38 -0
- package/dist/forms/effects/file/fileCards.schema.d.ts +1 -0
- package/dist/forms/effects/file/fileCards.schema.js +9 -0
- package/dist/forms/effects/file/fileDrag.d.ts +9 -0
- package/dist/forms/effects/file/fileDrag.js +45 -0
- package/dist/forms/effects/file/fileDrag.schema.d.ts +1 -0
- package/dist/forms/effects/file/fileDrag.schema.js +10 -0
- package/dist/forms/effects/file/filePage.d.ts +7 -0
- package/dist/forms/effects/file/filePage.js +46 -0
- package/dist/forms/effects/file/filePage.schema.d.ts +1 -0
- package/dist/forms/effects/file/filePage.schema.js +9 -0
- package/dist/forms/effects/file/filePreview.d.ts +7 -0
- package/dist/forms/effects/file/filePreview.js +17 -0
- package/dist/forms/effects/file/filePreview.schema.d.ts +1 -0
- package/dist/forms/effects/file/filePreview.schema.js +9 -0
- package/dist/forms/effects/file/fileProgress.d.ts +9 -0
- package/dist/forms/effects/file/fileProgress.js +92 -0
- package/dist/forms/effects/file/fileProgress.schema.d.ts +1 -0
- package/dist/forms/effects/file/fileProgress.schema.js +10 -0
- package/dist/forms/effects/file/fileReject.d.ts +7 -0
- package/dist/forms/effects/file/fileReject.js +24 -0
- package/dist/forms/effects/file/fileReject.schema.d.ts +1 -0
- package/dist/forms/effects/file/fileReject.schema.js +9 -0
- package/dist/forms/effects/input/inputAside.d.ts +11 -0
- package/dist/forms/effects/input/inputAside.js +42 -0
- package/dist/forms/effects/input/inputAside.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputAside.schema.js +11 -0
- package/dist/forms/effects/input/inputBeam.d.ts +14 -0
- package/dist/forms/effects/input/inputBeam.js +51 -0
- package/dist/forms/effects/input/inputBeam.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputBeam.schema.js +18 -0
- package/dist/forms/effects/input/inputCaret.d.ts +13 -0
- package/dist/forms/effects/input/inputCaret.js +33 -0
- package/dist/forms/effects/input/inputCaret.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputCaret.schema.js +26 -0
- package/dist/forms/effects/input/inputCounter.d.ts +16 -0
- package/dist/forms/effects/input/inputCounter.js +84 -0
- package/dist/forms/effects/input/inputCounter.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputCounter.schema.js +12 -0
- package/dist/forms/effects/input/inputCycle.d.ts +19 -0
- package/dist/forms/effects/input/inputCycle.js +93 -0
- package/dist/forms/effects/input/inputCycle.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputCycle.schema.js +11 -0
- package/dist/forms/effects/input/inputDepth.d.ts +11 -0
- package/dist/forms/effects/input/inputDepth.js +31 -0
- package/dist/forms/effects/input/inputDepth.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputDepth.schema.js +11 -0
- package/dist/forms/effects/input/inputDraw.d.ts +13 -0
- package/dist/forms/effects/input/inputDraw.js +27 -0
- package/dist/forms/effects/input/inputDraw.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputDraw.schema.js +12 -0
- package/dist/forms/effects/input/inputFlap.d.ts +9 -0
- package/dist/forms/effects/input/inputFlap.js +24 -0
- package/dist/forms/effects/input/inputFlap.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputFlap.schema.js +10 -0
- package/dist/forms/effects/input/inputFloat.d.ts +12 -0
- package/dist/forms/effects/input/inputFloat.js +46 -0
- package/dist/forms/effects/input/inputFloat.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputFloat.schema.js +10 -0
- package/dist/forms/effects/input/inputFlood.d.ts +11 -0
- package/dist/forms/effects/input/inputFlood.js +30 -0
- package/dist/forms/effects/input/inputFlood.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputFlood.schema.js +11 -0
- package/dist/forms/effects/input/inputFrame.d.ts +18 -0
- package/dist/forms/effects/input/inputFrame.js +77 -0
- package/dist/forms/effects/input/inputFrame.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputFrame.schema.js +13 -0
- package/dist/forms/effects/input/inputIcon.d.ts +11 -0
- package/dist/forms/effects/input/inputIcon.js +18 -0
- package/dist/forms/effects/input/inputIcon.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputIcon.schema.js +11 -0
- package/dist/forms/effects/input/inputNotch.d.ts +9 -0
- package/dist/forms/effects/input/inputNotch.js +70 -0
- package/dist/forms/effects/input/inputNotch.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputNotch.schema.js +10 -0
- package/dist/forms/effects/input/inputReveal.d.ts +15 -0
- package/dist/forms/effects/input/inputReveal.js +56 -0
- package/dist/forms/effects/input/inputReveal.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputReveal.schema.js +16 -0
- package/dist/forms/effects/input/inputRing.d.ts +13 -0
- package/dist/forms/effects/input/inputRing.js +27 -0
- package/dist/forms/effects/input/inputRing.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputRing.schema.js +12 -0
- package/dist/forms/effects/input/inputRise.d.ts +9 -0
- package/dist/forms/effects/input/inputRise.js +22 -0
- package/dist/forms/effects/input/inputRise.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputRise.schema.js +10 -0
- package/dist/forms/effects/input/inputSearch.d.ts +9 -0
- package/dist/forms/effects/input/inputSearch.js +37 -0
- package/dist/forms/effects/input/inputSearch.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputSearch.schema.js +10 -0
- package/dist/forms/effects/input/inputSkeleton.d.ts +7 -0
- package/dist/forms/effects/input/inputSkeleton.js +41 -0
- package/dist/forms/effects/input/inputSkeleton.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputSkeleton.schema.js +9 -0
- package/dist/forms/effects/input/inputSpotlight.d.ts +9 -0
- package/dist/forms/effects/input/inputSpotlight.js +20 -0
- package/dist/forms/effects/input/inputSpotlight.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputSpotlight.schema.js +10 -0
- package/dist/forms/effects/input/inputStrength.d.ts +9 -0
- package/dist/forms/effects/input/inputStrength.js +29 -0
- package/dist/forms/effects/input/inputStrength.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputStrength.schema.js +10 -0
- package/dist/forms/effects/input/inputSwap.d.ts +11 -0
- package/dist/forms/effects/input/inputSwap.js +39 -0
- package/dist/forms/effects/input/inputSwap.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputSwap.schema.js +11 -0
- package/dist/forms/effects/input/inputUnderline.d.ts +13 -0
- package/dist/forms/effects/input/inputUnderline.js +36 -0
- package/dist/forms/effects/input/inputUnderline.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputUnderline.schema.js +18 -0
- package/dist/forms/effects/input/inputWave.d.ts +11 -0
- package/dist/forms/effects/input/inputWave.js +23 -0
- package/dist/forms/effects/input/inputWave.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputWave.schema.js +11 -0
- package/dist/forms/effects/input/inputWavy.d.ts +11 -0
- package/dist/forms/effects/input/inputWavy.js +53 -0
- package/dist/forms/effects/input/inputWavy.schema.d.ts +1 -0
- package/dist/forms/effects/input/inputWavy.schema.js +11 -0
- package/dist/forms/effects/kit.d.ts +213 -0
- package/dist/forms/effects/kit.js +226 -0
- package/dist/forms/effects/picker/chipPop.d.ts +7 -0
- package/dist/forms/effects/picker/chipPop.js +28 -0
- package/dist/forms/effects/picker/chipPop.schema.d.ts +1 -0
- package/dist/forms/effects/picker/chipPop.schema.js +9 -0
- package/dist/forms/effects/picker/listHighlight.d.ts +9 -0
- package/dist/forms/effects/picker/listHighlight.js +42 -0
- package/dist/forms/effects/picker/listHighlight.schema.d.ts +1 -0
- package/dist/forms/effects/picker/listHighlight.schema.js +10 -0
- package/dist/forms/effects/picker/listMatch.d.ts +7 -0
- package/dist/forms/effects/picker/listMatch.js +21 -0
- package/dist/forms/effects/picker/listMatch.schema.d.ts +1 -0
- package/dist/forms/effects/picker/listMatch.schema.js +9 -0
- package/dist/forms/effects/picker/pickerArrow.d.ts +9 -0
- package/dist/forms/effects/picker/pickerArrow.js +17 -0
- package/dist/forms/effects/picker/pickerArrow.schema.d.ts +1 -0
- package/dist/forms/effects/picker/pickerArrow.schema.js +10 -0
- package/dist/forms/effects/picker/pickerCheck.d.ts +7 -0
- package/dist/forms/effects/picker/pickerCheck.js +31 -0
- package/dist/forms/effects/picker/pickerCheck.schema.d.ts +1 -0
- package/dist/forms/effects/picker/pickerCheck.schema.js +9 -0
- package/dist/forms/effects/picker/pickerOpen.d.ts +12 -0
- package/dist/forms/effects/picker/pickerOpen.js +34 -0
- package/dist/forms/effects/picker/pickerOpen.schema.d.ts +1 -0
- package/dist/forms/effects/picker/pickerOpen.schema.js +16 -0
- package/dist/forms/effects/picker/pickerSheet.d.ts +9 -0
- package/dist/forms/effects/picker/pickerSheet.js +47 -0
- package/dist/forms/effects/picker/pickerSheet.schema.d.ts +1 -0
- package/dist/forms/effects/picker/pickerSheet.schema.js +10 -0
- package/dist/forms/effects/picker/pickerStagger.d.ts +9 -0
- package/dist/forms/effects/picker/pickerStagger.js +31 -0
- package/dist/forms/effects/picker/pickerStagger.schema.d.ts +1 -0
- package/dist/forms/effects/picker/pickerStagger.schema.js +10 -0
- package/dist/forms/effects/radio/radioCard.d.ts +10 -0
- package/dist/forms/effects/radio/radioCard.js +15 -0
- package/dist/forms/effects/radio/radioCard.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioCard.schema.js +9 -0
- package/dist/forms/effects/radio/radioCircle.d.ts +9 -0
- package/dist/forms/effects/radio/radioCircle.js +49 -0
- package/dist/forms/effects/radio/radioCircle.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioCircle.schema.js +10 -0
- package/dist/forms/effects/radio/radioDot.d.ts +7 -0
- package/dist/forms/effects/radio/radioDot.js +10 -0
- package/dist/forms/effects/radio/radioDot.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioDot.schema.js +9 -0
- package/dist/forms/effects/radio/radioEmoji.d.ts +9 -0
- package/dist/forms/effects/radio/radioEmoji.js +94 -0
- package/dist/forms/effects/radio/radioEmoji.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioEmoji.schema.js +10 -0
- package/dist/forms/effects/radio/radioPulse.d.ts +9 -0
- package/dist/forms/effects/radio/radioPulse.js +24 -0
- package/dist/forms/effects/radio/radioPulse.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioPulse.schema.js +10 -0
- package/dist/forms/effects/radio/radioScale.d.ts +9 -0
- package/dist/forms/effects/radio/radioScale.js +73 -0
- package/dist/forms/effects/radio/radioScale.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioScale.schema.js +10 -0
- package/dist/forms/effects/radio/radioSegment.d.ts +7 -0
- package/dist/forms/effects/radio/radioSegment.js +52 -0
- package/dist/forms/effects/radio/radioSegment.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioSegment.schema.js +9 -0
- package/dist/forms/effects/radio/radioStars.d.ts +15 -0
- package/dist/forms/effects/radio/radioStars.js +125 -0
- package/dist/forms/effects/radio/radioStars.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioStars.schema.js +13 -0
- package/dist/forms/effects/radio/radioTravel.d.ts +9 -0
- package/dist/forms/effects/radio/radioTravel.js +49 -0
- package/dist/forms/effects/radio/radioTravel.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioTravel.schema.js +10 -0
- package/dist/forms/effects/radio/radioUnderline.d.ts +9 -0
- package/dist/forms/effects/radio/radioUnderline.js +61 -0
- package/dist/forms/effects/radio/radioUnderline.schema.d.ts +1 -0
- package/dist/forms/effects/radio/radioUnderline.schema.js +10 -0
- package/dist/forms/effects/radio/shape.d.ts +13 -0
- package/dist/forms/effects/radio/shape.js +61 -0
- package/dist/forms/effects/range/rangeBubble.d.ts +9 -0
- package/dist/forms/effects/range/rangeBubble.js +54 -0
- package/dist/forms/effects/range/rangeBubble.schema.d.ts +1 -0
- package/dist/forms/effects/range/rangeBubble.schema.js +10 -0
- package/dist/forms/effects/range/rangeElastic.d.ts +9 -0
- package/dist/forms/effects/range/rangeElastic.js +101 -0
- package/dist/forms/effects/range/rangeElastic.schema.d.ts +1 -0
- package/dist/forms/effects/range/rangeElastic.schema.js +10 -0
- package/dist/forms/effects/range/rangeFace.d.ts +11 -0
- package/dist/forms/effects/range/rangeFace.js +68 -0
- package/dist/forms/effects/range/rangeFace.schema.d.ts +1 -0
- package/dist/forms/effects/range/rangeFace.schema.js +11 -0
- package/dist/forms/effects/range/rangeFill.d.ts +7 -0
- package/dist/forms/effects/range/rangeFill.js +29 -0
- package/dist/forms/effects/range/rangeFill.schema.d.ts +1 -0
- package/dist/forms/effects/range/rangeFill.schema.js +9 -0
- package/dist/forms/effects/range/rangeHistogram.d.ts +14 -0
- package/dist/forms/effects/range/rangeHistogram.js +65 -0
- package/dist/forms/effects/range/rangeHistogram.schema.d.ts +1 -0
- package/dist/forms/effects/range/rangeHistogram.schema.js +11 -0
- package/dist/forms/effects/range/rangeThumb.d.ts +9 -0
- package/dist/forms/effects/range/rangeThumb.js +39 -0
- package/dist/forms/effects/range/rangeThumb.schema.d.ts +1 -0
- package/dist/forms/effects/range/rangeThumb.schema.js +10 -0
- package/dist/forms/effects/range/rangeTicks.d.ts +9 -0
- package/dist/forms/effects/range/rangeTicks.js +44 -0
- package/dist/forms/effects/range/rangeTicks.schema.d.ts +1 -0
- package/dist/forms/effects/range/rangeTicks.schema.js +18 -0
- package/dist/forms/effects/ring.d.ts +5 -0
- package/dist/forms/effects/ring.js +12 -0
- package/dist/forms/effects/step/stepProgress.d.ts +9 -0
- package/dist/forms/effects/step/stepProgress.js +35 -0
- package/dist/forms/effects/step/stepProgress.schema.d.ts +1 -0
- package/dist/forms/effects/step/stepProgress.schema.js +10 -0
- package/dist/forms/effects/step/stepSlide.d.ts +9 -0
- package/dist/forms/effects/step/stepSlide.js +50 -0
- package/dist/forms/effects/step/stepSlide.schema.d.ts +1 -0
- package/dist/forms/effects/step/stepSlide.schema.js +10 -0
- package/dist/forms/effects/switch/switchBulb.d.ts +9 -0
- package/dist/forms/effects/switch/switchBulb.js +70 -0
- package/dist/forms/effects/switch/switchBulb.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchBulb.schema.js +10 -0
- package/dist/forms/effects/switch/switchDayNight.d.ts +15 -0
- package/dist/forms/effects/switch/switchDayNight.js +96 -0
- package/dist/forms/effects/switch/switchDayNight.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchDayNight.schema.js +13 -0
- package/dist/forms/effects/switch/switchFace.d.ts +7 -0
- package/dist/forms/effects/switch/switchFace.js +75 -0
- package/dist/forms/effects/switch/switchFace.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchFace.schema.js +9 -0
- package/dist/forms/effects/switch/switchFlip.d.ts +7 -0
- package/dist/forms/effects/switch/switchFlip.js +45 -0
- package/dist/forms/effects/switch/switchFlip.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchFlip.schema.js +9 -0
- package/dist/forms/effects/switch/switchGoo.d.ts +7 -0
- package/dist/forms/effects/switch/switchGoo.js +95 -0
- package/dist/forms/effects/switch/switchGoo.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchGoo.schema.js +9 -0
- package/dist/forms/effects/switch/switchIcon.d.ts +9 -0
- package/dist/forms/effects/switch/switchIcon.js +63 -0
- package/dist/forms/effects/switch/switchIcon.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchIcon.schema.js +10 -0
- package/dist/forms/effects/switch/switchLabels.d.ts +11 -0
- package/dist/forms/effects/switch/switchLabels.js +55 -0
- package/dist/forms/effects/switch/switchLabels.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchLabels.schema.js +11 -0
- package/dist/forms/effects/switch/switchMorph.d.ts +9 -0
- package/dist/forms/effects/switch/switchMorph.js +22 -0
- package/dist/forms/effects/switch/switchMorph.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchMorph.schema.js +10 -0
- package/dist/forms/effects/switch/switchNeon.d.ts +11 -0
- package/dist/forms/effects/switch/switchNeon.js +53 -0
- package/dist/forms/effects/switch/switchNeon.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchNeon.schema.js +11 -0
- package/dist/forms/effects/switch/switchSlide.d.ts +7 -0
- package/dist/forms/effects/switch/switchSlide.js +11 -0
- package/dist/forms/effects/switch/switchSlide.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchSlide.schema.js +9 -0
- package/dist/forms/effects/switch/switchStretch.d.ts +9 -0
- package/dist/forms/effects/switch/switchStretch.js +21 -0
- package/dist/forms/effects/switch/switchStretch.schema.d.ts +1 -0
- package/dist/forms/effects/switch/switchStretch.schema.js +10 -0
- package/dist/forms/effects/valid/validAsync.d.ts +7 -0
- package/dist/forms/effects/valid/validAsync.js +36 -0
- package/dist/forms/effects/valid/validAsync.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validAsync.schema.js +9 -0
- package/dist/forms/effects/valid/validBubble.d.ts +7 -0
- package/dist/forms/effects/valid/validBubble.js +61 -0
- package/dist/forms/effects/valid/validBubble.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validBubble.schema.js +9 -0
- package/dist/forms/effects/valid/validCheck.d.ts +9 -0
- package/dist/forms/effects/valid/validCheck.js +50 -0
- package/dist/forms/effects/valid/validCheck.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validCheck.schema.js +10 -0
- package/dist/forms/effects/valid/validColor.d.ts +11 -0
- package/dist/forms/effects/valid/validColor.js +28 -0
- package/dist/forms/effects/valid/validColor.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validColor.schema.js +11 -0
- package/dist/forms/effects/valid/validFlash.d.ts +9 -0
- package/dist/forms/effects/valid/validFlash.js +36 -0
- package/dist/forms/effects/valid/validFlash.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validFlash.schema.js +10 -0
- package/dist/forms/effects/valid/validMessage.d.ts +7 -0
- package/dist/forms/effects/valid/validMessage.js +24 -0
- package/dist/forms/effects/valid/validMessage.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validMessage.schema.js +9 -0
- package/dist/forms/effects/valid/validShake.d.ts +9 -0
- package/dist/forms/effects/valid/validShake.js +37 -0
- package/dist/forms/effects/valid/validShake.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validShake.schema.js +10 -0
- package/dist/forms/effects/valid/validSwap.d.ts +7 -0
- package/dist/forms/effects/valid/validSwap.js +33 -0
- package/dist/forms/effects/valid/validSwap.schema.d.ts +1 -0
- package/dist/forms/effects/valid/validSwap.schema.js +9 -0
- package/dist/forms/errors.d.ts +12 -0
- package/dist/forms/errors.js +20 -0
- package/dist/forms/file.d.ts +1 -0
- package/dist/forms/file.js +268 -0
- package/dist/forms/files.d.ts +7 -0
- package/dist/forms/files.js +29 -0
- package/dist/forms/formSteps.d.ts +9 -0
- package/dist/forms/formSteps.js +6 -0
- package/dist/forms/ids.d.ts +17 -0
- package/dist/forms/ids.js +13 -0
- package/dist/forms/inline.d.ts +3 -0
- package/dist/forms/inline.js +128 -0
- package/dist/forms/number.d.ts +1 -0
- package/dist/forms/number.js +67 -0
- package/dist/forms/otp.d.ts +4 -0
- package/dist/forms/otp.js +171 -0
- package/dist/forms/picker.d.ts +1 -0
- package/dist/forms/picker.js +303 -0
- package/dist/forms/range.d.ts +3 -0
- package/dist/forms/range.js +223 -0
- package/dist/forms/registry.d.ts +4 -0
- package/dist/forms/registry.js +133 -0
- package/dist/forms/reveal.d.ts +6 -0
- package/dist/forms/reveal.js +51 -0
- package/dist/forms/runtime/focus.d.ts +16 -0
- package/dist/forms/runtime/focus.js +55 -0
- package/dist/forms/runtime/listMatch.d.ts +2 -0
- package/dist/forms/runtime/listMatch.js +33 -0
- package/dist/forms/runtime/origin.d.ts +10 -0
- package/dist/forms/runtime/origin.js +25 -0
- package/dist/forms/runtime/placePopup.d.ts +10 -0
- package/dist/forms/runtime/placePopup.js +55 -0
- package/dist/forms/runtime/useFlip.d.ts +7 -0
- package/dist/forms/runtime/useFlip.js +38 -0
- package/dist/forms/runtime/useMorph.d.ts +8 -0
- package/dist/forms/runtime/useMorph.js +56 -0
- package/dist/forms/runtime/useOffScreen.d.ts +3 -0
- package/dist/forms/runtime/useOffScreen.js +16 -0
- package/dist/forms/runtime/useSwitchDrag.d.ts +8 -0
- package/dist/forms/runtime/useSwitchDrag.js +83 -0
- package/dist/forms/runtime/vanish.d.ts +6 -0
- package/dist/forms/runtime/vanish.js +185 -0
- package/dist/forms/steps.d.ts +1 -0
- package/dist/forms/steps.js +149 -0
- package/dist/forms/swap.d.ts +7 -0
- package/dist/forms/swap.js +149 -0
- package/dist/forms/switch.d.ts +1 -0
- package/dist/forms/switch.js +99 -0
- package/dist/forms/tools.d.ts +3 -0
- package/dist/forms/tools.js +248 -0
- package/dist/forms/types.d.ts +161 -0
- package/dist/forms/types.js +5 -0
- package/dist/index.d.ts +234 -0
- package/dist/index.js +119 -0
- package/dist/schema/forms.d.ts +3 -0
- package/dist/schema/forms.js +194 -0
- package/dist/schema/index.d.ts +2 -1
- package/dist/schema/index.js +3 -1
- package/dist/schema/types.d.ts +2 -2
- package/dist/schema/types.js +1 -0
- package/guides/README.md +62 -61
- package/guides/contact.md +100 -77
- package/guides/forms.md +105 -0
- package/package.json +1 -1
- package/styles/form.css +594 -0
- package/theme.css +6 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef, type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { FormEffect } from "../types.js";
|
|
3
|
+
/** One chip. */
|
|
4
|
+
export interface ChipOption {
|
|
5
|
+
readonly value: string;
|
|
6
|
+
readonly label: ReactNode;
|
|
7
|
+
readonly disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface ChipGroupOwnProps {
|
|
10
|
+
/** The group's question: a real `<legend>`. */
|
|
11
|
+
readonly label?: ReactNode;
|
|
12
|
+
readonly description?: ReactNode;
|
|
13
|
+
/** The error message. While set, every chip is `aria-invalid` and the group points at the message. */
|
|
14
|
+
readonly error?: ReactNode;
|
|
15
|
+
readonly invalid?: boolean;
|
|
16
|
+
readonly options: readonly ChipOption[];
|
|
17
|
+
/** Several chips on at once (checkboxes), or one (radios: the arrow keys move between them). Default `true`. */
|
|
18
|
+
readonly multiple?: boolean;
|
|
19
|
+
/** The values that are on, when you hold them (with `onValueChange`). */
|
|
20
|
+
readonly value?: readonly string[];
|
|
21
|
+
readonly defaultValue?: readonly string[];
|
|
22
|
+
/** Called with the values that are on after each change. */
|
|
23
|
+
readonly onValueChange?: (values: string[]) => void;
|
|
24
|
+
/** `chipCheck`, `chipMerge`: they stack. */
|
|
25
|
+
readonly effect?: FormEffect | readonly FormEffect[];
|
|
26
|
+
readonly unstyled?: boolean;
|
|
27
|
+
/** Goes on the chips' row. */
|
|
28
|
+
readonly className?: string;
|
|
29
|
+
/** Goes on the `<fieldset>`. */
|
|
30
|
+
readonly rootClassName?: string;
|
|
31
|
+
/** Goes on the `<legend>`. */
|
|
32
|
+
readonly labelClassName?: string;
|
|
33
|
+
readonly style?: CSSProperties;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Every other prop goes on each chip's input, `ref` included: `name`, `onChange`, `onBlur`, `required`, `disabled`, so
|
|
37
|
+
* `{...register("filters")}` spreads onto all of them (React Hook Form collects checkboxes as a list, radios as one).
|
|
38
|
+
*/
|
|
39
|
+
export type ChipGroupProps = ChipGroupOwnProps & Omit<ComponentPropsWithRef<"input">, keyof ChipGroupOwnProps | "type" | "checked" | "defaultChecked">;
|
|
40
|
+
/**
|
|
41
|
+
* Filter chips in a real `<fieldset>`: each chip is a real checkbox (or, with `multiple={false}`, a radio) lying over
|
|
42
|
+
* the whole chip, so a click, Space, the arrow keys of a radio group and a plain form submit all work as the browser's
|
|
43
|
+
* own. A chip that is on shows a check before its words, never colour alone.
|
|
44
|
+
*
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <ChipGroup label="Diet" options={diets} effect={[chipCheck(), chipMerge()]} {...register("diet")} />
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function ChipGroup(props: ChipGroupProps): import("react").JSX.Element;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef } from "react";
|
|
4
|
+
import { warnOnce } from "../../buttons/runtime/dev.js";
|
|
5
|
+
import { CHIPS_CSS } from "../chips.js";
|
|
6
|
+
import { fieldDescribedBy, fieldIds } from "../ids.js";
|
|
7
|
+
import { EffectSheets } from "./choiceParts.js";
|
|
8
|
+
import { effectList, filled, withUid } from "./shared.js";
|
|
9
|
+
const OPTION = "[data-sx-chip-control]";
|
|
10
|
+
/**
|
|
11
|
+
* Filter chips in a real `<fieldset>`: each chip is a real checkbox (or, with `multiple={false}`, a radio) lying over
|
|
12
|
+
* the whole chip, so a click, Space, the arrow keys of a radio group and a plain form submit all work as the browser's
|
|
13
|
+
* own. A chip that is on shows a check before its words, never colour alone.
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <ChipGroup label="Diet" options={diets} effect={[chipCheck(), chipMerge()]} {...register("diet")} />
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function ChipGroup(props) {
|
|
20
|
+
const { label, description, error, invalid, options, multiple = true, value, defaultValue, onValueChange, effect, unstyled, className, rootClassName, labelClassName, style, id: ownId, name: ownName, ...rest } = props;
|
|
21
|
+
const effects = effectList(effect);
|
|
22
|
+
const auto = useId().replace(/[^\w-]/g, "");
|
|
23
|
+
const idBase = ownId ?? `sx-chips${auto}`;
|
|
24
|
+
const uid = `sx${auto}`;
|
|
25
|
+
const ids = fieldIds(idBase);
|
|
26
|
+
// radios need one name to be one group
|
|
27
|
+
const name = ownName ?? (multiple ? undefined : idBase);
|
|
28
|
+
const hasError = filled(error);
|
|
29
|
+
const isInvalid = hasError || invalid === true;
|
|
30
|
+
const describedBy = fieldDescribedBy(ids, { error: hasError, description: filled(description), own: rest["aria-describedby"] });
|
|
31
|
+
const { "aria-describedby": _own, onChange: ownChange, ...shared } = rest;
|
|
32
|
+
if (process.env.NODE_ENV !== "production") {
|
|
33
|
+
for (const e of effects)
|
|
34
|
+
if (!e.fits.includes("chip"))
|
|
35
|
+
warnOnce(`the effect ${e.id} is not made for a ChipGroup: it fits ${e.fits.join(", ")}.`);
|
|
36
|
+
if (!filled(label) && !rest["aria-label"] && !rest["aria-labelledby"])
|
|
37
|
+
warnOnce("a ChipGroup has no label: give it `label` (its legend).");
|
|
38
|
+
}
|
|
39
|
+
// the chips' state is the inputs' own (CSS reads :checked), so a form reset needs nothing from here
|
|
40
|
+
const controlled = value !== undefined;
|
|
41
|
+
const root = useRef(null);
|
|
42
|
+
const read = () => [...(root.current?.querySelectorAll(OPTION) ?? [])].filter((i) => i.checked).map((i) => i.value);
|
|
43
|
+
const changed = (e) => {
|
|
44
|
+
ownChange?.(e);
|
|
45
|
+
if (!onValueChange)
|
|
46
|
+
return;
|
|
47
|
+
if (!controlled)
|
|
48
|
+
return onValueChange(read());
|
|
49
|
+
const v = e.target.value;
|
|
50
|
+
onValueChange(!multiple ? [v] : e.target.checked ? [...value.filter((x) => x !== v), v] : value.filter((x) => x !== v));
|
|
51
|
+
};
|
|
52
|
+
const vars = { ...withUid(Object.assign({}, ...effects.map((e) => e.vars)), uid), ...style };
|
|
53
|
+
return (_jsxs(_Fragment, { children: [_jsx(EffectSheets, { name: "form-chips", base: CHIPS_CSS, effects: effects }), _jsxs("fieldset", { ref: root, id: idBase, "data-sx-chips": effects.map((e) => e.id).join(" "), "data-sx-kind": multiple ? "checkbox" : "radio", "data-sx-styled": unstyled ? undefined : "", "aria-describedby": describedBy, className: rootClassName, style: vars, children: [filled(label) ? (_jsx("legend", { "data-sx-chips-legend": "", className: labelClassName, children: label })) : null, filled(description) ? (_jsx("p", { "data-sx-chips-description": "", id: ids.description, children: description })) : null, _jsx("div", { "data-sx-chips-options": "", className: className, children: options.map((o, i) => (_jsxs("label", { "data-sx-chip-option": "", style: { "--sx-chip-i": i }, children: [effects.flatMap((e) => e.layers
|
|
54
|
+
.filter((l) => l.slot === "row")
|
|
55
|
+
.map((l, k) => (_jsx("span", { "data-sx-chip-layer": e.id, "data-sx-chip-part": l.part, "data-sx-chip-keep": l.keep ? "" : undefined, "aria-hidden": true }, `${e.id}-${k}`)))), _jsx("input", { ...shared, type: multiple ? "checkbox" : "radio", name: name, value: o.value, id: `${idBase}-${i}`, "data-sx-chip-control": "", disabled: o.disabled || shared.disabled, ...(controlled ? { checked: value.includes(o.value) } : { defaultChecked: defaultValue?.includes(o.value) }), onChange: changed, "aria-invalid": isInvalid || undefined }), _jsx("span", { "data-sx-chip-check": "", "aria-hidden": true, children: _jsx("svg", { viewBox: "0 0 24 24", focusable: "false", children: _jsx("path", { d: "m5 12.5 4.5 4.5L19 7.5", pathLength: 1 }) }) }), _jsx("span", { "data-sx-chip-text": "", children: o.label })] }, o.value))) }), _jsx("div", { "data-sx-chips-message": "", children: _jsx("p", { "data-sx-chips-error": "", id: ids.error, role: "alert", children: hasError ? (_jsx("span", { "data-sx-chips-says": "", children: error }, typeof error === "string" ? error : undefined)) : null }) })] })] }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type CSSProperties, type FocusEvent, type ReactNode, type Ref } from "react";
|
|
2
|
+
import type { FormEffect } from "../types.js";
|
|
3
|
+
/** One choice of a Combobox. */
|
|
4
|
+
export interface ComboboxOption {
|
|
5
|
+
readonly value: string;
|
|
6
|
+
/** What the option shows: words, or anything with `text` beside it. */
|
|
7
|
+
readonly label: ReactNode;
|
|
8
|
+
/** Its words, to filter by and to show in the box once chosen. Needed when `label` is not plain text. */
|
|
9
|
+
readonly text?: string;
|
|
10
|
+
readonly disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** The words a Combobox says, for your language. `{n}` is a number, `{query}` the typed text, `{label}` an option's words. */
|
|
13
|
+
export interface ComboboxLabels {
|
|
14
|
+
readonly empty?: string;
|
|
15
|
+
readonly loading?: string;
|
|
16
|
+
readonly results?: string;
|
|
17
|
+
readonly create?: string;
|
|
18
|
+
readonly remove?: string;
|
|
19
|
+
readonly open?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ComboboxProps {
|
|
22
|
+
/** The visible label: a real `<label for>` on the text box. */
|
|
23
|
+
readonly label?: ReactNode;
|
|
24
|
+
readonly description?: ReactNode;
|
|
25
|
+
/** The error message, with `aria-invalid` and a `role="alert"` region. */
|
|
26
|
+
readonly error?: ReactNode;
|
|
27
|
+
readonly invalid?: boolean;
|
|
28
|
+
readonly options: readonly ComboboxOption[];
|
|
29
|
+
/** Several values, shown as chips; Backspace in the empty box removes the last. */
|
|
30
|
+
readonly multiple?: boolean;
|
|
31
|
+
/** The chosen value (a list with `multiple`), when you hold it. `null` or `[]`: nothing. */
|
|
32
|
+
readonly value?: string | readonly string[] | null;
|
|
33
|
+
readonly defaultValue?: string | readonly string[];
|
|
34
|
+
/** Called with the new value (a list with `multiple`): fits React Hook Form's `Controller` as it is. */
|
|
35
|
+
readonly onChange?: (value: string | string[] | null) => void;
|
|
36
|
+
readonly onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
|
37
|
+
/** Called as the user types: load matching options here for a search that runs on a server. */
|
|
38
|
+
readonly onInputChange?: (query: string) => void;
|
|
39
|
+
/** `false`: show the options as given (yours are already filtered). Default: the words contain the typed text. */
|
|
40
|
+
readonly filter?: false | ((option: ComboboxOption, query: string) => boolean);
|
|
41
|
+
/** Typed words that match no option can be added as a value of their own. */
|
|
42
|
+
readonly creatable?: boolean;
|
|
43
|
+
/** Options are on their way: the list says so. */
|
|
44
|
+
readonly loading?: boolean;
|
|
45
|
+
/** Submitted with a plain form: one hidden input per value. */
|
|
46
|
+
readonly name?: string;
|
|
47
|
+
readonly placeholder?: string;
|
|
48
|
+
readonly disabled?: boolean;
|
|
49
|
+
readonly required?: boolean;
|
|
50
|
+
readonly autoFocus?: boolean;
|
|
51
|
+
/** The text box: React Hook Form's `field.ref` goes here, so it can focus a wrong field. */
|
|
52
|
+
readonly ref?: Ref<HTMLInputElement>;
|
|
53
|
+
readonly id?: string;
|
|
54
|
+
readonly effect?: FormEffect | readonly FormEffect[];
|
|
55
|
+
readonly labels?: ComboboxLabels;
|
|
56
|
+
readonly unstyled?: boolean;
|
|
57
|
+
/** Goes on the box you see. */
|
|
58
|
+
readonly className?: string;
|
|
59
|
+
readonly rootClassName?: string;
|
|
60
|
+
readonly labelClassName?: string;
|
|
61
|
+
readonly style?: CSSProperties;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Our own combobox: a text box that filters a list of options as you type (the ARIA "combobox with listbox popup"
|
|
65
|
+
* pattern). The focus stays in the box; ↓ / ↑ move through the list, Enter picks, Esc closes, Alt+↓ opens. `multiple`
|
|
66
|
+
* shows the picks as chips, `creatable` adds typed words, `loading` covers a search that runs on a server. The list opens
|
|
67
|
+
* in the top layer, tied to the box by anchor positioning, flipped above when the screen has no room below.
|
|
68
|
+
*
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <Controller name="fruit" control={control} render={({ field }) => (
|
|
71
|
+
* <Combobox label="Fruit" options={fruits} effect={[pickerOpen(), listHighlight()]} {...field} />
|
|
72
|
+
* )} />
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function Combobox(props: ComboboxProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef, useState, } from "react";
|
|
4
|
+
import { fieldDescribedBy, fieldIds } from "../ids.js";
|
|
5
|
+
import { PICKER_BASE_CSS } from "../picker.js";
|
|
6
|
+
import { markMatches } from "../runtime/listMatch.js";
|
|
7
|
+
import { dockSheet, placeByHand } from "../runtime/placePopup.js";
|
|
8
|
+
import { useFlip } from "../runtime/useFlip.js";
|
|
9
|
+
import { EffectSheets, choiceLayers } from "./choiceParts.js";
|
|
10
|
+
import { checkPicker } from "./pickerChecks.js";
|
|
11
|
+
import { effectList, filled, withUid } from "./shared.js";
|
|
12
|
+
const WORDS = {
|
|
13
|
+
empty: "No results",
|
|
14
|
+
loading: "Loading…",
|
|
15
|
+
results: "{n} results",
|
|
16
|
+
create: "Add “{query}”",
|
|
17
|
+
remove: "Remove {label}",
|
|
18
|
+
open: "Show options",
|
|
19
|
+
};
|
|
20
|
+
const textOf = (o) => o.text ?? (typeof o.label === "string" ? o.label : o.value);
|
|
21
|
+
const asList = (v) => (v === null || v === undefined || v === "" ? [] : typeof v === "string" ? [v] : [...v]);
|
|
22
|
+
const contains = (o, query) => textOf(o).toLowerCase().includes(query.trim().toLowerCase());
|
|
23
|
+
/**
|
|
24
|
+
* Our own combobox: a text box that filters a list of options as you type (the ARIA "combobox with listbox popup"
|
|
25
|
+
* pattern). The focus stays in the box; ↓ / ↑ move through the list, Enter picks, Esc closes, Alt+↓ opens. `multiple`
|
|
26
|
+
* shows the picks as chips, `creatable` adds typed words, `loading` covers a search that runs on a server. The list opens
|
|
27
|
+
* in the top layer, tied to the box by anchor positioning, flipped above when the screen has no room below.
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <Controller name="fruit" control={control} render={({ field }) => (
|
|
31
|
+
* <Combobox label="Fruit" options={fruits} effect={[pickerOpen(), listHighlight()]} {...field} />
|
|
32
|
+
* )} />
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function Combobox(props) {
|
|
36
|
+
const { label, description, error, invalid, options, multiple, value, defaultValue, onChange, onBlur, onInputChange, filter, creatable, loading, name, placeholder, disabled, required, autoFocus, ref, id: ownId, effect, labels, unstyled, className, rootClassName, labelClassName, style, } = props;
|
|
37
|
+
const effects = effectList(effect);
|
|
38
|
+
const words = { ...WORDS, ...labels };
|
|
39
|
+
const auto = useId().replace(/[^\w-]/g, "");
|
|
40
|
+
const id = ownId ?? `sx-combo${auto}`;
|
|
41
|
+
const uid = `sx${auto}`;
|
|
42
|
+
const ids = fieldIds(id);
|
|
43
|
+
const listId = `${id}-list`;
|
|
44
|
+
const hasError = filled(error);
|
|
45
|
+
const isInvalid = hasError || invalid === true;
|
|
46
|
+
const describedBy = fieldDescribedBy(ids, { error: hasError, description: filled(description) });
|
|
47
|
+
if (process.env.NODE_ENV !== "production") {
|
|
48
|
+
checkPicker({
|
|
49
|
+
control: "Combobox",
|
|
50
|
+
effects,
|
|
51
|
+
labelled: filled(label),
|
|
52
|
+
count: options.length,
|
|
53
|
+
wordless: options.filter((o) => typeof o.label !== "string" && o.text === undefined).length,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const controlled = value !== undefined;
|
|
57
|
+
const [held, setHeld] = useState(() => asList(defaultValue));
|
|
58
|
+
const chosen = controlled ? asList(value) : held;
|
|
59
|
+
const [made, setMade] = useState([]);
|
|
60
|
+
const all = [...options, ...made.filter((m) => !options.some((o) => o.value === m.value))];
|
|
61
|
+
const byValue = (v) => all.find((o) => o.value === v);
|
|
62
|
+
const [open, setOpen] = useState(false);
|
|
63
|
+
const [query, setQuery] = useState("");
|
|
64
|
+
/** Single mode: the box shows the typed words while typing, the chosen option's words otherwise. */
|
|
65
|
+
const [typing, setTyping] = useState(false);
|
|
66
|
+
const [active, setActive] = useState(-1);
|
|
67
|
+
const [leaving, setLeaving] = useState([]);
|
|
68
|
+
const [said, setSaid] = useState("");
|
|
69
|
+
const keep = typing || multiple;
|
|
70
|
+
const shown = filter === false || !keep || !query.trim() ? all : all.filter((o) => (filter ? filter(o, query) : contains(o, query)));
|
|
71
|
+
const fresh = creatable && query.trim() !== "" && !all.some((o) => textOf(o).toLowerCase() === query.trim().toLowerCase());
|
|
72
|
+
const rows = fresh ? [...shown, { value: query.trim(), label: words.create.replace("{query}", query.trim()), text: query.trim() }] : shown;
|
|
73
|
+
const boxText = multiple || typing ? query : chosen[0] !== undefined ? (byValue(chosen[0]) ? textOf(byValue(chosen[0])) : chosen[0]) : "";
|
|
74
|
+
const root = useRef(null);
|
|
75
|
+
const box = useRef(null);
|
|
76
|
+
const popup = useRef(null);
|
|
77
|
+
const list = useRef(null);
|
|
78
|
+
const input = useRef(null);
|
|
79
|
+
const marks = useRef([]);
|
|
80
|
+
const optionId = (i) => `${id}-option-${i}`;
|
|
81
|
+
const flips = effects.some((e) => e.needs.flip);
|
|
82
|
+
useFlip(box, flips, "[data-sx-chip]");
|
|
83
|
+
const set = (next) => {
|
|
84
|
+
if (!controlled)
|
|
85
|
+
setHeld(next);
|
|
86
|
+
onChange?.(multiple ? next : (next[0] ?? null));
|
|
87
|
+
};
|
|
88
|
+
const remove = (v) => {
|
|
89
|
+
const gone = byValue(v);
|
|
90
|
+
if (flips && gone)
|
|
91
|
+
setLeaving((l) => [...l, gone]);
|
|
92
|
+
set(chosen.filter((x) => x !== v));
|
|
93
|
+
};
|
|
94
|
+
const pick = (i) => {
|
|
95
|
+
const row = rows[i];
|
|
96
|
+
if (!row || row.disabled)
|
|
97
|
+
return;
|
|
98
|
+
if (fresh && i === rows.length - 1)
|
|
99
|
+
setMade((m) => [...m, { value: row.value, label: row.value, text: row.value }]);
|
|
100
|
+
if (multiple) {
|
|
101
|
+
if (chosen.includes(row.value))
|
|
102
|
+
remove(row.value);
|
|
103
|
+
else
|
|
104
|
+
set([...chosen, row.value]);
|
|
105
|
+
setQuery("");
|
|
106
|
+
onInputChange?.("");
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
set([row.value]);
|
|
110
|
+
setOpen(false);
|
|
111
|
+
setTyping(false);
|
|
112
|
+
setQuery("");
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const openAt = (where) => {
|
|
116
|
+
setOpen(true);
|
|
117
|
+
const at = where === "none" ? -1 : where === "last" ? rows.length - 1 : Math.max(0, rows.findIndex((r) => chosen.includes(r.value)));
|
|
118
|
+
setActive(rows.length === 0 ? -1 : at);
|
|
119
|
+
};
|
|
120
|
+
const keys = (e) => {
|
|
121
|
+
if (e.key === "ArrowDown") {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
if (!open)
|
|
124
|
+
openAt(e.altKey ? "none" : "first");
|
|
125
|
+
else
|
|
126
|
+
setActive((a) => Math.min(rows.length - 1, a + 1));
|
|
127
|
+
}
|
|
128
|
+
else if (e.key === "ArrowUp") {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
if (!open)
|
|
131
|
+
openAt("last");
|
|
132
|
+
else
|
|
133
|
+
setActive((a) => Math.max(0, a - 1));
|
|
134
|
+
}
|
|
135
|
+
else if (e.key === "Enter") {
|
|
136
|
+
if (open && active >= 0) {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
pick(active);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (e.key === "Escape") {
|
|
142
|
+
if (open) {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
setOpen(false);
|
|
145
|
+
}
|
|
146
|
+
else if (typing || query) {
|
|
147
|
+
setTyping(false);
|
|
148
|
+
setQuery("");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if (e.key === "Backspace" && multiple && query === "" && chosen.length > 0) {
|
|
152
|
+
remove(chosen[chosen.length - 1]);
|
|
153
|
+
}
|
|
154
|
+
else if (e.key === "Tab") {
|
|
155
|
+
setOpen(false);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const typed = (e) => {
|
|
159
|
+
const next = e.target.value;
|
|
160
|
+
setQuery(next);
|
|
161
|
+
setTyping(true);
|
|
162
|
+
setOpen(true);
|
|
163
|
+
setActive(next.trim() ? 0 : -1);
|
|
164
|
+
onInputChange?.(next);
|
|
165
|
+
};
|
|
166
|
+
const left = (e) => {
|
|
167
|
+
setOpen(false);
|
|
168
|
+
setTyping(false);
|
|
169
|
+
if (!multiple)
|
|
170
|
+
setQuery("");
|
|
171
|
+
onBlur?.(e);
|
|
172
|
+
};
|
|
173
|
+
// the popup: shown and hidden as the top layer's own, placed by hand only where anchor positioning is missing
|
|
174
|
+
const docks = effects.some((e) => e.needs.sheet);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
const el = popup.current;
|
|
177
|
+
if (!el || !box.current || typeof el.showPopover !== "function")
|
|
178
|
+
return;
|
|
179
|
+
if (open && !el.matches(":popover-open"))
|
|
180
|
+
el.showPopover();
|
|
181
|
+
if (!open && el.matches(":popover-open"))
|
|
182
|
+
el.hidePopover();
|
|
183
|
+
if (!open)
|
|
184
|
+
return;
|
|
185
|
+
const stops = [placeByHand(el, box.current), docks ? dockSheet(el, box.current) : () => { }];
|
|
186
|
+
return () => stops.forEach((stop) => stop());
|
|
187
|
+
}, [open, docks]);
|
|
188
|
+
// keep the active option in view
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (open && active >= 0)
|
|
191
|
+
document.getElementById(optionId(active))?.scrollIntoView({ block: "nearest" });
|
|
192
|
+
}, [open, active]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
193
|
+
// mark the typed words in the options (listMatch)
|
|
194
|
+
const marking = effects.some((e) => e.needs.match);
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (!marking)
|
|
197
|
+
return;
|
|
198
|
+
marks.current = markMatches(list.current, open && keep ? query : "", marks.current);
|
|
199
|
+
});
|
|
200
|
+
useEffect(() => () => void markMatches(null, "", marks.current), []);
|
|
201
|
+
// read out how many results there are once the typing stops
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (!open || !keep || !query.trim())
|
|
204
|
+
return;
|
|
205
|
+
const t = setTimeout(() => setSaid(loading ? words.loading : words.results.replace("{n}", String(shown.length))), 500);
|
|
206
|
+
return () => clearTimeout(t);
|
|
207
|
+
}, [open, keep, query, shown.length, loading, words.loading, words.results]);
|
|
208
|
+
// chips leaving: gone once their animation ends (at once when nothing animates them)
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
for (const chip of box.current?.querySelectorAll("[data-sx-leaving]") ?? []) {
|
|
211
|
+
const key = chip.dataset.key;
|
|
212
|
+
const drop = () => setLeaving((l) => l.filter((o) => o.value !== key));
|
|
213
|
+
if (getComputedStyle(chip).animationName === "none")
|
|
214
|
+
drop();
|
|
215
|
+
else
|
|
216
|
+
chip.addEventListener("animationend", drop, { once: true });
|
|
217
|
+
}
|
|
218
|
+
}, [leaving]);
|
|
219
|
+
const setRef = (el) => {
|
|
220
|
+
input.current = el;
|
|
221
|
+
if (typeof ref === "function")
|
|
222
|
+
ref(el);
|
|
223
|
+
else if (ref)
|
|
224
|
+
ref.current = el;
|
|
225
|
+
};
|
|
226
|
+
const vars = {
|
|
227
|
+
"--sx-picker-anchor": `--${uid}-box`,
|
|
228
|
+
...withUid(Object.assign({}, ...effects.map((e) => e.vars)), uid),
|
|
229
|
+
...style,
|
|
230
|
+
};
|
|
231
|
+
const chips = multiple
|
|
232
|
+
? [...chosen.map((v) => byValue(v) ?? { value: v, label: v }), ...leaving.filter((o) => !chosen.includes(o.value))]
|
|
233
|
+
: [];
|
|
234
|
+
return (_jsxs(_Fragment, { children: [_jsx(EffectSheets, { name: "form-picker", base: PICKER_BASE_CSS, effects: effects }), _jsxs("div", { ref: root, "data-sx-picker": effects.map((e) => e.id).join(" "), "data-sx-kind": "combobox", "data-sx-styled": unstyled ? undefined : "", "data-sx-open": open ? "" : undefined, className: rootClassName, style: vars, children: [filled(label) ? (_jsx("label", { "data-sx-picker-label": "", id: ids.label, htmlFor: id, className: labelClassName, children: label })) : null, _jsxs("div", { ref: box, "data-sx-picker-box": "", className: className, style: { anchorName: `--${uid}-box` }, children: [chips.map((o) => {
|
|
235
|
+
const gone = !chosen.includes(o.value);
|
|
236
|
+
return (_jsxs("span", { "data-key": o.value, "data-sx-chip": "", "data-sx-leaving": gone ? "" : undefined, children: [textOf(o), _jsx("button", { type: "button", "aria-label": words.remove.replace("{label}", textOf(o)), tabIndex: gone ? -1 : undefined, disabled: disabled, onClick: () => {
|
|
237
|
+
remove(o.value);
|
|
238
|
+
input.current?.focus();
|
|
239
|
+
}, children: _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": true, focusable: "false", children: _jsx("path", { d: "m8 8 8 8M16 8l-8 8" }) }) })] }, o.value));
|
|
240
|
+
}), _jsx("input", { ref: setRef, id: id, type: "text", role: "combobox", "data-sx-picker-control": "", autoComplete: "off", value: boxText, placeholder: chips.length > 0 ? undefined : placeholder, disabled: disabled, autoFocus: autoFocus, "aria-expanded": open, "aria-controls": listId, "aria-autocomplete": "list", "aria-activedescendant": open && active >= 0 && rows[active] ? optionId(active) : undefined, "aria-invalid": isInvalid || undefined, "aria-required": required || undefined, "aria-describedby": describedBy, onChange: typed, onKeyDown: keys, onBlur: left, onClick: () => (open ? undefined : openAt("first")) }), _jsx("button", { type: "button", "data-sx-picker-arrow": "", tabIndex: -1, "aria-label": words.open, "aria-expanded": open, "aria-controls": listId, disabled: disabled, onPointerDown: (e) => e.preventDefault(), onClick: () => {
|
|
241
|
+
if (open)
|
|
242
|
+
setOpen(false);
|
|
243
|
+
else
|
|
244
|
+
openAt("first");
|
|
245
|
+
input.current?.focus();
|
|
246
|
+
}, children: _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": true, focusable: "false", children: _jsx("path", { d: "m6 9 6 6 6-6" }) }) })] }), _jsxs("div", { ref: popup, popover: "manual", "data-sx-picker-popup": "", children: [choiceLayers(effects, "list", uid), _jsxs("ul", { ref: list, id: listId, role: "listbox", "data-sx-picker-list": "", "aria-labelledby": filled(label) ? ids.label : undefined, "aria-multiselectable": multiple || undefined, "aria-busy": loading || undefined, children: [rows.map((o, i) => (_jsxs("li", { id: optionId(i), role: "option", "data-sx-option": "", "data-active": i === active ? "" : undefined, "aria-selected": chosen.includes(o.value) && !(fresh && i === rows.length - 1), "aria-disabled": o.disabled || undefined, style: { "--sx-option-i": i }, onPointerDown: (e) => e.preventDefault(), onPointerMove: () => i !== active && setActive(i), onClick: () => pick(i), children: [_jsx("span", { "data-sx-option-text": "", children: o.label }), _jsx("span", { "data-sx-option-check": "", "aria-hidden": true, children: _jsx("svg", { viewBox: "0 0 24 24", focusable: "false", children: _jsx("path", { d: "m5.5 12.5 4.25 4.25L18.5 7.5", pathLength: 1 }) }) })] }, `${o.value}-${i === rows.length - 1 && fresh ? "new" : "has"}`))), rows.length === 0 ? (_jsx("li", { "data-sx-option-note": "", role: "presentation", children: loading ? words.loading : words.empty })) : null] })] }), name !== undefined
|
|
247
|
+
? (chosen.length > 0 ? chosen : multiple ? [] : [""]).map((v, i) => _jsx("input", { type: "hidden", name: name, value: v }, `${v}-${i}`))
|
|
248
|
+
: null, filled(description) ? (_jsx("p", { "data-sx-picker-description": "", id: ids.description, children: description })) : null, _jsx("span", { "data-sx-picker-sr": "", role: "status", children: said }), _jsx("div", { "data-sx-picker-message": "", children: _jsx("p", { "data-sx-picker-error": "", id: ids.error, role: "alert", children: hasError ? (_jsx("span", { "data-sx-picker-says": "", children: error }, typeof error === "string" ? error : undefined)) : null }) })] })] }));
|
|
249
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type ErrorSummaryItem, type ErrorTree } from "../errors.js";
|
|
3
|
+
export interface ErrorSummaryProps {
|
|
4
|
+
/** The problems, in page order: a list, or React Hook Form's `formState.errors` as it is. Nothing renders while empty. */
|
|
5
|
+
readonly errors: readonly ErrorSummaryItem[] | ErrorTree;
|
|
6
|
+
/** The heading. Default "There is a problem". */
|
|
7
|
+
readonly title?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Changes on each submit attempt (React Hook Form's `formState.submitCount`): the summary shows from the first attempt
|
|
10
|
+
* on and takes the focus on each one, so a second failed submit is announced too, while a problem found as the user
|
|
11
|
+
* leaves a field never pulls the focus away. Without it, the summary shows whenever there are errors and takes the
|
|
12
|
+
* focus when it appears.
|
|
13
|
+
*/
|
|
14
|
+
readonly attempt?: number;
|
|
15
|
+
/** Goes on the summary's box. */
|
|
16
|
+
readonly className?: string;
|
|
17
|
+
/** Drop the default look. */
|
|
18
|
+
readonly unstyled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A box at the top of a form listing every problem after a failed submit, each a link to its field (GOV.UK's pattern).
|
|
22
|
+
* It takes the focus when it appears, so screen readers read it all; a link scrolls to its field, focuses it and
|
|
23
|
+
* flashes it (`validFlash`).
|
|
24
|
+
*
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <ErrorSummary errors={formState.errors} attempt={formState.submitCount} />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function ErrorSummary({ errors, title, attempt, className, unstyled }: ErrorSummaryProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef } from "react";
|
|
4
|
+
import { styleHref } from "../../backgrounds/hosts/CssHost.js";
|
|
5
|
+
import { motionOnly } from "../../transitions/progress.js";
|
|
6
|
+
import { errorItems } from "../errors.js";
|
|
7
|
+
import { goToField } from "../runtime/focus.js";
|
|
8
|
+
const SHEET = `@layer components {
|
|
9
|
+
:where([data-sx-error-summary]) {
|
|
10
|
+
display: block;
|
|
11
|
+
}
|
|
12
|
+
:where([data-sx-error-summary]:focus) {
|
|
13
|
+
outline: 3px solid var(--color-sx-primary, currentColor);
|
|
14
|
+
outline-offset: 2px;
|
|
15
|
+
}
|
|
16
|
+
:where([data-sx-error-summary] ul) {
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
list-style: none;
|
|
20
|
+
}
|
|
21
|
+
}`;
|
|
22
|
+
const MOTION = `@layer components {
|
|
23
|
+
:where([data-sx-error-summary]) {
|
|
24
|
+
transition: opacity 200ms ease-out, translate 200ms ease-out;
|
|
25
|
+
}
|
|
26
|
+
@starting-style {
|
|
27
|
+
:where([data-sx-error-summary]) {
|
|
28
|
+
opacity: 0;
|
|
29
|
+
translate: 0 -0.5rem;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}`;
|
|
33
|
+
/** The field a problem is about: by id, then by name within the summary's form, then on the page. */
|
|
34
|
+
function findField(id, from) {
|
|
35
|
+
const byId = document.getElementById(id);
|
|
36
|
+
if (byId)
|
|
37
|
+
return byId;
|
|
38
|
+
const scope = from?.closest("form") ?? document;
|
|
39
|
+
return scope.querySelector(`[name="${CSS.escape(id)}"]`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A box at the top of a form listing every problem after a failed submit, each a link to its field (GOV.UK's pattern).
|
|
43
|
+
* It takes the focus when it appears, so screen readers read it all; a link scrolls to its field, focuses it and
|
|
44
|
+
* flashes it (`validFlash`).
|
|
45
|
+
*
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <ErrorSummary errors={formState.errors} attempt={formState.submitCount} />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function ErrorSummary({ errors, title = "There is a problem", attempt, className, unstyled }) {
|
|
51
|
+
const items = errorItems(errors);
|
|
52
|
+
const box = useRef(null);
|
|
53
|
+
const titleId = `${useId().replace(/[^\w-]/g, "")}-title`;
|
|
54
|
+
// with `attempt`, nothing shows before the first submit: errors found as the user leaves a field stay with the field
|
|
55
|
+
const shown = items.length > 0 && attempt !== 0;
|
|
56
|
+
const last = useRef(undefined);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const el = box.current;
|
|
59
|
+
// without `attempt`, it takes the focus when it appears; with it, on each new attempt only, never because a
|
|
60
|
+
// problem came or went while the user was typing
|
|
61
|
+
const fresh = attempt === undefined || attempt !== last.current;
|
|
62
|
+
last.current = attempt;
|
|
63
|
+
if (!shown || !el || !fresh)
|
|
64
|
+
return;
|
|
65
|
+
const still = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
66
|
+
el.focus({ preventScroll: true });
|
|
67
|
+
el.scrollIntoView({ block: "start", behavior: still ? "auto" : "smooth" });
|
|
68
|
+
}, [shown, attempt]);
|
|
69
|
+
const go = (e, id) => {
|
|
70
|
+
const field = findField(id, box.current);
|
|
71
|
+
if (!field)
|
|
72
|
+
return;
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
goToField(field);
|
|
75
|
+
};
|
|
76
|
+
if (!shown)
|
|
77
|
+
return null;
|
|
78
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { href: styleHref("form-error-summary", SHEET), precedence: "sx", children: SHEET }), _jsx("style", { href: styleHref("form-error-summary-motion", MOTION), precedence: "sx", children: motionOnly(MOTION) }), _jsx("div", { ref: box, "data-sx-error-summary": "", "data-sx-styled": unstyled ? undefined : "", tabIndex: -1, "aria-labelledby": titleId, className: className, children: _jsxs("div", { role: "alert", children: [_jsx("h2", { id: titleId, "data-sx-error-summary-title": "", children: title }), _jsx("ul", { children: items.map((item) => (_jsx("li", { children: _jsx("a", { href: `#${item.id}`, onClick: (e) => go(e, item.id), children: item.message }) }, item.id))) })] }) })] }));
|
|
79
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef, type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { FormEffect } from "../types.js";
|
|
3
|
+
import { type FieldLabels } from "./fieldTools.js";
|
|
4
|
+
type FieldTag = "input" | "textarea";
|
|
5
|
+
interface FieldOwnProps<E extends FieldTag> {
|
|
6
|
+
/** `"textarea"` for several lines. Default `"input"`. */
|
|
7
|
+
readonly as?: E;
|
|
8
|
+
/** The visible label: a real `<label for>`. Leave it out only with an `aria-label`. */
|
|
9
|
+
readonly label?: ReactNode;
|
|
10
|
+
/** A hint under the field, read with it by screen readers. */
|
|
11
|
+
readonly description?: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* The error message, under the field. While set, the field is `aria-invalid` and screen readers read the message in
|
|
14
|
+
* place of the description; `role="alert"` announces it when it appears.
|
|
15
|
+
*/
|
|
16
|
+
readonly error?: ReactNode;
|
|
17
|
+
/** Invalid with no message of its own (a message elsewhere, an error summary). */
|
|
18
|
+
readonly invalid?: boolean;
|
|
19
|
+
/** An icon at the start of the field (`inputIcon`, `inputSearch`, `inputFlood` use it). Hidden from screen readers. */
|
|
20
|
+
readonly icon?: ReactNode;
|
|
21
|
+
/** Controls at the end of the field: a unit, a button. */
|
|
22
|
+
readonly end?: ReactNode;
|
|
23
|
+
/** The value is loading: `aria-busy` (`inputSkeleton` shows it). */
|
|
24
|
+
readonly busy?: boolean;
|
|
25
|
+
/** A button that empties the field. It shows once there is text. */
|
|
26
|
+
readonly clearable?: boolean;
|
|
27
|
+
/** A button that shows the password as text and hides it again. Hidden again on submit, so it is never saved as text. */
|
|
28
|
+
readonly reveal?: boolean;
|
|
29
|
+
/** Says so under the field while Caps Lock is on. Default: on for `type="password"`. */
|
|
30
|
+
readonly capsLock?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* A character counter: `true` counts against `maxLength`; a number is a soft limit that can be typed past (better for
|
|
33
|
+
* pasting and editing: the field says how many are too many). Read out near the limit.
|
|
34
|
+
*/
|
|
35
|
+
readonly counter?: boolean | number;
|
|
36
|
+
/** A strength meter under the field, 0 (very weak) to 4 (strong), from your own scorer. Leave it out while empty. */
|
|
37
|
+
readonly strength?: number;
|
|
38
|
+
/**
|
|
39
|
+
* An async check (is the name free?): a spinner while `"checking"`, then a mark. `"invalid"` makes the field invalid:
|
|
40
|
+
* say why in `error`.
|
|
41
|
+
*/
|
|
42
|
+
readonly status?: "checking" | "valid" | "invalid";
|
|
43
|
+
/** More help behind an ⓘ button, in a popover. Screen readers hear it with the field. */
|
|
44
|
+
readonly hint?: ReactNode;
|
|
45
|
+
/** A textarea grows with its text, from `rows` lines (default 2): `true`, or the most lines before it scrolls. */
|
|
46
|
+
readonly grow?: boolean | number;
|
|
47
|
+
/** The words of the field's own buttons and notes, for your language. */
|
|
48
|
+
readonly labels?: FieldLabels;
|
|
49
|
+
/** One effect or a list; they stack. */
|
|
50
|
+
readonly effect?: FormEffect | readonly FormEffect[];
|
|
51
|
+
/**
|
|
52
|
+
* The outline around the box while the field has the keyboard's (or the typing) focus. `"auto"`: shown, unless an
|
|
53
|
+
* effect marks the focus by itself (an underline, a drawn border, a ring, a beam, a fill). `"always"`: shown with any
|
|
54
|
+
* effect. `"none"`: never: your own styles must show the focus then. Under forced colours it always shows, and with
|
|
55
|
+
* `"auto"` under reduced motion too, where effects' layers step aside. Restyle it with `--sx-field-ring-color`,
|
|
56
|
+
* `--sx-field-ring-width`, `--sx-field-ring-style` and `--sx-field-ring-offset`. Default `"auto"`.
|
|
57
|
+
*/
|
|
58
|
+
readonly focusRing?: "auto" | "always" | "none";
|
|
59
|
+
/** The default look, from the sx tokens (`styles/form.css`). Default `"outline"`. */
|
|
60
|
+
readonly variant?: "outline" | "filled" | "underline";
|
|
61
|
+
/**
|
|
62
|
+
* A field inside a sentence ("My name is ___ and I work at ___"): it sits in the line, grows with what is typed, and its
|
|
63
|
+
* label is read by screen readers only (the sentence says it). An error shows under it without moving the text.
|
|
64
|
+
*/
|
|
65
|
+
readonly inline?: boolean;
|
|
66
|
+
/** Drop the default look. */
|
|
67
|
+
readonly unstyled?: boolean;
|
|
68
|
+
/** Goes on the box you see (border, background, radius). */
|
|
69
|
+
readonly className?: string;
|
|
70
|
+
/** Goes on the outer element that holds the label, the box and the messages (`w-full`, grid placement). */
|
|
71
|
+
readonly rootClassName?: string;
|
|
72
|
+
/** Goes on the label. */
|
|
73
|
+
readonly labelClassName?: string;
|
|
74
|
+
/** Goes on the outer element, beside the effects' variables. */
|
|
75
|
+
readonly style?: CSSProperties;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Every other prop goes on the input itself, `ref` included, so `{...register("email")}` from React Hook Form, a
|
|
79
|
+
* `name` for a plain form submit, `value` / `onChange`, `required`, `type` all land where they belong.
|
|
80
|
+
*/
|
|
81
|
+
export type FieldProps<E extends FieldTag = "input"> = FieldOwnProps<E> & Omit<ComponentPropsWithRef<E>, keyof FieldOwnProps<E>>;
|
|
82
|
+
/**
|
|
83
|
+
* A text field (input or textarea) that wears any number of effects. The real input does all the work: every prop and
|
|
84
|
+
* the `ref` go on it, and the effects only paint around it, reading its state from CSS.
|
|
85
|
+
*
|
|
86
|
+
* ```tsx
|
|
87
|
+
* <Field label="Email" type="email" effect={[inputFloat(), inputUnderline()]} {...register("email")}
|
|
88
|
+
* error={errors.email?.message} />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function Field<E extends FieldTag = "input">(props: FieldProps<E>): import("react").JSX.Element;
|
|
92
|
+
export {};
|