@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,219 @@
|
|
|
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 { styleHref } from "../../backgrounds/hosts/CssHost.js";
|
|
5
|
+
import { renderSvgElement } from "../../backgrounds/hosts/svgElement.js";
|
|
6
|
+
import { splitLabel } from "../../text/split.js";
|
|
7
|
+
import { motionOnly } from "../../transitions/progress.js";
|
|
8
|
+
import { FORM_BASE_CSS } from "../base.js";
|
|
9
|
+
import { fieldDescribedBy, fieldIds } from "../ids.js";
|
|
10
|
+
import { attachOrigin } from "../runtime/origin.js";
|
|
11
|
+
import { useOffScreen } from "../runtime/useOffScreen.js";
|
|
12
|
+
import { FORM_TOOLS_CSS, FORM_TOOLS_MOTION } from "../tools.js";
|
|
13
|
+
import { usePrograms } from "./choiceParts.js";
|
|
14
|
+
import { checkField } from "./fieldChecks.js";
|
|
15
|
+
import { effectList, filled, withUid } from "./shared.js";
|
|
16
|
+
import { CapsIcon, ClearButton, Counter, FIELD_LABELS, HintButton, RevealButton, StatusMark, Strength, setNativeValue } from "./fieldTools.js";
|
|
17
|
+
/** The effects' label plans as one. */
|
|
18
|
+
function mergePlans(effects) {
|
|
19
|
+
const plans = effects.flatMap((e) => (e.label ? [e.label] : []));
|
|
20
|
+
return plans.length > 0 ? Object.assign({}, ...plans) : undefined;
|
|
21
|
+
}
|
|
22
|
+
function Layer({ effect, layer, uid, label }) {
|
|
23
|
+
const attrs = {
|
|
24
|
+
"data-sx-field-layer": effect.id,
|
|
25
|
+
"data-sx-field-part": layer.part,
|
|
26
|
+
"data-sx-field-slot": layer.slot,
|
|
27
|
+
"data-sx-field-keep": layer.keep ? "" : undefined,
|
|
28
|
+
"aria-hidden": true,
|
|
29
|
+
};
|
|
30
|
+
if (layer.legend) {
|
|
31
|
+
return (_jsx("fieldset", { ...attrs, children: _jsx("legend", { children: _jsx("span", { children: label }) }) }));
|
|
32
|
+
}
|
|
33
|
+
return (_jsxs("span", { ...attrs, children: [layer.svg ? (_jsx("svg", { viewBox: layer.svg.viewBox, preserveAspectRatio: layer.svg.preserveAspectRatio, focusable: "false", children: layer.svg.children.map((el, i) => renderSvgElement(el, uid, i, "data-sx-field-svg")) })) : null, layer.children?.map((part) => _jsx("span", { "data-sx-field-part": part }, part)), layer.lines?.map((line, i) => (_jsx("span", { "data-sx-field-part": "line", style: { "--sx-field-i": i }, children: line }, i))), layer.text ?? (layer.copy ? label : null)] }));
|
|
34
|
+
}
|
|
35
|
+
/** Every layer of these effects in one slot, in the effects' order. */
|
|
36
|
+
function layersIn(effects, slot, uid, label) {
|
|
37
|
+
return effects.flatMap((effect) => effect.layers.filter((l) => l.slot === slot).map((layer, i) => _jsx(Layer, { effect: effect, layer: layer, uid: uid, label: label }, `${effect.id}-${i}`)));
|
|
38
|
+
}
|
|
39
|
+
/** The label's words: plain, in a span, or cut into letters with one copy for screen readers. */
|
|
40
|
+
function LabelText({ plan, text, children }) {
|
|
41
|
+
if (plan?.split && text !== undefined) {
|
|
42
|
+
return (_jsxs("span", { "data-sx-field-text": "", children: [_jsx("span", { "data-sx-field-sr": "", children: text }), _jsx("span", { "data-sx-field-chars": "", dir: "auto", "aria-hidden": true, children: splitLabel(text).map((piece, i) => (_jsx("span", { "data-sx-field-char": "", style: { "--sx-field-i": i }, children: piece }, i))) })] }));
|
|
43
|
+
}
|
|
44
|
+
return plan?.wrap || plan?.split ? _jsx("span", { "data-sx-field-text": "", children: children }) : _jsx(_Fragment, { children: children });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A text field (input or textarea) that wears any number of effects. The real input does all the work: every prop and
|
|
48
|
+
* the `ref` go on it, and the effects only paint around it, reading its state from CSS.
|
|
49
|
+
*
|
|
50
|
+
* ```tsx
|
|
51
|
+
* <Field label="Email" type="email" effect={[inputFloat(), inputUnderline()]} {...register("email")}
|
|
52
|
+
* error={errors.email?.message} />
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function Field(props) {
|
|
56
|
+
const { as, label, description, error, invalid, icon, end, busy, clearable, reveal, capsLock, counter, strength, status, hint, grow, labels, effect, focusRing, inline, variant = "outline", unstyled, className, rootClassName, labelClassName, style, id: ownId, placeholder, required, ...rest } = props;
|
|
57
|
+
const Control = as ?? "input";
|
|
58
|
+
const effects = effectList(effect);
|
|
59
|
+
const words = { ...FIELD_LABELS, ...labels };
|
|
60
|
+
const type = rest.type;
|
|
61
|
+
const auto = useId();
|
|
62
|
+
const id = ownId ?? `sx-field${auto.replace(/[^\w-]/g, "")}`;
|
|
63
|
+
const ids = fieldIds(id);
|
|
64
|
+
const hintId = `${id}-hint`;
|
|
65
|
+
const hasError = filled(error);
|
|
66
|
+
const ownInvalid = rest["aria-invalid"];
|
|
67
|
+
const isInvalid = hasError || invalid === true || status === "invalid" || ownInvalid === true || ownInvalid === "true";
|
|
68
|
+
const own = [filled(hint) ? hintId : "", rest["aria-describedby"] ?? ""].filter(Boolean).join(" ") || undefined;
|
|
69
|
+
const describedBy = fieldDescribedBy(ids, { error: hasError, description: filled(description), own });
|
|
70
|
+
const plan = mergePlans(effects);
|
|
71
|
+
const text = typeof label === "string" ? label : undefined;
|
|
72
|
+
const root = useRef(null);
|
|
73
|
+
const box = useRef(null);
|
|
74
|
+
const hintBox = useRef(null);
|
|
75
|
+
const control = () => root.current?.querySelector("[data-sx-field-control]") ?? null;
|
|
76
|
+
const origin = effects.some((e) => e.needs.origin);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!origin || !root.current || !box.current)
|
|
79
|
+
return;
|
|
80
|
+
return attachOrigin(root.current, box.current);
|
|
81
|
+
}, [origin]);
|
|
82
|
+
useOffScreen(root, effects.some((e) => e.needs.inView));
|
|
83
|
+
usePrograms(root, effects);
|
|
84
|
+
// --- the counter: the length, read from the input as it changes (also after a form reset or a value set in code) ---
|
|
85
|
+
const counted = counter !== undefined && counter !== false;
|
|
86
|
+
const limit = counter === true ? rest.maxLength : typeof counter === "number" ? counter : undefined;
|
|
87
|
+
if (process.env.NODE_ENV !== "production") {
|
|
88
|
+
checkField({
|
|
89
|
+
effects, plan, as, type, hasError, status, reveal, counter, limit, grow, focusRing,
|
|
90
|
+
labelled: filled(label) || !!rest["aria-label"] || !!rest["aria-labelledby"],
|
|
91
|
+
labelIsText: text !== undefined,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const [length, setLength] = useState(() => String(rest.value ?? rest.defaultValue ?? "").length);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const el = control();
|
|
97
|
+
if (!counted || !el)
|
|
98
|
+
return;
|
|
99
|
+
const sync = () => setLength(el.value.length);
|
|
100
|
+
const later = () => setTimeout(sync);
|
|
101
|
+
el.addEventListener("input", sync);
|
|
102
|
+
el.form?.addEventListener("reset", later);
|
|
103
|
+
return () => {
|
|
104
|
+
el.removeEventListener("input", sync);
|
|
105
|
+
el.form?.removeEventListener("reset", later);
|
|
106
|
+
};
|
|
107
|
+
}, [counted]);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const el = control();
|
|
110
|
+
if (counted && el)
|
|
111
|
+
setLength(el.value.length);
|
|
112
|
+
});
|
|
113
|
+
const left = limit === undefined ? 0 : limit - length;
|
|
114
|
+
const near = limit !== undefined && left <= Math.max(10, Math.round(limit * 0.1));
|
|
115
|
+
const [said, setSaid] = useState("");
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (limit === undefined)
|
|
118
|
+
return;
|
|
119
|
+
// read out once the typing stops, and only near the limit: every keystroke would drown the screen reader
|
|
120
|
+
const next = left < 0 ? words.over.replace("{n}", String(-left)) : near ? words.left.replace("{n}", String(left)) : "";
|
|
121
|
+
const timer = setTimeout(() => setSaid(next), 700);
|
|
122
|
+
return () => clearTimeout(timer);
|
|
123
|
+
}, [limit, left, near, words.over, words.left]);
|
|
124
|
+
// --- show password: the type flips, the caret stays where it was, and a submit hides it again ---
|
|
125
|
+
const [shown, setShown] = useState(false);
|
|
126
|
+
const caret = useRef(null);
|
|
127
|
+
const toggle = () => {
|
|
128
|
+
const el = control();
|
|
129
|
+
if (el)
|
|
130
|
+
caret.current = [el.selectionStart, el.selectionEnd];
|
|
131
|
+
setShown((s) => !s);
|
|
132
|
+
};
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const el = control();
|
|
135
|
+
const at = caret.current;
|
|
136
|
+
if (!el || !at)
|
|
137
|
+
return;
|
|
138
|
+
caret.current = null;
|
|
139
|
+
try {
|
|
140
|
+
el.setSelectionRange(at[0], at[1]);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// a type without a selection
|
|
144
|
+
}
|
|
145
|
+
}, [shown]);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
const form = reveal ? control()?.form : undefined;
|
|
148
|
+
if (!form)
|
|
149
|
+
return;
|
|
150
|
+
const hide = () => setShown(false);
|
|
151
|
+
form.addEventListener("submit", hide);
|
|
152
|
+
return () => form.removeEventListener("submit", hide);
|
|
153
|
+
}, [reveal]);
|
|
154
|
+
// --- Caps Lock: read from each key, forgotten when the field loses the focus ---
|
|
155
|
+
const caps = capsLock ?? type === "password";
|
|
156
|
+
const [capsOn, setCapsOn] = useState(false);
|
|
157
|
+
const readCaps = (e) => setCapsOn(e.getModifierState("CapsLock"));
|
|
158
|
+
const capsHandlers = caps
|
|
159
|
+
? {
|
|
160
|
+
onKeyDown: (e) => {
|
|
161
|
+
readCaps(e);
|
|
162
|
+
rest.onKeyDown?.(e);
|
|
163
|
+
},
|
|
164
|
+
onKeyUp: (e) => {
|
|
165
|
+
readCaps(e);
|
|
166
|
+
rest.onKeyUp?.(e);
|
|
167
|
+
},
|
|
168
|
+
onBlur: (e) => {
|
|
169
|
+
setCapsOn(false);
|
|
170
|
+
rest.onBlur?.(e);
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
: {};
|
|
174
|
+
// --- the hint: popover="hint" where it exists, else "auto" (it closes on a click outside too, but also closes other
|
|
175
|
+
// popovers). Anchor positioning places it; without that, the tools sheet docks it at the bottom of the screen ---
|
|
176
|
+
const hinted = filled(hint);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const el = hintBox.current;
|
|
179
|
+
if (el && el.popover !== "hint")
|
|
180
|
+
el.popover = "auto";
|
|
181
|
+
}, [hinted]);
|
|
182
|
+
// ids for filters and gradients inside layers, unique per field and valid in url(#…)
|
|
183
|
+
const uid = `sx${auto.replace(/[^\w-]/g, "")}`;
|
|
184
|
+
const fills = layersIn(effects, "fill", uid, label);
|
|
185
|
+
const ends = layersIn(effects, "end", uid, label);
|
|
186
|
+
const keepFill = effects.some((e) => e.layers.some((l) => l.slot === "fill" && l.keep));
|
|
187
|
+
const level = strength === undefined ? undefined : Math.max(0, Math.min(4, Math.round(strength)));
|
|
188
|
+
const vars = {
|
|
189
|
+
...withUid(Object.assign({}, ...effects.map((e) => e.vars)), uid),
|
|
190
|
+
...(limit === undefined ? {} : { "--sx-field-used": Math.max(0, Math.min(1, length / limit)) }),
|
|
191
|
+
...(grow ? { "--sx-field-rows": rest.rows ?? 2, ...(typeof grow === "number" ? { "--sx-field-rows-max": grow } : {}) } : {}),
|
|
192
|
+
...style,
|
|
193
|
+
};
|
|
194
|
+
const hasEnd = ends.length > 0 || status || counted || clearable || reveal || hinted || filled(end);
|
|
195
|
+
const controlProps = {
|
|
196
|
+
...rest,
|
|
197
|
+
...capsHandlers,
|
|
198
|
+
id,
|
|
199
|
+
type: reveal && shown ? "text" : type,
|
|
200
|
+
"data-sx-field-control": "",
|
|
201
|
+
// a floating label needs :placeholder-shown, and an empty field shows a placeholder only when it has one
|
|
202
|
+
placeholder: placeholder ?? " ",
|
|
203
|
+
required,
|
|
204
|
+
"aria-required": required || undefined,
|
|
205
|
+
"aria-invalid": isInvalid || undefined,
|
|
206
|
+
"aria-describedby": describedBy,
|
|
207
|
+
"aria-busy": busy || undefined,
|
|
208
|
+
};
|
|
209
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { href: styleHref("form-base", FORM_BASE_CSS), precedence: "sx", children: FORM_BASE_CSS }), _jsx("style", { href: styleHref("form-tools", FORM_TOOLS_CSS), precedence: "sx", children: FORM_TOOLS_CSS }), _jsx("style", { href: styleHref("form-tools-motion", FORM_TOOLS_MOTION), precedence: "sx", children: motionOnly(FORM_TOOLS_MOTION) }), effects.filter((e) => e.css).map((e) => {
|
|
210
|
+
const css = motionOnly(e.css);
|
|
211
|
+
return (_jsx("style", { href: styleHref(`form-${e.id}`, css), precedence: "sx", children: css }, e.id));
|
|
212
|
+
}), effects.filter((e) => e.stateCss).map((e) => (_jsx("style", { href: styleHref(`form-${e.id}-state`, e.stateCss), precedence: "sx", children: e.stateCss }, e.id))), _jsxs("div", { ref: root, "data-sx-field": effects.map((e) => e.id).join(" "), "data-sx-styled": unstyled ? undefined : "", "data-sx-variant": unstyled ? undefined : variant, "data-sx-status": status, "data-sx-strength": level, "data-sx-count": near ? (left < 0 ? "over" : "near") : undefined, "data-sx-revealed": reveal && shown ? "" : undefined, "data-sx-field-grow": grow ? "" : undefined, "data-sx-ring": focusRing === "auto" ? undefined : focusRing, "data-sx-inline": inline ? "" : undefined, className: rootClassName, style: vars, children: [filled(label) ? (_jsxs("label", { "data-sx-field-label": "", id: ids.label, htmlFor: id, className: labelClassName, children: [_jsx(LabelText, { plan: plan, text: text, children: label }), layersIn(effects, "label", uid, label)] })) : null, layersIn(effects, "behind", uid, label), _jsxs("div", { ref: box, "data-sx-field-box": "", className: className, children: [layersIn(effects, "under", uid, label), fills.length > 0 ? (_jsx("span", { "data-sx-field-clip": "", "data-sx-field-keep": keepFill ? "" : undefined, "aria-hidden": true, children: fills })) : null, filled(icon) ? (_jsx("span", { "data-sx-field-icon": "", "aria-hidden": true, children: icon })) : null, _jsx(Control, { ...controlProps }), hasEnd ? (_jsxs("span", { "data-sx-field-end": "", children: [ends, status ? _jsx(StatusMark, {}) : null, counted ? (_jsx(Counter, { used: length, limit: limit, children: layersIn(effects, "counter", uid, label) })) : null, clearable ? (_jsx(ClearButton, { label: words.clear, controls: id, onClick: () => {
|
|
213
|
+
const el = control();
|
|
214
|
+
if (!el)
|
|
215
|
+
return;
|
|
216
|
+
setNativeValue(el, "");
|
|
217
|
+
el.focus();
|
|
218
|
+
} })) : null, reveal ? _jsx(RevealButton, { label: words.show, controls: id, pressed: shown, onClick: toggle }) : null, hinted ? _jsx(HintButton, { label: words.hint, target: hintId, anchor: `--${uid}-hint` }) : null, end] })) : null, layersIn(effects, "edge", uid, label)] }), level === undefined ? null : _jsx(Strength, { level: level, text: words.strength.replace("{level}", words.levels[level] ?? "") }), filled(description) ? (_jsx("p", { "data-sx-field-description": "", id: ids.description, children: description })) : null, caps ? (_jsx("p", { "data-sx-field-note": "", role: "status", children: capsOn ? (_jsxs("span", { children: [_jsx(CapsIcon, {}), words.capsLock] })) : null })) : null, _jsx("div", { "data-sx-field-message": "", children: _jsx("p", { "data-sx-field-error": "", id: ids.error, role: "alert", children: hasError ? (_jsx("span", { "data-sx-field-says": "", children: error }, typeof error === "string" ? error : undefined)) : null }) }), hinted ? (_jsx("div", { ref: hintBox, id: hintId, popover: "hint", "data-sx-field-hint": "", style: { positionAnchor: `--${uid}-hint` }, children: hint })) : null, limit === undefined ? null : (_jsx("span", { "data-sx-field-sr": "", role: "status", children: said })), status ? (_jsx("span", { "data-sx-field-sr": "", role: "status", children: status === "checking" ? words.checking : status === "valid" ? words.valid : "" })) : null] })] }));
|
|
219
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type CSSProperties, type FocusEvent, type ReactNode, type Ref } from "react";
|
|
2
|
+
import type { FormEffect } from "../types.js";
|
|
3
|
+
/** The words a FileDrop says, for your language. `{name}` is a file's name, `{n}` a number, `{max}` a size. */
|
|
4
|
+
export interface FileDropLabels {
|
|
5
|
+
readonly drop?: string;
|
|
6
|
+
readonly page?: string;
|
|
7
|
+
readonly list?: string;
|
|
8
|
+
readonly remove?: string;
|
|
9
|
+
readonly up?: string;
|
|
10
|
+
readonly down?: string;
|
|
11
|
+
readonly added?: string;
|
|
12
|
+
readonly removed?: string;
|
|
13
|
+
readonly moved?: string;
|
|
14
|
+
readonly uploaded?: string;
|
|
15
|
+
readonly tooBig?: string;
|
|
16
|
+
readonly wrongType?: string;
|
|
17
|
+
readonly tooMany?: string;
|
|
18
|
+
readonly twice?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface FileDropProps {
|
|
21
|
+
/** The visible label: a real `<label for>` on the file input. */
|
|
22
|
+
readonly label?: ReactNode;
|
|
23
|
+
/** Help under the zone: what it takes ("PDF or images, up to 5 MB"). */
|
|
24
|
+
readonly description?: ReactNode;
|
|
25
|
+
/** The error message (the app's). Without one, the zone's own refusals show here. */
|
|
26
|
+
readonly error?: ReactNode;
|
|
27
|
+
readonly invalid?: boolean;
|
|
28
|
+
/** The files, when you hold them (a `Controller`'s `field.value`). */
|
|
29
|
+
readonly value?: readonly File[];
|
|
30
|
+
readonly defaultValue?: readonly File[];
|
|
31
|
+
/** Called with every file after a change: `field.onChange` fits. */
|
|
32
|
+
readonly onChange?: (files: File[]) => void;
|
|
33
|
+
readonly onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
|
|
34
|
+
/** Submitted with a plain form: the real input carries every file in the list. */
|
|
35
|
+
readonly name?: string;
|
|
36
|
+
readonly id?: string;
|
|
37
|
+
/** Goes on the real file input (React Hook Form focuses it on an error). */
|
|
38
|
+
readonly ref?: Ref<HTMLInputElement>;
|
|
39
|
+
readonly disabled?: boolean;
|
|
40
|
+
readonly required?: boolean;
|
|
41
|
+
/** As on a file input: `"image/*,.pdf"`. Anything else is refused, with words. */
|
|
42
|
+
readonly accept?: string;
|
|
43
|
+
/** The largest file, in bytes. */
|
|
44
|
+
readonly maxSize?: number;
|
|
45
|
+
/** The most files. Default: no limit, or 1 without `multiple`. */
|
|
46
|
+
readonly maxFiles?: number;
|
|
47
|
+
/** Several files (a new one adds); `false`: one (a new one replaces it). Default `true`. */
|
|
48
|
+
readonly multiple?: boolean;
|
|
49
|
+
/** Files dragged anywhere over the page land here, under an overlay that says so. */
|
|
50
|
+
readonly page?: boolean;
|
|
51
|
+
/** Up and down buttons on each card, to set the order. */
|
|
52
|
+
readonly reorder?: boolean;
|
|
53
|
+
/** How far each file's upload has got, 0 to 1 (1: done, a check shows). You upload; this shows it. */
|
|
54
|
+
readonly progress?: (file: File) => number | undefined;
|
|
55
|
+
/** The zone's icon. */
|
|
56
|
+
readonly icon?: ReactNode;
|
|
57
|
+
/** The language for file sizes. Default the visitor's. */
|
|
58
|
+
readonly locale?: string;
|
|
59
|
+
readonly effect?: FormEffect | readonly FormEffect[];
|
|
60
|
+
readonly labels?: FileDropLabels;
|
|
61
|
+
readonly unstyled?: boolean;
|
|
62
|
+
/** Goes on the drop zone. */
|
|
63
|
+
readonly className?: string;
|
|
64
|
+
readonly rootClassName?: string;
|
|
65
|
+
readonly labelClassName?: string;
|
|
66
|
+
readonly style?: CSSProperties;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A drop zone round the real file input. Drop files on it, or press its button (the browser's own, from the keyboard
|
|
70
|
+
* too) to pick them; each lands in a list of cards with its name, size and a remove button (with `reorder`, up and down
|
|
71
|
+
* too). It checks `accept`, `maxSize`, `maxFiles` and twins (same name and size) before a file gets in, and says why one
|
|
72
|
+
* didn't. The files are the value: hold them with `value` / `onChange` (a `Controller`), upload them yourself, and pass
|
|
73
|
+
* `progress`. The real input always carries the whole list, so a plain form submits them.
|
|
74
|
+
*
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <Controller name="files" control={control} render={({ field }) => (
|
|
77
|
+
* <FileDrop label="Attachments" accept="image/*,.pdf" maxSize={5e6} effect={[fileDrag(), fileCards()]} {...field} />
|
|
78
|
+
* )} />
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function FileDrop(props: FileDropProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { warnOnce } from "../../buttons/runtime/dev.js";
|
|
5
|
+
import { renderSvgElement } from "../../backgrounds/hosts/svgElement.js";
|
|
6
|
+
import { FILE_CSS } from "../file.js";
|
|
7
|
+
import { accepts, fileSize } from "../files.js";
|
|
8
|
+
import { fieldDescribedBy, fieldIds } from "../ids.js";
|
|
9
|
+
import { useFlip } from "../runtime/useFlip.js";
|
|
10
|
+
import { EffectSheets, usePrograms } from "./choiceParts.js";
|
|
11
|
+
import { effectList, filled, withUid } from "./shared.js";
|
|
12
|
+
const WORDS = {
|
|
13
|
+
drop: "Drop files here, or",
|
|
14
|
+
page: "Drop to add",
|
|
15
|
+
list: "Chosen files",
|
|
16
|
+
remove: "Remove {name}",
|
|
17
|
+
up: "Move {name} up",
|
|
18
|
+
down: "Move {name} down",
|
|
19
|
+
added: "Added: {name}",
|
|
20
|
+
removed: "{name} removed",
|
|
21
|
+
moved: "{name} moved to place {n}",
|
|
22
|
+
uploaded: "{name} uploaded",
|
|
23
|
+
tooBig: "{name} is larger than {max}.",
|
|
24
|
+
wrongType: "{name} isn't a kind of file this takes.",
|
|
25
|
+
tooMany: "Only {n} files can be added.",
|
|
26
|
+
twice: "{name} is already here.",
|
|
27
|
+
};
|
|
28
|
+
const twin = (a, b) => a.name === b.name && a.size === b.size;
|
|
29
|
+
/** Dragged items' types, where the browser tells them during the drag (Chromium and Firefox; Safari says only "Files"). */
|
|
30
|
+
const draggedTypes = (e) => [...(e.dataTransfer?.items ?? [])].filter((i) => i.kind === "file").map((i) => i.type);
|
|
31
|
+
const hasFiles = (e) => [...(e.dataTransfer?.types ?? [])].includes("Files");
|
|
32
|
+
const Icon = ({ children }) => (_jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": true, focusable: "false", children: children }));
|
|
33
|
+
const UPLOAD = _jsx(Icon, { children: _jsx("path", { d: "M12 15V4m0 0L7.5 8.5M12 4l4.5 4.5M4.5 15v3.5a1.5 1.5 0 0 0 1.5 1.5h12a1.5 1.5 0 0 0 1.5-1.5V15" }) });
|
|
34
|
+
const DOC = _jsx(Icon, { children: _jsx("path", { d: "M14 3.5H7A1.5 1.5 0 0 0 5.5 5v14A1.5 1.5 0 0 0 7 20.5h10a1.5 1.5 0 0 0 1.5-1.5V8L14 3.5ZM14 3.5V8h4.5" }) });
|
|
35
|
+
function Layers({ effects, slot, uid }) {
|
|
36
|
+
return (_jsx(_Fragment, { children: effects.flatMap((e) => e.layers
|
|
37
|
+
.filter((l) => l.slot === slot)
|
|
38
|
+
.map((l, i) => (_jsx("span", { "data-sx-file-layer": e.id, "data-sx-file-part": l.part, "data-sx-file-keep": l.keep ? "" : undefined, "aria-hidden": true, children: l.svg ? (_jsx("svg", { viewBox: l.svg.viewBox, focusable: "false", children: l.svg.children.map((el, k) => renderSvgElement(el, uid, k, "data-sx-file-svg")) })) : null }, `${e.id}-${i}`)))) }));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A drop zone round the real file input. Drop files on it, or press its button (the browser's own, from the keyboard
|
|
42
|
+
* too) to pick them; each lands in a list of cards with its name, size and a remove button (with `reorder`, up and down
|
|
43
|
+
* too). It checks `accept`, `maxSize`, `maxFiles` and twins (same name and size) before a file gets in, and says why one
|
|
44
|
+
* didn't. The files are the value: hold them with `value` / `onChange` (a `Controller`), upload them yourself, and pass
|
|
45
|
+
* `progress`. The real input always carries the whole list, so a plain form submits them.
|
|
46
|
+
*
|
|
47
|
+
* ```tsx
|
|
48
|
+
* <Controller name="files" control={control} render={({ field }) => (
|
|
49
|
+
* <FileDrop label="Attachments" accept="image/*,.pdf" maxSize={5e6} effect={[fileDrag(), fileCards()]} {...field} />
|
|
50
|
+
* )} />
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function FileDrop(props) {
|
|
54
|
+
const { label, description, error, invalid, value, defaultValue, onChange, onBlur, name, id: ownId, ref, disabled, required, accept, maxSize, maxFiles, multiple = true, page, reorder, progress, icon, locale, effect, labels, unstyled, className, rootClassName, labelClassName, style, } = props;
|
|
55
|
+
const effects = effectList(effect);
|
|
56
|
+
const words = { ...WORDS, ...labels };
|
|
57
|
+
const auto = useId().replace(/[^\w-]/g, "");
|
|
58
|
+
const id = ownId ?? `sx-file${auto}`;
|
|
59
|
+
const uid = `sx${auto}`;
|
|
60
|
+
const ids = fieldIds(id);
|
|
61
|
+
const hintId = `${id}-hint`;
|
|
62
|
+
if (process.env.NODE_ENV !== "production") {
|
|
63
|
+
for (const e of effects)
|
|
64
|
+
if (!e.fits.includes("file"))
|
|
65
|
+
warnOnce(`the effect ${e.id} is not made for a FileDrop: it fits ${e.fits.join(", ")}.`);
|
|
66
|
+
if (!filled(label))
|
|
67
|
+
warnOnce("a FileDrop has no label: give it `label`.");
|
|
68
|
+
if (effects.some((e) => e.id === "filePage") && !page)
|
|
69
|
+
warnOnce("filePage animates the page-wide overlay: give the FileDrop `page`.");
|
|
70
|
+
}
|
|
71
|
+
const controlled = value !== undefined;
|
|
72
|
+
const [held, setHeld] = useState(() => defaultValue ?? []);
|
|
73
|
+
const files = controlled ? value : held;
|
|
74
|
+
const [leaving, setLeaving] = useState([]);
|
|
75
|
+
const [refusal, setRefusal] = useState(null);
|
|
76
|
+
const [said, setSaid] = useState("");
|
|
77
|
+
const [over, setOver] = useState(null);
|
|
78
|
+
const [pageOver, setPageOver] = useState(false);
|
|
79
|
+
const [shake, setShake] = useState(false);
|
|
80
|
+
const [live, setLive] = useState(false);
|
|
81
|
+
const root = useRef(null);
|
|
82
|
+
const list = useRef(null);
|
|
83
|
+
const input = useRef(null);
|
|
84
|
+
const flips = effects.some((e) => e.needs.flip);
|
|
85
|
+
const previews = effects.some((e) => e.needs.preview);
|
|
86
|
+
const limit = maxFiles ?? (multiple ? Infinity : 1);
|
|
87
|
+
useFlip(list, flips, "[data-sx-file-card]");
|
|
88
|
+
usePrograms(root, effects);
|
|
89
|
+
const say = (s) => setSaid((now) => (now === s ? `${s} ` : s));
|
|
90
|
+
/** The real input carries every file in the list, for a plain form submit. */
|
|
91
|
+
const carry = (next) => {
|
|
92
|
+
const el = input.current;
|
|
93
|
+
if (!el || typeof DataTransfer === "undefined")
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
const dt = new DataTransfer();
|
|
97
|
+
for (const f of next)
|
|
98
|
+
dt.items.add(f);
|
|
99
|
+
el.files = dt.files;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// an engine that can't set files: the list is still the value
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const set = (next) => {
|
|
106
|
+
if (!controlled)
|
|
107
|
+
setHeld(next);
|
|
108
|
+
carry(next);
|
|
109
|
+
onChange?.(next);
|
|
110
|
+
};
|
|
111
|
+
const add = (incoming) => {
|
|
112
|
+
if (disabled || incoming.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
setLive(true);
|
|
115
|
+
const took = [];
|
|
116
|
+
const why = [];
|
|
117
|
+
const max = maxSize === undefined ? "" : fileSize(maxSize, locale);
|
|
118
|
+
for (const f of incoming) {
|
|
119
|
+
if (!accepts(f, accept))
|
|
120
|
+
why.push(words.wrongType.replace("{name}", f.name));
|
|
121
|
+
else if (maxSize !== undefined && f.size > maxSize)
|
|
122
|
+
why.push(words.tooBig.replace("{name}", f.name).replace("{max}", max));
|
|
123
|
+
else if (multiple && [...files, ...took].some((g) => twin(f, g)))
|
|
124
|
+
why.push(words.twice.replace("{name}", f.name));
|
|
125
|
+
else
|
|
126
|
+
took.push(f);
|
|
127
|
+
}
|
|
128
|
+
const room = multiple ? Math.max(0, limit - files.length) : 1;
|
|
129
|
+
if (took.length > room) {
|
|
130
|
+
why.push(words.tooMany.replace("{n}", String(limit)));
|
|
131
|
+
took.length = room;
|
|
132
|
+
}
|
|
133
|
+
const next = multiple ? [...files, ...took] : took.length > 0 ? took : [...files];
|
|
134
|
+
if (!multiple && took.length > 0 && files[0] && flips)
|
|
135
|
+
setLeaving((l) => [...l, files[0]]);
|
|
136
|
+
if (took.length > 0)
|
|
137
|
+
set(next);
|
|
138
|
+
else
|
|
139
|
+
carry(files);
|
|
140
|
+
setRefusal(why.length > 0 ? why.join(" ") : null);
|
|
141
|
+
if (why.length > 0) {
|
|
142
|
+
// a frame without the mark, so a second refusal shakes again
|
|
143
|
+
setShake(false);
|
|
144
|
+
requestAnimationFrame(() => setShake(true));
|
|
145
|
+
}
|
|
146
|
+
say([took.length > 0 ? words.added.replace("{name}", took.map((f) => f.name).join(", ")) : "", ...why].filter(Boolean).join(" "));
|
|
147
|
+
};
|
|
148
|
+
const remove = (at) => {
|
|
149
|
+
const gone = files[at];
|
|
150
|
+
if (!gone)
|
|
151
|
+
return;
|
|
152
|
+
setLive(true);
|
|
153
|
+
if (flips)
|
|
154
|
+
setLeaving((l) => [...l, gone]);
|
|
155
|
+
const next = files.filter((_, i) => i !== at);
|
|
156
|
+
set(next);
|
|
157
|
+
setRefusal(null);
|
|
158
|
+
say(words.removed.replace("{name}", gone.name));
|
|
159
|
+
// the focus goes to the card now in its place, or back to the input
|
|
160
|
+
requestAnimationFrame(() => {
|
|
161
|
+
const cards = list.current?.querySelectorAll("[data-sx-file-card]:not([data-sx-leaving]) [data-sx-file-remove]");
|
|
162
|
+
(cards?.[Math.min(at, next.length - 1)] ?? input.current)?.focus();
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
const move = (at, by) => {
|
|
166
|
+
const to = at + by;
|
|
167
|
+
if (to < 0 || to >= files.length)
|
|
168
|
+
return;
|
|
169
|
+
const next = [...files];
|
|
170
|
+
[next[at], next[to]] = [next[to], next[at]];
|
|
171
|
+
set(next);
|
|
172
|
+
say(words.moved.replace("{name}", files[at].name).replace("{n}", String(to + 1)));
|
|
173
|
+
};
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (!shake)
|
|
176
|
+
return;
|
|
177
|
+
const t = setTimeout(() => setShake(false), 500);
|
|
178
|
+
return () => clearTimeout(t);
|
|
179
|
+
}, [shake]);
|
|
180
|
+
// a value set by the app (a Controller, a reset) is carried by the input too
|
|
181
|
+
useEffect(() => carry(files), [files]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
const form = input.current?.form;
|
|
184
|
+
if (!form || controlled)
|
|
185
|
+
return;
|
|
186
|
+
const reset = () => setTimeout(() => setHeld([]));
|
|
187
|
+
form.addEventListener("reset", reset);
|
|
188
|
+
return () => form.removeEventListener("reset", reset);
|
|
189
|
+
}, [controlled]);
|
|
190
|
+
// cards leaving: gone once their animation ends (at once when nothing animates them)
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
for (const card of list.current?.querySelectorAll("[data-sx-leaving]") ?? []) {
|
|
193
|
+
const key = card.dataset.key;
|
|
194
|
+
const drop = () => setLeaving((l) => l.filter((f) => keyOf(f) !== key));
|
|
195
|
+
if (getComputedStyle(card).animationName === "none")
|
|
196
|
+
drop();
|
|
197
|
+
else
|
|
198
|
+
card.addEventListener("animationend", drop, { once: true });
|
|
199
|
+
}
|
|
200
|
+
}, [leaving]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
201
|
+
// pictures: an object URL each, let go when the file leaves the list or the zone unmounts
|
|
202
|
+
const urls = useRef(new Map());
|
|
203
|
+
const shown = useMemo(() => [...files, ...leaving.filter((f) => !files.includes(f))], [files, leaving]);
|
|
204
|
+
const picture = (f) => {
|
|
205
|
+
if (!previews || !f.type.startsWith("image/") || typeof URL === "undefined")
|
|
206
|
+
return undefined;
|
|
207
|
+
let url = urls.current.get(f);
|
|
208
|
+
if (!url) {
|
|
209
|
+
url = URL.createObjectURL(f);
|
|
210
|
+
urls.current.set(f, url);
|
|
211
|
+
}
|
|
212
|
+
return url;
|
|
213
|
+
};
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
for (const [f, url] of urls.current) {
|
|
216
|
+
if (!shown.includes(f)) {
|
|
217
|
+
URL.revokeObjectURL(url);
|
|
218
|
+
urls.current.delete(f);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}, [shown]);
|
|
222
|
+
useEffect(() => () => urls.current.forEach((url) => URL.revokeObjectURL(url)), []);
|
|
223
|
+
// the whole page takes a drop: an overlay says so while files are dragged over the window
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
if (!page)
|
|
226
|
+
return;
|
|
227
|
+
let depth = 0;
|
|
228
|
+
const enter = (e) => {
|
|
229
|
+
if (!hasFiles(e))
|
|
230
|
+
return;
|
|
231
|
+
depth++;
|
|
232
|
+
setPageOver(true);
|
|
233
|
+
};
|
|
234
|
+
const leave = () => {
|
|
235
|
+
depth = Math.max(0, depth - 1);
|
|
236
|
+
if (depth === 0)
|
|
237
|
+
setPageOver(false);
|
|
238
|
+
};
|
|
239
|
+
const overPage = (e) => {
|
|
240
|
+
if (hasFiles(e))
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
};
|
|
243
|
+
const drop = (e) => {
|
|
244
|
+
if (!hasFiles(e))
|
|
245
|
+
return;
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
depth = 0;
|
|
248
|
+
setPageOver(false);
|
|
249
|
+
setOver(null);
|
|
250
|
+
if (!root.current?.querySelector("[data-sx-file-zone]")?.contains(e.target))
|
|
251
|
+
add([...(e.dataTransfer?.files ?? [])]);
|
|
252
|
+
};
|
|
253
|
+
window.addEventListener("dragenter", enter);
|
|
254
|
+
window.addEventListener("dragleave", leave);
|
|
255
|
+
window.addEventListener("dragover", overPage);
|
|
256
|
+
window.addEventListener("drop", drop);
|
|
257
|
+
return () => {
|
|
258
|
+
window.removeEventListener("dragenter", enter);
|
|
259
|
+
window.removeEventListener("dragleave", leave);
|
|
260
|
+
window.removeEventListener("dragover", overPage);
|
|
261
|
+
window.removeEventListener("drop", drop);
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
const zoneDrag = {
|
|
265
|
+
onDragEnter: (e) => {
|
|
266
|
+
if (!hasFiles(e) || disabled)
|
|
267
|
+
return;
|
|
268
|
+
e.preventDefault();
|
|
269
|
+
const types = draggedTypes(e);
|
|
270
|
+
setOver(types.length > 0 && types.every((t) => t) && !types.some((t) => accepts({ name: "", type: t }, accept)) ? "reject" : "ok");
|
|
271
|
+
},
|
|
272
|
+
onDragOver: (e) => {
|
|
273
|
+
if (!hasFiles(e) || disabled)
|
|
274
|
+
return;
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
if (e.dataTransfer)
|
|
277
|
+
e.dataTransfer.dropEffect = "copy";
|
|
278
|
+
},
|
|
279
|
+
onDragLeave: (e) => {
|
|
280
|
+
if (!e.currentTarget.contains(e.relatedTarget))
|
|
281
|
+
setOver(null);
|
|
282
|
+
},
|
|
283
|
+
onDrop: (e) => {
|
|
284
|
+
if (!hasFiles(e))
|
|
285
|
+
return;
|
|
286
|
+
e.preventDefault();
|
|
287
|
+
setOver(null);
|
|
288
|
+
if (page)
|
|
289
|
+
setPageOver(false);
|
|
290
|
+
add([...(e.dataTransfer?.files ?? [])]);
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
const setRef = (el) => {
|
|
294
|
+
input.current = el;
|
|
295
|
+
if (typeof ref === "function")
|
|
296
|
+
ref(el);
|
|
297
|
+
else if (ref)
|
|
298
|
+
ref.current = el;
|
|
299
|
+
};
|
|
300
|
+
const hasError = filled(error);
|
|
301
|
+
const message = hasError ? error : refusal;
|
|
302
|
+
const isInvalid = hasError || invalid === true;
|
|
303
|
+
const describedBy = [hintId, fieldDescribedBy(ids, { error: filled(message), description: filled(description) })].filter(Boolean).join(" ");
|
|
304
|
+
const vars = { ...withUid(Object.assign({}, ...effects.map((e) => e.vars)), uid), ...style };
|
|
305
|
+
return (_jsxs(_Fragment, { children: [_jsx(EffectSheets, { name: "form-file", base: FILE_CSS, effects: effects }), _jsxs("div", { ref: root, "data-sx-file": effects.map((e) => e.id).join(" "), "data-sx-styled": unstyled ? undefined : "", "data-sx-over": over ?? undefined, "data-sx-page-over": pageOver ? "" : undefined, "data-sx-rejected": shake ? "" : undefined, "data-sx-live": live ? "" : undefined, className: rootClassName, style: vars, children: [filled(label) ? (_jsx("label", { "data-sx-file-label": "", id: ids.label, htmlFor: id, className: labelClassName, children: label })) : null, _jsxs("div", { "data-sx-file-zone": "", className: className, ...zoneDrag, onClick: (e) => {
|
|
306
|
+
// the whole zone opens the picker; the input's own button does so by itself
|
|
307
|
+
if (e.target !== input.current && !disabled)
|
|
308
|
+
input.current?.click();
|
|
309
|
+
}, children: [_jsx(Layers, { effects: effects, slot: "zone", uid: uid }), _jsx("span", { "data-sx-file-icon": "", "aria-hidden": true, children: icon ?? UPLOAD }), _jsx("span", { "data-sx-file-hint": "", id: hintId, children: words.drop }), _jsx("input", { ref: setRef, id: id, type: "file", name: name, accept: accept, multiple: multiple, disabled: disabled, "data-sx-file-control": "", "aria-invalid": isInvalid || filled(refusal) || undefined, "aria-required": required || undefined, "aria-describedby": describedBy, onChange: (e) => add([...(e.target.files ?? [])]), onBlur: onBlur })] }), filled(description) ? (_jsx("p", { "data-sx-file-description": "", id: ids.description, children: description })) : null, shown.length > 0 ? (_jsx("ul", { ref: list, "data-sx-file-list": "", "aria-label": words.list, children: shown.map((f) => {
|
|
310
|
+
const at = files.indexOf(f);
|
|
311
|
+
const gone = at === -1;
|
|
312
|
+
const p = gone ? undefined : progress?.(f);
|
|
313
|
+
const done = p !== undefined && p >= 1;
|
|
314
|
+
const src = picture(f);
|
|
315
|
+
return (_jsxs("li", { "data-key": keyOf(f), "data-sx-file-card": "", "data-sx-leaving": gone ? "" : undefined, "data-sx-done": done ? "" : undefined, "data-sx-uploading": p !== undefined && !done ? "" : undefined, style: { "--sx-file-progress": p ?? 0, "--sx-file-i": at }, children: [_jsx(Layers, { effects: effects, slot: "card", uid: uid }), _jsx("span", { "data-sx-file-thumb": "", "aria-hidden": true, children: src ? _jsx("img", { src: src, alt: "", onLoad: (e) => e.currentTarget.setAttribute("data-sx-loaded", "") }) : DOC }), _jsxs("span", { "data-sx-file-info": "", children: [_jsx("span", { "data-sx-file-name": "", children: f.name }), _jsx("span", { "data-sx-file-size": "", children: fileSize(f.size, locale) }), p !== undefined ? (_jsx("span", { "data-sx-file-bar": "", role: "progressbar", "aria-label": f.name, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(Math.min(1, p) * 100), children: _jsx("span", { "data-sx-file-fill": "" }) })) : null] }), _jsx("span", { "data-sx-file-done": "", "aria-hidden": true, children: _jsx(Icon, { children: _jsx("path", { d: "m5 12.5 4.5 4.5L19 7.5", pathLength: 1 }) }) }), _jsxs("span", { "data-sx-file-actions": "", children: [reorder ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", "data-sx-file-up": "", "aria-label": words.up.replace("{name}", f.name), disabled: gone || disabled || at === 0, onClick: () => move(at, -1), children: _jsx(Icon, { children: _jsx("path", { d: "m6 14 6-6 6 6" }) }) }), _jsx("button", { type: "button", "data-sx-file-down": "", "aria-label": words.down.replace("{name}", f.name), disabled: gone || disabled || at === files.length - 1, onClick: () => move(at, 1), children: _jsx(Icon, { children: _jsx("path", { d: "m6 10 6 6 6-6" }) }) })] })) : null, _jsx("button", { type: "button", "data-sx-file-remove": "", "aria-label": words.remove.replace("{name}", f.name), disabled: disabled, tabIndex: gone ? -1 : undefined, onClick: () => remove(at), children: _jsx(Icon, { children: _jsx("path", { d: "m7 7 10 10M17 7 7 17" }) }) })] })] }, keyOf(f)));
|
|
316
|
+
}) })) : null, _jsx("span", { "data-sx-file-sr": "", role: "status", children: said }), _jsx("div", { "data-sx-file-message": "", children: _jsx("p", { "data-sx-file-error": "", id: ids.error, role: "alert", children: filled(message) ? (_jsx("span", { "data-sx-file-says": "", children: message }, typeof message === "string" ? message : undefined)) : null }) }), page ? (_jsxs("div", { "data-sx-file-page": "", "aria-hidden": true, children: [_jsx(Layers, { effects: effects, slot: "page", uid: uid }), _jsx("span", { "data-sx-file-page-words": "", children: words.page })] })) : null] })] }));
|
|
317
|
+
}
|
|
318
|
+
/** A card's key: the same file keeps its card through a move. */
|
|
319
|
+
const keys = new WeakMap();
|
|
320
|
+
let count = 0;
|
|
321
|
+
function keyOf(f) {
|
|
322
|
+
let k = keys.get(f);
|
|
323
|
+
if (!k) {
|
|
324
|
+
k = `f${++count}`;
|
|
325
|
+
keys.set(f, k);
|
|
326
|
+
}
|
|
327
|
+
return k;
|
|
328
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
export interface FormBuddyProps {
|
|
3
|
+
/** The form it watches, by `id`. Default: the form it sits in, else the first form beside it. */
|
|
4
|
+
readonly form?: string;
|
|
5
|
+
/** Your call on its face: `"happy"` once the form went through, `"worried"` on a failure. Default: worried while a field is flagged wrong. */
|
|
6
|
+
readonly mood?: "happy" | "worried";
|
|
7
|
+
/** How wide it is: any CSS length. Default `8rem`. */
|
|
8
|
+
readonly size?: string;
|
|
9
|
+
readonly className?: string;
|
|
10
|
+
readonly style?: CSSProperties;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Our own little character for a sign-in form: a round sprout on the form's top edge. Its eyes follow the caret in
|
|
14
|
+
* the field you type in; on a password it covers its eyes, and peeks when the password is shown; it looks worried while
|
|
15
|
+
* a field is wrong, and happy when you say so (`mood="happy"`). Decoration only: hidden from screen readers, it never
|
|
16
|
+
* takes the pointer and never covers a field. Under reduced motion its face changes without moving.
|
|
17
|
+
*
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <form id="sign-in"> <FormBuddy mood={done ? "happy" : undefined} /> <Field label="Email" … /> … </form>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function FormBuddy({ form, mood, size, className, style }: FormBuddyProps): import("react").JSX.Element;
|