@mhmo91/schmancy 0.10.15 → 0.10.17
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/custom-elements.json +2554 -3086
- package/dist/active-host-BP0zy_Y9.js +63 -0
- package/dist/{active-host-CvNYoprt.js.map → active-host-BP0zy_Y9.js.map} +1 -1
- package/dist/active-host-jH3iloCR.cjs +1 -0
- package/dist/{active-host-CcIa2tmW.cjs.map → active-host-jH3iloCR.cjs.map} +1 -1
- package/dist/agent/flow-CvG1fLW5.js.map +1 -1
- package/dist/agent/schmancy.agent.js +5694 -5500
- package/dist/agent/schmancy.agent.js.map +1 -1
- package/dist/agent/schmancy.manifest.json +971 -1189
- package/dist/agent/vendor-material-color-DcL7ZPxx.js.map +1 -1
- package/dist/{animation-CO_Csq84.cjs.map → animation-CCOIW4wJ.cjs.map} +1 -1
- package/dist/{animation-BK-8BwY8.js.map → animation-DCznELuT.js.map} +1 -1
- package/dist/{area-C_kgZZhN.js → area-ChxsDTu_.js} +2 -2
- package/dist/{area-C_kgZZhN.js.map → area-ChxsDTu_.js.map} +1 -1
- package/dist/{area-DFPtKzWy.cjs → area-Qt6yUnuA.cjs} +3 -3
- package/dist/{area-DFPtKzWy.cjs.map → area-Qt6yUnuA.cjs.map} +1 -1
- package/dist/area.cjs +1 -1
- package/dist/area.js +2 -2
- package/dist/{audio-CluX8Qpq.cjs → audio-D-TZzpXF.cjs} +1 -1
- package/dist/{audio-CluX8Qpq.cjs.map → audio-D-TZzpXF.cjs.map} +1 -1
- package/dist/{audio-DcXphulJ.js → audio-DS43uoRA.js} +1 -1
- package/dist/{audio-DcXphulJ.js.map → audio-DS43uoRA.js.map} +1 -1
- package/dist/audio.cjs +1 -1
- package/dist/audio.js +2 -2
- package/dist/{autocomplete-DWSuwSRS.js → autocomplete-CXvUjMD-.js} +46 -71
- package/dist/autocomplete-CXvUjMD-.js.map +1 -0
- package/dist/autocomplete-Ck2zbdF9.cjs +115 -0
- package/dist/autocomplete-Ck2zbdF9.cjs.map +1 -0
- package/dist/autocomplete.cjs +1 -1
- package/dist/autocomplete.js +1 -1
- package/dist/avatar.cjs +2 -2
- package/dist/avatar.cjs.map +1 -1
- package/dist/avatar.js +3 -3
- package/dist/avatar.js.map +1 -1
- package/dist/badge.cjs +1 -1
- package/dist/badge.js +1 -1
- package/dist/{boat-CZma2ojF.js → boat-Bj0wVcZi.js} +5 -5
- package/dist/{boat-CZma2ojF.js.map → boat-Bj0wVcZi.js.map} +1 -1
- package/dist/{boat-Dy6cc3hB.cjs → boat-DpFkILFF.cjs} +2 -2
- package/dist/{boat-Dy6cc3hB.cjs.map → boat-DpFkILFF.cjs.map} +1 -1
- package/dist/boat.cjs +1 -1
- package/dist/boat.js +1 -1
- package/dist/breadcrumb.cjs +3 -3
- package/dist/breadcrumb.cjs.map +1 -1
- package/dist/breadcrumb.js +2 -2
- package/dist/breadcrumb.js.map +1 -1
- package/dist/{busy-DCsqryvq.cjs → busy-CtcnclA3.cjs} +3 -3
- package/dist/{busy-DCsqryvq.cjs.map → busy-CtcnclA3.cjs.map} +1 -1
- package/dist/{busy-DeV2ByMw.js → busy-CyZSBnZP.js} +2 -2
- package/dist/{busy-DeV2ByMw.js.map → busy-CyZSBnZP.js.map} +1 -1
- package/dist/busy.cjs +1 -1
- package/dist/busy.js +1 -1
- package/dist/button.cjs +4 -4
- package/dist/button.cjs.map +1 -1
- package/dist/button.js +19 -4
- package/dist/button.js.map +1 -1
- package/dist/{card--GgSX4X5.cjs → card-Cl6jp1yX.cjs} +5 -5
- package/dist/{card--GgSX4X5.cjs.map → card-Cl6jp1yX.cjs.map} +1 -1
- package/dist/{card-BTTsHzJJ.js → card-nYZCKmOO.js} +3 -3
- package/dist/{card-BTTsHzJJ.js.map → card-nYZCKmOO.js.map} +1 -1
- package/dist/card.cjs +1 -1
- package/dist/card.js +1 -1
- package/dist/{checkbox-NNReP9s_.cjs → checkbox-BeNo0ZGt.cjs} +4 -4
- package/dist/{checkbox-Cj5j-ppk.js.map → checkbox-BeNo0ZGt.cjs.map} +1 -1
- package/dist/{checkbox-Cj5j-ppk.js → checkbox-DiUrZiyc.js} +17 -30
- package/dist/checkbox-DiUrZiyc.js.map +1 -0
- package/dist/checkbox.cjs +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/{chips-CP-CbfoZ.js → chips-CfPFXv7Z.js} +5 -5
- package/dist/{chips-CP-CbfoZ.js.map → chips-CfPFXv7Z.js.map} +1 -1
- package/dist/{chips-iporOXxK.cjs → chips-DK6m-VCM.cjs} +5 -5
- package/dist/{chips-iporOXxK.cjs.map → chips-DK6m-VCM.cjs.map} +1 -1
- package/dist/chips.cjs +1 -1
- package/dist/chips.js +2 -2
- package/dist/connectivity.cjs +2 -2
- package/dist/connectivity.cjs.map +1 -1
- package/dist/connectivity.js +3 -3
- package/dist/connectivity.js.map +1 -1
- package/dist/content-drawer.cjs +1 -1
- package/dist/content-drawer.js +1 -1
- package/dist/{context-DJTJnSK4.js.map → context-6oXCZmZN.js.map} +1 -1
- package/dist/{context-BpCETidA.cjs.map → context-CRZeiCqq.cjs.map} +1 -1
- package/dist/{cursor-glow-Bulq-38P.cjs → cursor-glow-C8LgCxpI.cjs} +1 -1
- package/dist/{cursor-glow-Bulq-38P.cjs.map → cursor-glow-C8LgCxpI.cjs.map} +1 -1
- package/dist/{cursor-glow-Ah7VXSj7.js → cursor-glow-Cs2XLDB9.js} +1 -1
- package/dist/{cursor-glow-Ah7VXSj7.js.map → cursor-glow-Cs2XLDB9.js.map} +1 -1
- package/dist/date-range-DA6anfcF.cjs +131 -0
- package/dist/date-range-DA6anfcF.cjs.map +1 -0
- package/dist/{date-range-CgNujP8r.js → date-range-DjlF2u7o.js} +124 -89
- package/dist/date-range-DjlF2u7o.js.map +1 -0
- package/dist/date-range-inline-BfYK795W.cjs +43 -0
- package/dist/{date-range-inline-D4IjOOO0.cjs.map → date-range-inline-BfYK795W.cjs.map} +1 -1
- package/dist/{date-range-inline-C2PXX_GY.js → date-range-inline-n7y_H6PJ.js} +2 -2
- package/dist/{date-range-inline-C2PXX_GY.js.map → date-range-inline-n7y_H6PJ.js.map} +1 -1
- package/dist/date-range-inline.cjs +1 -1
- package/dist/date-range-inline.js +1 -1
- package/dist/date-range.cjs +1 -1
- package/dist/date-range.js +1 -1
- package/dist/delay.cjs +2 -2
- package/dist/delay.cjs.map +1 -1
- package/dist/delay.js +3 -3
- package/dist/delay.js.map +1 -1
- package/dist/{details-DT2b3xOn.cjs → details-BdAVsLl-.cjs} +2 -2
- package/dist/{details-DT2b3xOn.cjs.map → details-BdAVsLl-.cjs.map} +1 -1
- package/dist/{details-VjaNwtfd.js → details-CS_ToAOj.js} +6 -6
- package/dist/{details-VjaNwtfd.js.map → details-CS_ToAOj.js.map} +1 -1
- package/dist/details.cjs +1 -1
- package/dist/details.js +1 -1
- package/dist/directives.cjs +1 -1
- package/dist/directives.cjs.map +1 -1
- package/dist/directives.js +5 -5
- package/dist/directives.js.map +1 -1
- package/dist/discovery.js.map +1 -1
- package/dist/{divider-BMO8pzEO.js → divider-COLK0RbT.js} +2 -2
- package/dist/{divider-BMO8pzEO.js.map → divider-COLK0RbT.js.map} +1 -1
- package/dist/{divider-BW33TZ-X.cjs → divider-CvWAnvdO.cjs} +2 -2
- package/dist/{divider-BW33TZ-X.cjs.map → divider-CvWAnvdO.cjs.map} +1 -1
- package/dist/divider.cjs +1 -1
- package/dist/divider.js +1 -1
- package/dist/dropdown.cjs +3 -3
- package/dist/dropdown.cjs.map +1 -1
- package/dist/dropdown.js +2 -2
- package/dist/dropdown.js.map +1 -1
- package/dist/{expand-DbELKKOt.js → expand-D9LzmpoV.js} +5 -5
- package/dist/{expand-DbELKKOt.js.map → expand-D9LzmpoV.js.map} +1 -1
- package/dist/{expand-_f5EUKWB.cjs → expand-r2sATPUJ.cjs} +3 -3
- package/dist/{expand-_f5EUKWB.cjs.map → expand-r2sATPUJ.cjs.map} +1 -1
- package/dist/expand.cjs +1 -1
- package/dist/expand.js +1 -1
- package/dist/float-2nHYuBx-.cjs +1 -0
- package/dist/{float-CKmd-0-t.cjs.map → float-2nHYuBx-.cjs.map} +1 -1
- package/dist/{float-B6RBb2dN.js → float-BWy39CXr.js} +2 -2
- package/dist/{float-B6RBb2dN.js.map → float-BWy39CXr.js.map} +1 -1
- package/dist/float.cjs +1 -1
- package/dist/float.js +1 -1
- package/dist/form-D1iJOLVb.js +267 -0
- package/dist/form-D1iJOLVb.js.map +1 -0
- package/dist/form-D9K1GhlP.cjs +42 -0
- package/dist/form-D9K1GhlP.cjs.map +1 -0
- package/dist/form.cjs +1 -1
- package/dist/form.js +9 -2
- package/dist/handover/agent-runtime-followups.md +1 -1
- package/dist/handover/agent-runtime-v1.md +3 -3
- package/dist/{hashContent-Bobsobip.cjs.map → hashContent-Ck6laKlk.cjs.map} +1 -1
- package/dist/{hashContent-BU6jl5ih.js.map → hashContent-dJrI-9sc.js.map} +1 -1
- package/dist/{icons-r-S17M8U.cjs → icons-BXp4vbnW.cjs} +2 -2
- package/dist/{icons-r-S17M8U.cjs.map → icons-BXp4vbnW.cjs.map} +1 -1
- package/dist/{icons-CoDo95Cu.js → icons-COrlmBPB.js} +3 -3
- package/dist/{icons-CoDo95Cu.js.map → icons-COrlmBPB.js.map} +1 -1
- package/dist/icons.cjs +1 -1
- package/dist/icons.js +1 -1
- package/dist/{iframe-P9c_qg1-.cjs → iframe-BwXj6mLp.cjs} +2 -2
- package/dist/{iframe-P9c_qg1-.cjs.map → iframe-BwXj6mLp.cjs.map} +1 -1
- package/dist/{iframe-k4oI-TIj.js → iframe-CPNsIy7k.js} +2 -2
- package/dist/{iframe-k4oI-TIj.js.map → iframe-CPNsIy7k.js.map} +1 -1
- package/dist/iframe.cjs +1 -1
- package/dist/iframe.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +60 -60
- package/dist/input-BGrF2qVq.cjs +52 -0
- package/dist/input-BGrF2qVq.cjs.map +1 -0
- package/dist/{input-D95GjINh.js → input-C1SnMNuQ.js} +103 -104
- package/dist/input-C1SnMNuQ.js.map +1 -0
- package/dist/{input-chip-DpC_XEKN.js → input-chip-CtQ0pH5b.js} +2 -2
- package/dist/{input-chip-DpC_XEKN.js.map → input-chip-CtQ0pH5b.js.map} +1 -1
- package/dist/{input-chip-D0ZXqTt5.cjs → input-chip-DZktYohr.cjs} +2 -2
- package/dist/{input-chip-D0ZXqTt5.cjs.map → input-chip-DZktYohr.cjs.map} +1 -1
- package/dist/input.cjs +1 -1
- package/dist/input.js +1 -1
- package/dist/json.cjs +2 -2
- package/dist/json.cjs.map +1 -1
- package/dist/json.js +3 -3
- package/dist/json.js.map +1 -1
- package/dist/kbd.cjs +2 -2
- package/dist/kbd.cjs.map +1 -1
- package/dist/kbd.js +2 -2
- package/dist/kbd.js.map +1 -1
- package/dist/{layout-CXPNsUIo.js → layout-BH28sKGc.js} +1 -1
- package/dist/{layout-CXPNsUIo.js.map → layout-BH28sKGc.js.map} +1 -1
- package/dist/{layout-Zhe7wSZ_.cjs → layout-Delq-QvR.cjs} +1 -1
- package/dist/{layout-Zhe7wSZ_.cjs.map → layout-Delq-QvR.cjs.map} +1 -1
- package/dist/layout.cjs +1 -1
- package/dist/layout.js +1 -1
- package/dist/{lazy-Dq9mRRjT.cjs.map → lazy-CayEFyC3.cjs.map} +1 -1
- package/dist/{lazy-B0ia54tT.js.map → lazy-D-bO2r4m.js.map} +1 -1
- package/dist/{lightbox-CovQtmyn.js → lightbox-CLwpaiai.js} +9 -9
- package/dist/{lightbox-CovQtmyn.js.map → lightbox-CLwpaiai.js.map} +1 -1
- package/dist/{lightbox-C-yHeoK0.cjs → lightbox-Ck6BpN5u.cjs} +3 -3
- package/dist/{lightbox-C-yHeoK0.cjs.map → lightbox-Ck6BpN5u.cjs.map} +1 -1
- package/dist/lightbox.cjs +1 -1
- package/dist/lightbox.js +1 -1
- package/dist/{list-C1pR9vhu.js → list-Bmce1Rb8.js} +2 -2
- package/dist/{list-C1pR9vhu.js.map → list-Bmce1Rb8.js.map} +1 -1
- package/dist/{list-CAijuky4.cjs → list-EmRwSpTU.cjs} +3 -3
- package/dist/{list-CAijuky4.cjs.map → list-EmRwSpTU.cjs.map} +1 -1
- package/dist/list.cjs +1 -1
- package/dist/list.js +1 -1
- package/dist/{magnetic-BJgB1dVi.cjs → magnetic-Bgh7aHHI.cjs} +1 -1
- package/dist/{magnetic-BJgB1dVi.cjs.map → magnetic-Bgh7aHHI.cjs.map} +1 -1
- package/dist/{magnetic-YwCNvtbB.js → magnetic-DxvoEz8_.js} +2 -2
- package/dist/{magnetic-YwCNvtbB.js.map → magnetic-DxvoEz8_.js.map} +1 -1
- package/dist/{menu-B59vZv9n.js → menu-BA_B7QOG.js} +3 -3
- package/dist/{menu-B59vZv9n.js.map → menu-BA_B7QOG.js.map} +1 -1
- package/dist/{menu-BaHO3Cip.cjs → menu-BTU3wGP6.cjs} +3 -3
- package/dist/{menu-BaHO3Cip.cjs.map → menu-BTU3wGP6.cjs.map} +1 -1
- package/dist/menu.cjs +1 -1
- package/dist/menu.js +1 -1
- package/dist/mixins-BOOu6q2n.cjs +298 -0
- package/dist/mixins-BOOu6q2n.cjs.map +1 -0
- package/dist/mixins-BWb9_e1s.js +680 -0
- package/dist/mixins-BWb9_e1s.js.map +1 -0
- package/dist/mixins.cjs +1 -1
- package/dist/mixins.js +2 -2
- package/dist/nav-drawer.cjs +1 -1
- package/dist/nav-drawer.js +1 -1
- package/dist/navigation-bar.cjs +1 -1
- package/dist/navigation-bar.js +1 -1
- package/dist/navigation-rail.cjs +3 -3
- package/dist/navigation-rail.cjs.map +1 -1
- package/dist/navigation-rail.js +2 -2
- package/dist/navigation-rail.js.map +1 -1
- package/dist/notification-CliGbcfU.cjs +23 -0
- package/dist/{notification-BC9nG8Sr.cjs.map → notification-CliGbcfU.cjs.map} +1 -1
- package/dist/{notification-BeLoVa47.js → notification-R2_Mf1HR.js} +4 -4
- package/dist/{notification-BeLoVa47.js.map → notification-R2_Mf1HR.js.map} +1 -1
- package/dist/notification.cjs +1 -1
- package/dist/notification.js +1 -1
- package/dist/{option-UvlSAcC4.js → option-DU1X4SDu.js} +2 -2
- package/dist/{option-UvlSAcC4.js.map → option-DU1X4SDu.js.map} +1 -1
- package/dist/{option-BWF4GBp-.cjs → option-Db98Ndzv.cjs} +2 -2
- package/dist/{option-BWF4GBp-.cjs.map → option-Db98Ndzv.cjs.map} +1 -1
- package/dist/option.cjs +1 -1
- package/dist/option.js +1 -1
- package/dist/{overlay-stack-DCDS17uj.js.map → overlay-stack-BR4iYivO.js.map} +1 -1
- package/dist/{overlay-stack-DPIe_aYv.cjs.map → overlay-stack-Dk0xETTy.cjs.map} +1 -1
- package/dist/overlay.cjs +2 -2
- package/dist/overlay.cjs.map +1 -1
- package/dist/{overlay.confirm-body-URtE1gI3.cjs → overlay.confirm-body-BkhNvr0c.cjs} +2 -2
- package/dist/{overlay.confirm-body-URtE1gI3.cjs.map → overlay.confirm-body-BkhNvr0c.cjs.map} +1 -1
- package/dist/{overlay.confirm-body-9W0B5QGv.js → overlay.confirm-body-uFp-0Zfh.js} +2 -2
- package/dist/{overlay.confirm-body-9W0B5QGv.js.map → overlay.confirm-body-uFp-0Zfh.js.map} +1 -1
- package/dist/overlay.js +8 -8
- package/dist/overlay.js.map +1 -1
- package/dist/{overlay.service-DnZTcKyJ.cjs → overlay.service-1YWfUD2S.cjs} +1 -1
- package/dist/{overlay.service-DnZTcKyJ.cjs.map → overlay.service-1YWfUD2S.cjs.map} +1 -1
- package/dist/{overlay.service-CVqs2Gu1.js → overlay.service-BcF12kGb.js} +2 -2
- package/dist/{overlay.service-CVqs2Gu1.js.map → overlay.service-BcF12kGb.js.map} +1 -1
- package/dist/page.cjs +2 -2
- package/dist/page.cjs.map +1 -1
- package/dist/page.js +5 -5
- package/dist/page.js.map +1 -1
- package/dist/{progress-C29Uw-WJ.js → progress-C9Y2D5cm.js} +2 -2
- package/dist/{progress-C29Uw-WJ.js.map → progress-C9Y2D5cm.js.map} +1 -1
- package/dist/{progress-CwzwY8Oe.cjs → progress-DiVTGAXa.cjs} +2 -2
- package/dist/{progress-CwzwY8Oe.cjs.map → progress-DiVTGAXa.cjs.map} +1 -1
- package/dist/progress.cjs +1 -1
- package/dist/progress.js +1 -1
- package/dist/{radio-group-CW8airhZ.js → radio-group-CAzjBI2n.js} +4 -4
- package/dist/radio-group-CAzjBI2n.js.map +1 -0
- package/dist/radio-group-DIRJyYv6.cjs +40 -0
- package/dist/radio-group-DIRJyYv6.cjs.map +1 -0
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/range.cjs +6 -4
- package/dist/range.cjs.map +1 -1
- package/dist/range.js +19 -15
- package/dist/range.js.map +1 -1
- package/dist/{reduced-motion-D-L12p7G.js.map → reduced-motion-D7LqTUMn.js.map} +1 -1
- package/dist/{reduced-motion-Ds-HjMzn.cjs.map → reduced-motion-Dzfp_w5x.cjs.map} +1 -1
- package/dist/{rxjs-utils-DCUHg_Ml.cjs.map → rxjs-utils-BKB2UM_j.cjs.map} +1 -1
- package/dist/{rxjs-utils-CVeJQ9KG.js.map → rxjs-utils-Dv9T9IpA.js.map} +1 -1
- package/dist/rxjs-utils.cjs +1 -1
- package/dist/rxjs-utils.js +1 -1
- package/dist/{scroll-BotoGcMU.js → scroll-BFHUtZOa.js} +2 -2
- package/dist/{scroll-BotoGcMU.js.map → scroll-BFHUtZOa.js.map} +1 -1
- package/dist/{scroll-CmhmUebp.cjs → scroll-nIZyoEMt.cjs} +2 -2
- package/dist/{scroll-CmhmUebp.cjs.map → scroll-nIZyoEMt.cjs.map} +1 -1
- package/dist/{search-BLCRsxIC.cjs.map → search-DPKoC-dT.cjs.map} +1 -1
- package/dist/{search-BTz7-Rev.js.map → search-MvIBA93K.js.map} +1 -1
- package/dist/{select-Dbn-CImU.js → select-7WqaUWBU.js} +52 -73
- package/dist/select-7WqaUWBU.js.map +1 -0
- package/dist/select-DTuf6p6T.cjs +56 -0
- package/dist/select-DTuf6p6T.cjs.map +1 -0
- package/dist/select.cjs +1 -1
- package/dist/select.js +1 -1
- package/dist/skeleton.cjs +2 -2
- package/dist/skeleton.cjs.map +1 -1
- package/dist/skeleton.js +2 -2
- package/dist/skeleton.js.map +1 -1
- package/dist/skills/SKILL.md +3 -0
- package/dist/skills/autocomplete.md +16 -3
- package/dist/skills/button.md +19 -0
- package/dist/skills/checkbox.md +19 -0
- package/dist/skills/date-range.md +19 -0
- package/dist/skills/form-ux-rules.md +55 -0
- package/dist/skills/form.md +121 -25
- package/dist/skills/input.md +19 -4
- package/dist/skills/range.md +15 -1
- package/dist/skills/schmancy/SKILL.md +3 -0
- package/dist/skills/schmancy/autocomplete.md +16 -3
- package/dist/skills/schmancy/button.md +19 -0
- package/dist/skills/schmancy/checkbox.md +19 -0
- package/dist/skills/schmancy/date-range.md +19 -0
- package/dist/skills/schmancy/form-ux-rules.md +55 -0
- package/dist/skills/schmancy/form.md +121 -25
- package/dist/skills/schmancy/input.md +19 -4
- package/dist/skills/schmancy/range.md +15 -1
- package/dist/skills/schmancy/select.md +13 -1
- package/dist/skills/schmancy/switch.md +21 -2
- package/dist/skills/schmancy/textarea.md +13 -0
- package/dist/skills/select.md +13 -1
- package/dist/skills/switch.md +21 -2
- package/dist/skills/textarea.md +13 -0
- package/dist/slider.cjs +3 -3
- package/dist/slider.cjs.map +1 -1
- package/dist/slider.js +2 -2
- package/dist/slider.js.map +1 -1
- package/dist/{sound.service-kKfsN0m-.js → sound.service-BIN2W7Rv.js} +1 -1
- package/dist/{sound.service-kKfsN0m-.js.map → sound.service-BIN2W7Rv.js.map} +1 -1
- package/dist/{sound.service-BGs6m0Cm.cjs → sound.service-DyY78ukR.cjs} +1 -1
- package/dist/{sound.service-BGs6m0Cm.cjs.map → sound.service-DyY78ukR.cjs.map} +1 -1
- package/dist/{splash-screen-DtkjCJYo.js → splash-screen-BcjjJSlK.js} +2 -2
- package/dist/{splash-screen-DtkjCJYo.js.map → splash-screen-BcjjJSlK.js.map} +1 -1
- package/dist/{splash-screen-DlQUv-kV.cjs → splash-screen-Kr1sPtME.cjs} +2 -2
- package/dist/{splash-screen-DlQUv-kV.cjs.map → splash-screen-Kr1sPtME.cjs.map} +1 -1
- package/dist/splash-screen.cjs +1 -1
- package/dist/splash-screen.js +1 -1
- package/dist/{src-DEUjlTsX.cjs → src-BbMJeLk9.cjs} +11 -11
- package/dist/{src-DEUjlTsX.cjs.map → src-BbMJeLk9.cjs.map} +1 -1
- package/dist/{src-D6e0adHi.js → src-DCu_mEk4.js} +40 -40
- package/dist/{src-D6e0adHi.js.map → src-DCu_mEk4.js.map} +1 -1
- package/dist/state-avic94Ft.cjs +1 -0
- package/dist/{state-DNdCPITt.cjs.map → state-avic94Ft.cjs.map} +1 -1
- package/dist/{state-BusMG6sM.js → state-nm8yzMPp.js} +1 -2
- package/dist/{state-BusMG6sM.js.map → state-nm8yzMPp.js.map} +1 -1
- package/dist/state.cjs +1 -1
- package/dist/state.js +2 -2
- package/dist/steps.cjs +3 -3
- package/dist/steps.cjs.map +1 -1
- package/dist/steps.js +2 -2
- package/dist/steps.js.map +1 -1
- package/dist/{surface-A82O1kgu.js → surface-BtMMHKol.js} +2 -2
- package/dist/{surface-A82O1kgu.js.map → surface-BtMMHKol.js.map} +1 -1
- package/dist/surface-CgXeKdGL.cjs +7 -0
- package/dist/{surface-BpppoNXN.cjs.map → surface-CgXeKdGL.cjs.map} +1 -1
- package/dist/surface.cjs +1 -1
- package/dist/surface.js +1 -1
- package/dist/switch.cjs +3 -3
- package/dist/switch.cjs.map +1 -1
- package/dist/switch.js +27 -43
- package/dist/switch.js.map +1 -1
- package/dist/table.cjs +3 -3
- package/dist/table.cjs.map +1 -1
- package/dist/table.js +2 -2
- package/dist/table.js.map +1 -1
- package/dist/{tabs-cVHHd1dY.js → tabs-81ADWQqa.js} +2 -2
- package/dist/{tabs-cVHHd1dY.js.map → tabs-81ADWQqa.js.map} +1 -1
- package/dist/{tabs-TO3UiBsm.cjs → tabs-DnG3K0bu.cjs} +2 -2
- package/dist/{tabs-TO3UiBsm.cjs.map → tabs-DnG3K0bu.cjs.map} +1 -1
- package/dist/tabs.cjs +1 -1
- package/dist/tabs.js +1 -1
- package/dist/teleport.cjs +1 -1
- package/dist/teleport.js +1 -1
- package/dist/textarea-3mWewuAf.js +186 -0
- package/dist/textarea-3mWewuAf.js.map +1 -0
- package/dist/textarea-BenjiTXB.cjs +43 -0
- package/dist/textarea-BenjiTXB.cjs.map +1 -0
- package/dist/textarea.cjs +1 -1
- package/dist/textarea.js +1 -1
- package/dist/{theme-CT408FqH.js → theme-CFPJW933.js} +9 -9
- package/dist/{theme-CT408FqH.js.map → theme-CFPJW933.js.map} +1 -1
- package/dist/theme-DNymrucy.cjs +181 -0
- package/dist/{theme-CpuF3D3q.cjs.map → theme-DNymrucy.cjs.map} +1 -1
- package/dist/{theme-button-pTb5-Wxx.js → theme-button-DC_shZ_7.js} +2 -2
- package/dist/{theme-button-pTb5-Wxx.js.map → theme-button-DC_shZ_7.js.map} +1 -1
- package/dist/theme-button-ENKa3TPT.cjs +8 -0
- package/dist/{theme-button-B6Xf-EiH.cjs.map → theme-button-ENKa3TPT.cjs.map} +1 -1
- package/dist/theme-button.cjs +1 -1
- package/dist/theme-button.js +1 -1
- package/dist/theme.cjs +1 -1
- package/dist/{theme.interface-B9TjbSBF.js.map → theme.interface-C2XNgsLB.js.map} +1 -1
- package/dist/{theme.interface-BujperTo.cjs.map → theme.interface-D4NeufQA.cjs.map} +1 -1
- package/dist/theme.js +4 -4
- package/dist/{theme.service-DIUo1mBP.js → theme.service-BOWIT_5k.js} +1 -1
- package/dist/{theme.service-DIUo1mBP.js.map → theme.service-BOWIT_5k.js.map} +1 -1
- package/dist/{theme.service-Cfk88qHK.cjs → theme.service-DkdH1t60.cjs} +1 -1
- package/dist/{theme.service-Cfk88qHK.cjs.map → theme.service-DkdH1t60.cjs.map} +1 -1
- package/dist/tooltip.js.map +1 -1
- package/dist/tree.cjs +2 -2
- package/dist/tree.cjs.map +1 -1
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/typewriter.cjs.map +1 -1
- package/dist/typewriter.js.map +1 -1
- package/dist/typography.cjs +2 -2
- package/dist/typography.cjs.map +1 -1
- package/dist/typography.js +2 -2
- package/dist/typography.js.map +1 -1
- package/dist/{utils-kND2Z9Xg.js → utils-Cj_nRRyx.js} +2 -2
- package/dist/{utils-kND2Z9Xg.js.map → utils-Cj_nRRyx.js.map} +1 -1
- package/dist/{utils-Dt5PpmaQ.cjs → utils-D2QUu4-g.cjs} +1 -1
- package/dist/{utils-Dt5PpmaQ.cjs.map → utils-D2QUu4-g.cjs.map} +1 -1
- package/dist/utils.cjs +1 -1
- package/dist/utils.js +4 -4
- package/dist/visually-hidden.cjs +2 -2
- package/dist/visually-hidden.cjs.map +1 -1
- package/dist/visually-hidden.js +2 -2
- package/dist/visually-hidden.js.map +1 -1
- package/dist/{window-CuBcOxbc.js → window-BTecgE_U.js} +7 -7
- package/dist/{window-CuBcOxbc.js.map → window-BTecgE_U.js.map} +1 -1
- package/dist/{window-CSKvv4Ts.cjs → window-DGydMS0g.cjs} +2 -2
- package/dist/{window-CSKvv4Ts.cjs.map → window-DGydMS0g.cjs.map} +1 -1
- package/dist/window.cjs +1 -1
- package/dist/window.js +1 -1
- package/package.json +1 -1
- package/skills/schmancy/SKILL.md +3 -0
- package/skills/schmancy/autocomplete.md +16 -3
- package/skills/schmancy/button.md +19 -0
- package/skills/schmancy/checkbox.md +19 -0
- package/skills/schmancy/date-range.md +19 -0
- package/skills/schmancy/form-ux-rules.md +55 -0
- package/skills/schmancy/form.md +121 -25
- package/skills/schmancy/input.md +19 -4
- package/skills/schmancy/range.md +15 -1
- package/skills/schmancy/select.md +13 -1
- package/skills/schmancy/switch.md +21 -2
- package/skills/schmancy/textarea.md +13 -0
- package/src/button/button.test.ts +122 -0
- package/src/button/button.ts +36 -0
- package/src/{autocomplete → form/fields/autocomplete}/autocomplete.ts +48 -75
- package/src/{checkbox → form/fields/checkbox}/checkbox.test.ts +1 -1
- package/src/form/fields/checkbox/checkbox.ts +126 -0
- package/src/form/fields/date-range/date-range.test.ts +102 -0
- package/src/{date-range → form/fields/date-range}/date-range.ts +90 -7
- package/src/form/fields/index.ts +9 -0
- package/src/form/fields/input/input.test.ts +201 -0
- package/src/{input → form/fields/input}/input.ts +153 -238
- package/src/{radio-group → form/fields/radio-group}/radio-button.ts +1 -1
- package/src/{radio-group → form/fields/radio-group}/radio-group.ts +1 -1
- package/src/form/fields/range/range.test.ts +90 -0
- package/src/{range → form/fields/range}/range.ts +34 -13
- package/src/{select → form/fields/select}/select.ts +77 -108
- package/src/{switch → form/fields/switch}/switch.test.ts +1 -1
- package/src/{switch → form/fields/switch}/switch.ts +71 -51
- package/src/form/fields/textarea/textarea.test.ts +54 -0
- package/src/{textarea → form/fields/textarea}/textarea.ts +33 -72
- package/src/form/form-state.ts +31 -0
- package/src/form/form-summary.test.ts +105 -0
- package/src/form/form-summary.ts +171 -0
- package/src/form/form.test.ts +218 -35
- package/src/form/form.ts +330 -99
- package/src/form/index.ts +3 -0
- package/src/index.ts +9 -9
- package/types/mixins/formField.mixin.d.ts +90 -0
- package/types/src/button/button.d.ts +9 -0
- package/types/src/button/button.test.d.ts +3 -0
- package/types/src/{autocomplete → form/fields/autocomplete}/autocomplete.d.ts +6 -15
- package/types/src/form/fields/checkbox/checkbox.d.ts +47 -0
- package/types/src/{date-range → form/fields/date-range}/date-range.d.ts +22 -4
- package/types/src/form/fields/date-range/date-range.test.d.ts +1 -0
- package/types/src/form/fields/index.d.ts +9 -0
- package/types/src/{input → form/fields/input}/input.d.ts +20 -45
- package/types/src/form/fields/input/input.test.d.ts +1 -0
- package/types/src/{radio-group → form/fields/radio-group}/radio-button.d.ts +1 -1
- package/types/src/{radio-group → form/fields/radio-group}/radio-group.d.ts +1 -1
- package/types/src/form/fields/range/range.d.ts +28 -0
- package/types/src/form/fields/range/range.test.d.ts +1 -0
- package/types/src/{select → form/fields/select}/select.d.ts +23 -24
- package/types/src/form/fields/switch/switch.d.ts +57 -0
- package/types/src/{textarea → form/fields/textarea}/textarea.d.ts +6 -39
- package/types/src/form/fields/textarea/textarea.test.d.ts +1 -0
- package/types/src/form/form-state.d.ts +22 -0
- package/types/src/form/form-summary.d.ts +42 -0
- package/types/src/form/form-summary.test.d.ts +4 -0
- package/types/src/form/form.d.ts +79 -34
- package/types/src/form/form.test.d.ts +2 -2
- package/types/src/form/index.d.ts +3 -0
- package/types/src/index.d.ts +9 -9
- package/dist/active-host-CcIa2tmW.cjs +0 -1
- package/dist/active-host-CvNYoprt.js +0 -57
- package/dist/autocomplete-DWSuwSRS.js.map +0 -1
- package/dist/autocomplete-iCJOia-q.cjs +0 -115
- package/dist/autocomplete-iCJOia-q.cjs.map +0 -1
- package/dist/checkbox-NNReP9s_.cjs.map +0 -1
- package/dist/date-range-CaOxwZDq.cjs +0 -131
- package/dist/date-range-CaOxwZDq.cjs.map +0 -1
- package/dist/date-range-CgNujP8r.js.map +0 -1
- package/dist/date-range-inline-D4IjOOO0.cjs +0 -43
- package/dist/decorate-23nYs4Le.js +0 -7
- package/dist/decorate-DpFmy0nm.cjs +0 -1
- package/dist/float-CKmd-0-t.cjs +0 -1
- package/dist/form-CFvwnfuJ.js +0 -68
- package/dist/form-CFvwnfuJ.js.map +0 -1
- package/dist/form-Ceijw1aA.cjs +0 -1
- package/dist/form-Ceijw1aA.cjs.map +0 -1
- package/dist/input-D95GjINh.js.map +0 -1
- package/dist/input-D9s4jDAb.cjs +0 -51
- package/dist/input-D9s4jDAb.cjs.map +0 -1
- package/dist/mixins-BV0w2yIE.js +0 -627
- package/dist/mixins-BV0w2yIE.js.map +0 -1
- package/dist/mixins-DvAYa-F7.cjs +0 -298
- package/dist/mixins-DvAYa-F7.cjs.map +0 -1
- package/dist/notification-BC9nG8Sr.cjs +0 -23
- package/dist/radio-group-ByMD6Lsj.cjs +0 -40
- package/dist/radio-group-ByMD6Lsj.cjs.map +0 -1
- package/dist/radio-group-CW8airhZ.js.map +0 -1
- package/dist/select-BdBThja4.cjs +0 -56
- package/dist/select-BdBThja4.cjs.map +0 -1
- package/dist/select-Dbn-CImU.js.map +0 -1
- package/dist/state-DNdCPITt.cjs +0 -1
- package/dist/surface-BpppoNXN.cjs +0 -7
- package/dist/textarea-B9dy-yec.js +0 -211
- package/dist/textarea-B9dy-yec.js.map +0 -1
- package/dist/textarea-DFY0Flgv.cjs +0 -39
- package/dist/textarea-DFY0Flgv.cjs.map +0 -1
- package/dist/theme-CpuF3D3q.cjs +0 -181
- package/dist/theme-button-B6Xf-EiH.cjs +0 -8
- package/src/checkbox/checkbox.ts +0 -162
- package/types/src/checkbox/checkbox.d.ts +0 -71
- package/types/src/range/range.d.ts +0 -25
- package/types/src/switch/switch.d.ts +0 -53
- /package/dist/{animation-CO_Csq84.cjs → animation-CCOIW4wJ.cjs} +0 -0
- /package/dist/{animation-BK-8BwY8.js → animation-DCznELuT.js} +0 -0
- /package/dist/{context-DJTJnSK4.js → context-6oXCZmZN.js} +0 -0
- /package/dist/{context-BpCETidA.cjs → context-CRZeiCqq.cjs} +0 -0
- /package/dist/{hashContent-Bobsobip.cjs → hashContent-Ck6laKlk.cjs} +0 -0
- /package/dist/{hashContent-BU6jl5ih.js → hashContent-dJrI-9sc.js} +0 -0
- /package/dist/{lazy-Dq9mRRjT.cjs → lazy-CayEFyC3.cjs} +0 -0
- /package/dist/{lazy-B0ia54tT.js → lazy-D-bO2r4m.js} +0 -0
- /package/dist/{overlay-stack-DCDS17uj.js → overlay-stack-BR4iYivO.js} +0 -0
- /package/dist/{overlay-stack-DPIe_aYv.cjs → overlay-stack-Dk0xETTy.cjs} +0 -0
- /package/dist/{reduced-motion-D-L12p7G.js → reduced-motion-D7LqTUMn.js} +0 -0
- /package/dist/{reduced-motion-Ds-HjMzn.cjs → reduced-motion-Dzfp_w5x.cjs} +0 -0
- /package/dist/{rxjs-utils-DCUHg_Ml.cjs → rxjs-utils-BKB2UM_j.cjs} +0 -0
- /package/dist/{rxjs-utils-CVeJQ9KG.js → rxjs-utils-Dv9T9IpA.js} +0 -0
- /package/dist/{search-BLCRsxIC.cjs → search-DPKoC-dT.cjs} +0 -0
- /package/dist/{search-BTz7-Rev.js → search-MvIBA93K.js} +0 -0
- /package/dist/{theme.interface-B9TjbSBF.js → theme.interface-C2XNgsLB.js} +0 -0
- /package/dist/{theme.interface-BujperTo.cjs → theme.interface-D4NeufQA.cjs} +0 -0
- /package/src/{autocomplete → form/fields/autocomplete}/autocomplete.scss +0 -0
- /package/src/{autocomplete → form/fields/autocomplete}/index.ts +0 -0
- /package/src/{checkbox → form/fields/checkbox}/index.ts +0 -0
- /package/src/{date-range → form/fields/date-range}/date-range-dialog.ts +0 -0
- /package/src/{date-range → form/fields/date-range}/date-range-helpers.ts +0 -0
- /package/src/{date-range → form/fields/date-range}/date-range-presets.ts +0 -0
- /package/src/{date-range → form/fields/date-range}/date-utils.ts +0 -0
- /package/src/{date-range → form/fields/date-range}/index.ts +0 -0
- /package/src/{input → form/fields/input}/index.ts +0 -0
- /package/src/{input → form/fields/input}/input.scss +0 -0
- /package/src/{radio-group → form/fields/radio-group}/index.ts +0 -0
- /package/src/{radio-group → form/fields/radio-group}/radio-group.scss +0 -0
- /package/src/{range → form/fields/range}/index.ts +0 -0
- /package/src/{select → form/fields/select}/index.ts +0 -0
- /package/src/{switch → form/fields/switch}/index.ts +0 -0
- /package/src/{textarea → form/fields/textarea}/index.ts +0 -0
- /package/src/{textarea → form/fields/textarea}/textarea.scss +0 -0
- /package/types/src/{autocomplete → form/fields/autocomplete}/index.d.ts +0 -0
- /package/types/src/{checkbox → form/fields/checkbox}/checkbox.test.d.ts +0 -0
- /package/types/src/{checkbox → form/fields/checkbox}/index.d.ts +0 -0
- /package/types/src/{date-range → form/fields/date-range}/date-range-dialog.d.ts +0 -0
- /package/types/src/{date-range → form/fields/date-range}/date-range-helpers.d.ts +0 -0
- /package/types/src/{date-range → form/fields/date-range}/date-range-presets.d.ts +0 -0
- /package/types/src/{date-range → form/fields/date-range}/date-utils.d.ts +0 -0
- /package/types/src/{date-range → form/fields/date-range}/index.d.ts +0 -0
- /package/types/src/{input → form/fields/input}/index.d.ts +0 -0
- /package/types/src/{radio-group → form/fields/radio-group}/index.d.ts +0 -0
- /package/types/src/{range → form/fields/range}/index.d.ts +0 -0
- /package/types/src/{select → form/fields/select}/index.d.ts +0 -0
- /package/types/src/{switch → form/fields/switch}/index.d.ts +0 -0
- /package/types/src/{switch → form/fields/switch}/switch.test.d.ts +0 -0
- /package/types/src/{textarea → form/fields/textarea}/index.d.ts +0 -0
package/dist/skills/button.md
CHANGED
|
@@ -40,6 +40,25 @@
|
|
|
40
40
|
- Hover: luminous glow shadow using primary color
|
|
41
41
|
- Active: spring press `scale(0.97)` / `scale(0.92)` for icon-button
|
|
42
42
|
|
|
43
|
+
## Submit-state mirroring (`type="submit"` only)
|
|
44
|
+
|
|
45
|
+
A `<schmancy-button type="submit">` inside a `<schmancy-form>` automatically reflects the form's submit state via a MutationObserver on the form's `aria-busy` attribute:
|
|
46
|
+
|
|
47
|
+
- While the form's `formSubmitState.status === 'submitting'` (form sets `aria-busy="true"`):
|
|
48
|
+
- Button mirrors `aria-busy="true"` onto itself.
|
|
49
|
+
- Broadcasts `:state(submitting)` for CSS targeting.
|
|
50
|
+
- **Stays focusable** — never gets the `disabled` attribute. Disabled buttons drop from the tab order; AT users would lose their place. WCAG 2.2 AA.
|
|
51
|
+
- Cleared automatically when the form's submit promise settles (success or error).
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
schmancy-button:state(submitting) {
|
|
55
|
+
opacity: 0.6;
|
|
56
|
+
/* show a spinner via ::before, swap label, etc. */
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`schmancy-button` is form-associated (`static formAssociated = true` + `attachInternals()`) — clicking a `type="submit"` inside any `<form>` ancestor (native or schmancy) calls `form.requestSubmit()`. Inside `<schmancy-form>` the click is also intercepted at the form host and bridged across the shadow boundary to the inner shadow-DOM `<form>`.
|
|
61
|
+
|
|
43
62
|
## Examples
|
|
44
63
|
```html
|
|
45
64
|
<!-- Filled primary -->
|
package/dist/skills/checkbox.md
CHANGED
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
| disabled | boolean | `false` | Whether the checkbox is disabled |
|
|
19
19
|
| required | boolean | `false` | Whether the checkbox is required |
|
|
20
20
|
| size | `'xxs'\|'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Checkbox size |
|
|
21
|
+
| validateOn | `'always'\|'touched'\|'dirty'\|'submitted'` | `'dirty'` | When validation errors display. From `SchmancyFormField`. |
|
|
22
|
+
| validationMessage | string | `''` | Error text — default `'Please check this box if you want to proceed.'` when required and unchecked. |
|
|
23
|
+
| error | boolean | `false` | Error state (gated by `validateOn`). |
|
|
24
|
+
| hint | string | `undefined` | Helper text. |
|
|
25
|
+
| touched / dirty / submitted | boolean | — | Validation state from `SchmancyFormField`. |
|
|
26
|
+
|
|
27
|
+
## Attributes
|
|
28
|
+
- `true-value` — string written to FormData when checked. Default `'on'` (matches native checkbox semantics).
|
|
21
29
|
|
|
22
30
|
## Events
|
|
23
31
|
| Event | Detail | Description |
|
|
@@ -33,4 +41,15 @@
|
|
|
33
41
|
<schmancy-checkbox name="terms" required>
|
|
34
42
|
Accept <a href="/terms">terms and conditions</a>
|
|
35
43
|
</schmancy-checkbox>
|
|
44
|
+
|
|
45
|
+
<!-- Custom FormData string -->
|
|
46
|
+
<schmancy-checkbox name="newsletter" true-value="opt-in"></schmancy-checkbox>
|
|
36
47
|
```
|
|
48
|
+
|
|
49
|
+
## Form-field contract
|
|
50
|
+
|
|
51
|
+
Extends `SchmancyFormField()`. `value` is the boolean state; FormData submission uses the `true-value` attribute (or `'on'`). Validity overrides the mixin's "non-empty value" semantics: required + unchecked → `internals.setValidity({ valueMissing: true })`. ARIA `aria-invalid` / `aria-required` reflect through `ElementInternals`.
|
|
52
|
+
|
|
53
|
+
Public API: `markTouched()`, `markSubmitted()`, `checkValidity()`, `setCustomValidity()`, `resetForm()`. Auto-discovered by `<schmancy-form>` via `FIELD_CONNECT_EVENT`.
|
|
54
|
+
|
|
55
|
+
See `form.md` and `form-ux-rules.md` for the binding 4-phase validation contract.
|
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
| collapse | boolean | `false` | Icon-only on mobile |
|
|
28
28
|
| customPresets | array | `[]` | Additional preset ranges |
|
|
29
29
|
| format | string | auto | Date format string |
|
|
30
|
+
| name | string | `''` | Form submission name. From `SchmancyFormField`. |
|
|
31
|
+
| validateOn | `'always'\|'touched'\|'dirty'\|'submitted'` | `'dirty'` | When validation errors display. |
|
|
32
|
+
| validationMessage | string | `''` | Error message — default `'Please select a date range.'` when required and either bound is empty. |
|
|
33
|
+
| error | boolean | `false` | Error state (gated by `validateOn`). |
|
|
34
|
+
| hint | string | `undefined` | Helper text. |
|
|
35
|
+
| touched / dirty / submitted | boolean | — | Validation state. `dirty` tracks `dateFrom.value` / `dateTo.value` against the snapshots taken at first render. |
|
|
30
36
|
|
|
31
37
|
## Events
|
|
32
38
|
| Event | Detail | Description |
|
|
@@ -48,3 +54,16 @@
|
|
|
48
54
|
```
|
|
49
55
|
|
|
50
56
|
Built-in presets include Today, Yesterday, This Week, This Month, This Year, and more.
|
|
57
|
+
|
|
58
|
+
## Form association
|
|
59
|
+
|
|
60
|
+
Extends `SchmancyFormField()`. Multi-entry FormData contribution:
|
|
61
|
+
- Set `name="dates"` and FormData receives **two** keys: `datesFrom` and `datesTo` (flat suffix shape, no bracket-key encoding).
|
|
62
|
+
- `internals.setFormValue(formData)` accepts a `FormData` object whose entries get appended to the parent form — native `new FormData(form)` sees both keys without consumer-side parsing.
|
|
63
|
+
- `<schmancy-form>`'s registry consumer also sees both keys via `toFormEntries()`.
|
|
64
|
+
|
|
65
|
+
`resetForm()` restores both `dateFrom.value` and `dateTo.value` to the snapshots captured at first render. Validity: required + either bound empty → `internals.setValidity({ valueMissing: true })`. Order/range constraints (start ≤ end, future-only, etc.) are out of scope — handle those in a domain schema layer.
|
|
66
|
+
|
|
67
|
+
Auto-discovered by `<schmancy-form>` via `FIELD_CONNECT_EVENT`. Public API: `markTouched()`, `markSubmitted()`, `checkValidity()`, `setCustomValidity()`, `resetForm()`.
|
|
68
|
+
|
|
69
|
+
See `form.md` and `form-ux-rules.md` for the binding 4-phase validation contract.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Form UX rules — Revolute pattern → schmancy mixin API
|
|
2
|
+
|
|
3
|
+
This is the mapping between the Revolute interview-bar form UX (the source-of-truth checklist for state-of-the-art form behaviour) and the `SchmancyFormField()` API that implements each rule. When you hit a UX question, find the rule here and use the named API — don't re-derive from scratch.
|
|
4
|
+
|
|
5
|
+
## The four-phase validation contract
|
|
6
|
+
|
|
7
|
+
| Phase | UX rule | Implementation |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 1 — Pristine | A field the user hasn't typed in shows no error, even if focused-then-blurred. The autofocus+cancel anti-pattern (focus a required field, click cancel, see "required") is forbidden. | `validateOn: 'dirty'` (default). `_shouldShowError()` returns `false` while `dirty === false`. |
|
|
10
|
+
| 2 — Dirty | User types and blurs → validate the field. Show error only if invalid. Empty required fields blurred-without-typing remain pristine. | `markTouched()` called on blur. Mixin's `willUpdate` sees `touched` change, sees `dirty` is true, calls `checkValidity()` which sets `this.error` because the gate is open. |
|
|
11
|
+
| 3 — Live correction | User returns to a field with an error → re-validate on every keystroke. Error clears immediately when valid. | Mixin's `willUpdate` re-runs `checkValidity()` on every value change while `_shouldShowError()` is true. |
|
|
12
|
+
| 4 — Submit | Submit click → validate **all** fields regardless of dirty. Focus the first invalid field. | `<schmancy-form>` calls `markSubmitted()` on every registered field. `submitted = true` overrides every `validateOn` mode and forces error display. The form then focuses the first field with `error === true`. |
|
|
13
|
+
|
|
14
|
+
After Phase 4, the field stays in Phase 3 (live re-validation) for the rest of the session because `submitted` persists until `resetForm()`.
|
|
15
|
+
|
|
16
|
+
## Per-rule cross-reference
|
|
17
|
+
|
|
18
|
+
| UX rule | API | Notes |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Default mode is "errors after the user has interacted" | `validateOn: 'dirty'` | Pristine fields stay clean; submit forces all errors. |
|
|
21
|
+
| Live search shows errors immediately | `validateOn="always"` | Bypass the dirty/touched gate entirely. |
|
|
22
|
+
| Wizard step holds errors until Next | `validateOn="submitted"` | Only `markSubmitted()` opens the gate. |
|
|
23
|
+
| Server-side error → specific field | `form.setFieldError(name, msg)` | RHF `setError` parity. Sets custom validity + marks submitted so the error shows. |
|
|
24
|
+
| Server-side error → form banner | `form.setFormError(msg, code?)` | Flips `formSubmitState` to `{ status: 'error', error: { message, code } }`. Top-of-form banners read this. |
|
|
25
|
+
| Async server-side check gating submit | `e.detail.until(promise)` | Form's `submit` event detail. Submit status reflects the promise outcome (`success` on resolve, `error` on reject). |
|
|
26
|
+
| Submit button must stay focusable while busy | `aria-busy="true"` on the host (auto) + `:state(submitting)` on `<schmancy-button[type=submit]>` (auto) | WCAG 2.2 AA. Disabled buttons drop from the tab order; we never disable. |
|
|
27
|
+
| Screen reader announces invalid fields | `internals.ariaInvalid` (auto) + `internals.ariaRequired` (auto) | Reflected through `ElementInternals` so AT reaches into shadow DOM. Not on the host as `aria-*` attributes. |
|
|
28
|
+
| Reset returns the form to pristine | `<schmancy-button type="reset">` or `form.reset()` | Mixin's `formResetCallback` → `resetForm()` clears `value`, `error`, `validationMessage`, `touched`, `submitted`. |
|
|
29
|
+
| Cross-field rules (password confirm) | `schema` prop with zod `.refine` | Validation seam is the schema, not the mixin. Cross-field violations surface on submit; map to a specific field via `form.setFieldError`. |
|
|
30
|
+
| Server-shape transforms (IBAN uppercase, trim) | `schema` prop with zod `.transform` | Runs during `schema.parse(rawFormData)` in `_onSubmit`. The `data` in the submit event is post-transform. |
|
|
31
|
+
| Multi-entry FormData (date range, tag input) | Override `toFormEntries()` + `internals.setFormValue(formData)` | Flat suffix keys (`${name}From`, `${name}To`) — no bracket-key encoding. Native `new FormData(form)` sees both keys. |
|
|
32
|
+
| Boolean state semantics (checkbox, switch) | Override `checkValidity()` | Required validity is `checked === true`, not the mixin's "non-empty value." |
|
|
33
|
+
| Numeric value (range) | `override value: number` | Narrows the mixin's wide union. Mixin's `dirty` getter uses `value !== _defaultValue` which works for primitives. |
|
|
34
|
+
| Two independent forms on one page | Each `<schmancy-form>` is its own context provider | `<schmancy-context .provides=${[formSubmitState]}>` is rendered inside each form. Submit state is isolated per instance — no cross-contamination. |
|
|
35
|
+
| Nested forms forbidden | `<schmancy-form>` `connectedCallback` checks for an ancestor `schmancy-form` and `console.error`s if found | Nested forms are platform-undefined; we surface the error early. |
|
|
36
|
+
|
|
37
|
+
## What's NOT in scope
|
|
38
|
+
|
|
39
|
+
These are deferred to v2 and **not** in the current contract:
|
|
40
|
+
|
|
41
|
+
- `<schmancy-form-summary>` (top-of-form error banner with anchor links to invalid fields).
|
|
42
|
+
- `validateOn: 'length'` (validate once `value.length === maxlength` — Stripe card-number pattern).
|
|
43
|
+
- Per-field `errorMessages` i18n hook (the default `'This field is required'` is hardcoded English).
|
|
44
|
+
- First-class `useFieldArray` API for dynamic fields.
|
|
45
|
+
- Async-validation helpers (`isValidating`, `runAsyncValidator`, `:state(validating)`).
|
|
46
|
+
- Wizard `clearSubmitted()` (post-submit state currently persists until `resetForm`).
|
|
47
|
+
- Structured `ValidityStateFlags` exposure beyond `valueMissing` + `customError`.
|
|
48
|
+
|
|
49
|
+
If a consumer needs one of these, file an issue with the use case before adding it — every addition to the binding contract has a propagation cost across all 9 form components.
|
|
50
|
+
|
|
51
|
+
## See also
|
|
52
|
+
|
|
53
|
+
- `form.md` — the `<schmancy-form>` API reference.
|
|
54
|
+
- `mixins.md` — `SchmancyFormField()` factory composition.
|
|
55
|
+
- `state.md` — `formSubmitState` singleton + per-form isolation.
|
package/dist/skills/form.md
CHANGED
|
@@ -1,49 +1,145 @@
|
|
|
1
1
|
# schmancy-form
|
|
2
2
|
|
|
3
|
-
> Form container
|
|
3
|
+
> Form container with isolated submit state, typed schema seam, and a unified validation UX contract every field implements.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
|
+
|
|
6
7
|
```html
|
|
7
|
-
<schmancy-form @submit=${(e) => handleSubmit(e.detail)}>
|
|
8
|
+
<schmancy-form @submit=${(e) => handleSubmit(e.detail.data)}>
|
|
8
9
|
<schmancy-input name="email" label="Email" required></schmancy-input>
|
|
9
10
|
<schmancy-button type="submit">Submit</schmancy-button>
|
|
10
11
|
</schmancy-form>
|
|
11
12
|
```
|
|
12
13
|
|
|
14
|
+
The submit detail is `{ data, formData, until }`:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
type SchmancyFormSubmitDetail<T = Record<string, FormDataEntryValue>> = {
|
|
18
|
+
data: T // typed when `schema` is set
|
|
19
|
+
formData: FormData // raw payload
|
|
20
|
+
until(p: Promise<unknown>): void // gate success/error on async outcome
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
13
24
|
## Properties
|
|
25
|
+
|
|
14
26
|
| Property | Type | Default | Description |
|
|
15
|
-
|
|
16
|
-
|
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `schema` | `ParseSchema` (zod / valibot / ArkType compatible) | — | When set, parses raw FormData on submit; `e.detail.data` is typed `z.infer<TSchema>`. |
|
|
29
|
+
| `novalidate` | `boolean` | `true` | Skip native browser validation popups (we control display). |
|
|
17
30
|
|
|
18
31
|
## Events
|
|
32
|
+
|
|
19
33
|
| Event | Detail | Description |
|
|
20
|
-
|
|
21
|
-
| submit |
|
|
22
|
-
| reset |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `submit` | `SchmancyFormSubmitDetail` | Fired after all registered fields pass validation. |
|
|
36
|
+
| `reset` | `CustomEvent` | Fired after the form resets. |
|
|
23
37
|
|
|
24
38
|
## Methods
|
|
39
|
+
|
|
25
40
|
| Method | Returns | Description |
|
|
26
|
-
|
|
27
|
-
| submit() | boolean |
|
|
28
|
-
| reset() | void | Resets
|
|
29
|
-
|
|
|
30
|
-
|
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `submit()` | `boolean` | Programmatic submission via the inner form's `requestSubmit()`. |
|
|
43
|
+
| `reset()` | `void` | Resets every registered field. |
|
|
44
|
+
| `setFieldError(name, message)` | `boolean` | RHF `setError(name, ...)`-equivalent: maps a server-side error to a specific field. |
|
|
45
|
+
| `setFormError(message, code?)` | `void` | RHF `setError('root.serverError', ...)`-equivalent: structured form-level error. |
|
|
46
|
+
| `getFormData()` | `FormData` | Snapshot of the registered fields' contributed entries. |
|
|
47
|
+
| `reportValidity()` | `boolean` | Checks every registered field. |
|
|
48
|
+
| `checkValidity()` | `boolean` | Same as `reportValidity` without the side effect. |
|
|
49
|
+
|
|
50
|
+
## Validation UX (binding contract)
|
|
51
|
+
|
|
52
|
+
Every field that extends `SchmancyFormField()` implements four phases:
|
|
53
|
+
|
|
54
|
+
| Phase | Trigger | Behaviour |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| 1 — Pristine | User hasn't changed the field | **No error shown**, ever. `dirty = false` is the gate. |
|
|
57
|
+
| 2 — Dirty | User typed something then blurred | Validate. **Show error only if invalid.** |
|
|
58
|
+
| 3 — Live correction | User returns to a field with an error | Re-validate on every keystroke. Error clears immediately when valid. |
|
|
59
|
+
| 4 — Submit | User clicks submit | Force-validate **all** fields regardless of `dirty`. Focus the first invalid field. |
|
|
60
|
+
|
|
61
|
+
The default mode is `validateOn="dirty"`. Override per-field for special cases:
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<schmancy-input validateOn="always" ...></schmancy-input> <!-- live search -->
|
|
65
|
+
<schmancy-input validateOn="submitted" ...></schmancy-input> <!-- wizard step -->
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
After a submit attempt, the field stays in live-correction mode (Phase 3) for the rest of the session — `resetForm()` is the only way back.
|
|
69
|
+
|
|
70
|
+
## Server-side errors
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
async _onSubmit(e: CustomEvent<SchmancyFormSubmitDetail>) {
|
|
74
|
+
const form = e.target as SchmancyForm
|
|
75
|
+
e.detail.until(
|
|
76
|
+
chargeCard(e.detail.data).catch(err => {
|
|
77
|
+
if (err.code === 'CARD_DECLINED') {
|
|
78
|
+
form.setFieldError('card', 'Card declined — check the number')
|
|
79
|
+
} else {
|
|
80
|
+
form.setFormError(err.message)
|
|
81
|
+
}
|
|
82
|
+
throw err // re-throw so submit-state flips to 'error'
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## CSS state hooks (free)
|
|
89
|
+
|
|
90
|
+
Every field broadcasts these via `:state(...)`:
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
schmancy-input:state(invalid) { --border-color: var(--color-error); }
|
|
94
|
+
schmancy-input:state(dirty) { --border-color: var(--color-primary); }
|
|
95
|
+
schmancy-input:state(touched) { /* user blurred at least once */ }
|
|
96
|
+
schmancy-input:state(submitted) { /* post-submit state */ }
|
|
97
|
+
schmancy-input:state(required) { /* required visual */ }
|
|
98
|
+
|
|
99
|
+
schmancy-button[type=submit]:state(submitting) { opacity: 0.6; } /* form busy */
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `<schmancy-form>` host gets `aria-busy="true"` while submitting (WCAG 2.2 AA — disabled submit buttons drop from tab order; we keep them focusable and signal busy via aria).
|
|
103
|
+
|
|
104
|
+
## Field-author contract
|
|
105
|
+
|
|
106
|
+
Components that extend `SchmancyFormField()` get FACE wiring, ARIA reflection (`internals.ariaInvalid` / `ariaRequired`), the validation UX state (`touched/dirty/submitted/validateOn`), the `_shouldShowError()` gate, the form-registry composed event (`FIELD_CONNECT_EVENT`), and reset/disabled propagation — all for free.
|
|
107
|
+
|
|
108
|
+
The component author must wire only two things:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// 1. Call markTouched() on blur — tells the mixin the user has left the field
|
|
112
|
+
this.input.addEventListener('blur', () => this.markTouched())
|
|
113
|
+
|
|
114
|
+
// 2. Call emitChange() when value changes
|
|
115
|
+
this.value = newValue
|
|
116
|
+
this.emitChange({ value: newValue })
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Override only when validity semantics differ (checkbox uses `checked`, switch uses `checked`, date-range uses `dateFrom`/`dateTo`) or when FormData contribution is multi-entry (date-range emits `${name}From` and `${name}To`).
|
|
120
|
+
|
|
121
|
+
## Submit triggers
|
|
122
|
+
|
|
123
|
+
`<schmancy-form>` listens on its host for `click` on `<button type=submit>` / `<schmancy-button type=submit>` and `keydown.Enter` on registered fields, then calls the inner shadow-DOM form's `requestSubmit()`. This bridges the gap that native form-association cannot cross from light-DOM slotted children to a shadow-DOM `<form>`.
|
|
31
124
|
|
|
32
125
|
## Examples
|
|
126
|
+
|
|
33
127
|
```html
|
|
34
|
-
<!--
|
|
35
|
-
<schmancy-
|
|
36
|
-
|
|
37
|
-
<schmancy-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
<schmancy-button type="reset">Reset</schmancy-button>
|
|
45
|
-
</div>
|
|
128
|
+
<!-- Two independent checkout forms — each <schmancy-form> instance has its own
|
|
129
|
+
isolated submit state via <schmancy-context>; no cross-contamination. -->
|
|
130
|
+
<schmancy-form @submit=${(e) => pay('primary', e)}>
|
|
131
|
+
<schmancy-input name="card" required></schmancy-input>
|
|
132
|
+
<schmancy-button type="submit">Pay £50</schmancy-button>
|
|
133
|
+
</schmancy-form>
|
|
134
|
+
|
|
135
|
+
<schmancy-form @submit=${(e) => pay('backup', e)}>
|
|
136
|
+
<schmancy-input name="card" required></schmancy-input>
|
|
137
|
+
<schmancy-button type="submit">Pay £50</schmancy-button>
|
|
46
138
|
</schmancy-form>
|
|
47
139
|
```
|
|
48
140
|
|
|
49
|
-
|
|
141
|
+
## See also
|
|
142
|
+
|
|
143
|
+
- `form-ux-rules.md` — the Revolute UX contract → mixin API mapping.
|
|
144
|
+
- `mixins.md` — `SchmancyFormField()` factory composition.
|
|
145
|
+
- `state.md` — `formSubmitState` + `<schmancy-context>` isolation pattern.
|
package/dist/skills/input.md
CHANGED
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
| validationMessage | string | `''` | Custom validation message |
|
|
23
23
|
| hint | string | `undefined` | Hint text below the field |
|
|
24
24
|
| size | `'xxs'\|'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Input height (24-56px) |
|
|
25
|
-
| validateOn | `'always'\|'touched'\|'dirty'\|'submitted'` | `'
|
|
25
|
+
| validateOn | `'always'\|'touched'\|'dirty'\|'submitted'` | `'dirty'` | When to display validation errors. Default suppresses errors on pristine fields until submit. |
|
|
26
|
+
| touched | boolean (read-only) | `false` | True after first blur — set by `markTouched()`. |
|
|
27
|
+
| dirty | boolean (getter) | — | True when `value` differs from the captured default. |
|
|
28
|
+
| submitted | boolean (read-only) | `false` | Set by `<schmancy-form>` on submit; flips field to live-correction mode. |
|
|
26
29
|
| align | `'left'\|'center'\|'right'` | `'left'` | Text alignment |
|
|
27
30
|
| pattern | string | `undefined` | Regex validation pattern |
|
|
28
31
|
| inputmode | string | `undefined` | Virtual keyboard hint |
|
|
@@ -34,9 +37,21 @@
|
|
|
34
37
|
## Events
|
|
35
38
|
| Event | Detail | Description |
|
|
36
39
|
|-------|--------|-------------|
|
|
37
|
-
| input | `{ value: string }` | Every keystroke |
|
|
38
|
-
| change | `{ value: string }` | On blur/native change |
|
|
39
|
-
| enter | `{ value: string }` |
|
|
40
|
+
| input | `{ value: string }` | Every keystroke (bubbles, composed) |
|
|
41
|
+
| change | `{ value: string }` | On blur/native change (bubbles, composed) |
|
|
42
|
+
| enter | `{ value: string }` | Enter key pressed |
|
|
43
|
+
| focus / blur | — | Standard focus events |
|
|
44
|
+
|
|
45
|
+
## Public API (from `SchmancyFormField`)
|
|
46
|
+
- `markTouched()` / `markSubmitted()` — flip the `_shouldShowError()` gate.
|
|
47
|
+
- `checkValidity()` / `reportValidity()` — return current validity (used by `<schmancy-form>` and native `<form>`).
|
|
48
|
+
- `setCustomValidity(message)` — server-side error path. `<schmancy-form>` calls this from `setFieldError(name, msg)`.
|
|
49
|
+
- `resetForm()` — restores the captured default; clears `error`, `touched`, `submitted`.
|
|
50
|
+
- `internals.ariaInvalid` / `ariaRequired` — set automatically; reaches AT through shadow DOM.
|
|
51
|
+
|
|
52
|
+
`<schmancy-form>` auto-discovers this field via `FIELD_CONNECT_EVENT` (composed event). No registration needed.
|
|
53
|
+
|
|
54
|
+
See `form.md` and `form-ux-rules.md` for the binding 4-phase validation contract.
|
|
40
55
|
|
|
41
56
|
## Examples
|
|
42
57
|
```html
|
package/dist/skills/range.md
CHANGED
|
@@ -20,9 +20,15 @@
|
|
|
20
20
|
| `min` | number | `0` | Minimum value |
|
|
21
21
|
| `max` | number | `1` | Maximum value |
|
|
22
22
|
| `step` | number | `0.01` | Increment per tick |
|
|
23
|
-
| `value` | number | `0` | Current value |
|
|
23
|
+
| `value` | number | `0` | Current value (narrowed override of the mixin's wide union). |
|
|
24
24
|
| `label` | string | — | Optional label (current value displays on the right) |
|
|
25
25
|
| `disabled` | boolean | `false` | Disabled state (38% opacity) |
|
|
26
|
+
| `name` | string | `''` | Form submission name. From `SchmancyFormField`. |
|
|
27
|
+
| `required` | boolean | `false` | Field must have a value to validate. |
|
|
28
|
+
| `validateOn` | `'always'\|'touched'\|'dirty'\|'submitted'` | `'dirty'` | When errors display. |
|
|
29
|
+
| `validationMessage` | string | `''` | Error message. |
|
|
30
|
+
| `hint` | string | `undefined` | Helper text. |
|
|
31
|
+
| `touched / dirty / submitted` | boolean | — | Validation state from `SchmancyFormField`. |
|
|
26
32
|
|
|
27
33
|
## Events
|
|
28
34
|
| Event | Payload | When |
|
|
@@ -34,6 +40,14 @@
|
|
|
34
40
|
- Circular thumb with hover halo (8px primary glow at 12% opacity).
|
|
35
41
|
- Disabled state: 38% opacity, not-allowed cursor.
|
|
36
42
|
|
|
43
|
+
## Form-field contract
|
|
44
|
+
|
|
45
|
+
Extends `SchmancyFormField()`. FormData contributes `String(value)` under `name`. `markTouched()` fires on every input event (slider drag), so the field transitions to `touched` immediately on interaction — the `dirty` gate opens once value diverges from the captured default.
|
|
46
|
+
|
|
47
|
+
Auto-discovered by `<schmancy-form>` via `FIELD_CONNECT_EVENT`. Public API: `markTouched()`, `markSubmitted()`, `checkValidity()`, `setCustomValidity()`, `resetForm()`.
|
|
48
|
+
|
|
49
|
+
See `form.md` and `form-ux-rules.md` for the binding 4-phase validation contract.
|
|
50
|
+
|
|
37
51
|
## Example — precise float control
|
|
38
52
|
```html
|
|
39
53
|
<schmancy-range
|
|
@@ -57,6 +57,9 @@ Use component tags (`<schmancy-menu>`, `<schmancy-dropdown>`, `<schmancy-tooltip
|
|
|
57
57
|
- **Schmancy primitive first** (`PRIMITIVE_FIRST`). Within `web/**`, every visible UI element is a custom element exported from `packages/schmancy/src/**`, and an element absent from that export set is added there before being imported into `web/**`. The rule sits above the styling rules: a `<div class="text-xs text-surface-on-variant">…</div>` whose role is typography is a violation even when every utility resolves to a registered token, because `<schmancy-typography>` already covers that role; the styling rules apply only to whatever class strings remain after the right primitive has been selected.
|
|
58
58
|
Sources: [packages/schmancy/skills/schmancy/INDEX.md](../INDEX.md) catalogues the export set by job (foundations / atoms / forms / navigation / overlays / interaction / feedback / display); each role's reference file (`typography.md`, `surface.md`, `button.md`, `overlay.md`, …) names the props, slots, and events that displace the equivalent `<div>` + utility-class pattern. The export set is the single source — a primitive that is not exported from `packages/schmancy/src/**` does not satisfy this rule even if it lives in a private file inside the schmancy tree.
|
|
59
59
|
Remediation: walk every `.ts` and `.html` file under `web/**` and list every raw HTML element whose class string carries design-system styling (typography, color, spacing-as-design-decision, surface, layout-as-design-decision, motion, overlay) — those are the violations. For each, look up the matching schmancy primitive in `INDEX.md` and rewrite the element through that primitive (`<schmancy-typography type=… token=…>` for type-scale text, `<schmancy-surface type=… fill=…>` for elevated/bounded surfaces, `<schmancy-grid>`/`<schmancy-flex>` for layout primitives with design intent, the imperative `show`/`$notify`/`schmancyContentDrawer.push` services for overlays, `<schmancy-scroll>` for scroll containers). When a needed primitive is absent from the export set, design and implement it as a new component under `packages/schmancy/src/<role>/` — extending `SchmancyElement` with `static styles = [css\`...\`]`, registered in `HTMLElementTagNameMap`, exported through the package barrel, and documented with a sibling `.md` in the skill's reference set — and only then introduce the first call site in `web/**`. The audit subagent iterates the whole `web/**` tree, surfaces the violation list, applies the rewrites, runs `yarn workspace @momo/web tsc --noEmit` plus the colocated `*-view.test.ts` suites, and reports pre-existing violations that require a new schmancy primitive as a separate punch list for designer/architect approval before the implementation lands. The loop exits when every `web/**` file's visible UI elements are schmancy primitives and the typecheck plus the test suites pass.
|
|
60
|
+
- **Module index re-exports the subpath surface** (`MODULE_INDEX_REEXPORTS_SUBPATH`). Each schmancy module exposed through the package's `./*` subpath export resolves to `src/<name>/index.ts`, and any other source file under `src/<name>/` is visible to consumers only through symbols that index re-exports.
|
|
61
|
+
Sources: the `exports` field in [packages/schmancy/package.json](../../package.json) maps the `./*` subpath to `./dist/*.js` and the matching `./types/src/*/index.d.ts` types entry, so the only declaration a consumer reaches for `import … from '@mhmo91/schmancy/<name>'` is the one emitted from `src/<name>/index.ts`. Files placed in subdirectories of that module are bundled into the same dist output, but they cross the package boundary only through symbols re-exported up the chain into the module index. Past incident: `packages/schmancy/src/form/index.ts` exposed only `form`, `form-state`, and `form-summary`; consumers importing field types like `SchmancyInput` and `SchmancyFormSubmitDetail` from `@mhmo91/schmancy/form` saw nothing because `form/fields/*` had no path into the module index.
|
|
62
|
+
Remediation: list every direct subdirectory of `packages/schmancy/src/<name>/` that owns its own `index.ts` with component exports, and for each one verify that `packages/schmancy/src/<name>/index.ts` carries an `export * from './<subdir>'` (directly, or via a grouping barrel such as `<subdir>/index.ts` re-exported by the module index). The audit subagent runs `ls -d packages/schmancy/src/<name>/*/` for every module under `./*`, greps each subdirectory's `index.ts` for `^export`, then greps the module's `index.ts` for `export \* from './<subdir>'` (or for an intermediate barrel that re-exports it), and lists every subdirectory that fails the second grep as a violation. The fix per violation is one line in the module's `index.ts`; when a module groups several subdirectories under one role (e.g. `form/fields/*`), the loop also adds an intermediate `<subdir>/index.ts` barrel and re-exports that single barrel from the module index rather than enumerating leaves. The loop exits when every direct subdirectory's symbols round-trip through the module index and `yarn workspace @momo/web tsc --noEmit` passes against the consumers that import from `@mhmo91/schmancy/<name>`.
|
|
60
63
|
|
|
61
64
|
## Non-negotiable conventions
|
|
62
65
|
|
|
@@ -20,13 +20,18 @@
|
|
|
20
20
|
| name | string | `''` | Form submission name |
|
|
21
21
|
| required | boolean | `false` | Whether selection is required |
|
|
22
22
|
| multi | boolean | `false` | Enable multi-select with inline chips |
|
|
23
|
-
| size | `'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Input height |
|
|
23
|
+
| size | `'xxs'\|'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Input height (M3 sizes 24–56dp). |
|
|
24
24
|
| maxHeight | string | `'300px'` | Max dropdown height |
|
|
25
25
|
| debounceMs | number | `200` | Search debounce in ms |
|
|
26
26
|
| similarityThreshold | number | `0.3` | Minimum fuzzy match score (0-1) |
|
|
27
|
-
| error | boolean | `false` | Error state |
|
|
28
|
-
| validationMessage | string | `''` | Validation message |
|
|
27
|
+
| error | boolean | `false` | Error state (gated by `validateOn`). From `SchmancyFormField`. |
|
|
28
|
+
| validationMessage | string | `''` | Validation message. |
|
|
29
29
|
| description | string | `''` | Screen reader description |
|
|
30
|
+
| validateOn | `'always'\|'touched'\|'dirty'\|'submitted'` | `'dirty'` | When validation errors display. |
|
|
31
|
+
| disabled | boolean | `false` | Disabled state. |
|
|
32
|
+
| hint | string | `undefined` | Helper text below the field. |
|
|
33
|
+
| touched / dirty / submitted | boolean | — | Validation state from `SchmancyFormField`. |
|
|
34
|
+
| autocomplete | string | `'off'` | Native autocomplete attribute on inner input. |
|
|
30
35
|
|
|
31
36
|
## Events
|
|
32
37
|
| Event | Detail | Description |
|
|
@@ -51,3 +56,11 @@
|
|
|
51
56
|
```
|
|
52
57
|
|
|
53
58
|
Children must be `<schmancy-option>` elements. In multi mode, selected items appear as removable chips.
|
|
59
|
+
|
|
60
|
+
## Form-field contract
|
|
61
|
+
|
|
62
|
+
Extends `SchmancyFormField()`. Auto-discovered by `<schmancy-form>` via `FIELD_CONNECT_EVENT`. Public API: `markTouched()`, `markSubmitted()`, `checkValidity()`, `reportValidity()`, `setCustomValidity()`, `resetForm()` (resets the BehaviorSubjects + clears the input).
|
|
63
|
+
|
|
64
|
+
Default `validateOn: 'dirty'` — a required-empty autocomplete shows no error until the user types-then-clears or submits. Error UI rendered in a `role="alert"` div linked via `aria-describedby`.
|
|
65
|
+
|
|
66
|
+
See `form.md` and `form-ux-rules.md` for the binding 4-phase validation contract.
|
|
@@ -40,6 +40,25 @@
|
|
|
40
40
|
- Hover: luminous glow shadow using primary color
|
|
41
41
|
- Active: spring press `scale(0.97)` / `scale(0.92)` for icon-button
|
|
42
42
|
|
|
43
|
+
## Submit-state mirroring (`type="submit"` only)
|
|
44
|
+
|
|
45
|
+
A `<schmancy-button type="submit">` inside a `<schmancy-form>` automatically reflects the form's submit state via a MutationObserver on the form's `aria-busy` attribute:
|
|
46
|
+
|
|
47
|
+
- While the form's `formSubmitState.status === 'submitting'` (form sets `aria-busy="true"`):
|
|
48
|
+
- Button mirrors `aria-busy="true"` onto itself.
|
|
49
|
+
- Broadcasts `:state(submitting)` for CSS targeting.
|
|
50
|
+
- **Stays focusable** — never gets the `disabled` attribute. Disabled buttons drop from the tab order; AT users would lose their place. WCAG 2.2 AA.
|
|
51
|
+
- Cleared automatically when the form's submit promise settles (success or error).
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
schmancy-button:state(submitting) {
|
|
55
|
+
opacity: 0.6;
|
|
56
|
+
/* show a spinner via ::before, swap label, etc. */
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`schmancy-button` is form-associated (`static formAssociated = true` + `attachInternals()`) — clicking a `type="submit"` inside any `<form>` ancestor (native or schmancy) calls `form.requestSubmit()`. Inside `<schmancy-form>` the click is also intercepted at the form host and bridged across the shadow boundary to the inner shadow-DOM `<form>`.
|
|
61
|
+
|
|
43
62
|
## Examples
|
|
44
63
|
```html
|
|
45
64
|
<!-- Filled primary -->
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
| disabled | boolean | `false` | Whether the checkbox is disabled |
|
|
19
19
|
| required | boolean | `false` | Whether the checkbox is required |
|
|
20
20
|
| size | `'xxs'\|'xs'\|'sm'\|'md'\|'lg'` | `'md'` | Checkbox size |
|
|
21
|
+
| validateOn | `'always'\|'touched'\|'dirty'\|'submitted'` | `'dirty'` | When validation errors display. From `SchmancyFormField`. |
|
|
22
|
+
| validationMessage | string | `''` | Error text — default `'Please check this box if you want to proceed.'` when required and unchecked. |
|
|
23
|
+
| error | boolean | `false` | Error state (gated by `validateOn`). |
|
|
24
|
+
| hint | string | `undefined` | Helper text. |
|
|
25
|
+
| touched / dirty / submitted | boolean | — | Validation state from `SchmancyFormField`. |
|
|
26
|
+
|
|
27
|
+
## Attributes
|
|
28
|
+
- `true-value` — string written to FormData when checked. Default `'on'` (matches native checkbox semantics).
|
|
21
29
|
|
|
22
30
|
## Events
|
|
23
31
|
| Event | Detail | Description |
|
|
@@ -33,4 +41,15 @@
|
|
|
33
41
|
<schmancy-checkbox name="terms" required>
|
|
34
42
|
Accept <a href="/terms">terms and conditions</a>
|
|
35
43
|
</schmancy-checkbox>
|
|
44
|
+
|
|
45
|
+
<!-- Custom FormData string -->
|
|
46
|
+
<schmancy-checkbox name="newsletter" true-value="opt-in"></schmancy-checkbox>
|
|
36
47
|
```
|
|
48
|
+
|
|
49
|
+
## Form-field contract
|
|
50
|
+
|
|
51
|
+
Extends `SchmancyFormField()`. `value` is the boolean state; FormData submission uses the `true-value` attribute (or `'on'`). Validity overrides the mixin's "non-empty value" semantics: required + unchecked → `internals.setValidity({ valueMissing: true })`. ARIA `aria-invalid` / `aria-required` reflect through `ElementInternals`.
|
|
52
|
+
|
|
53
|
+
Public API: `markTouched()`, `markSubmitted()`, `checkValidity()`, `setCustomValidity()`, `resetForm()`. Auto-discovered by `<schmancy-form>` via `FIELD_CONNECT_EVENT`.
|
|
54
|
+
|
|
55
|
+
See `form.md` and `form-ux-rules.md` for the binding 4-phase validation contract.
|
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
| collapse | boolean | `false` | Icon-only on mobile |
|
|
28
28
|
| customPresets | array | `[]` | Additional preset ranges |
|
|
29
29
|
| format | string | auto | Date format string |
|
|
30
|
+
| name | string | `''` | Form submission name. From `SchmancyFormField`. |
|
|
31
|
+
| validateOn | `'always'\|'touched'\|'dirty'\|'submitted'` | `'dirty'` | When validation errors display. |
|
|
32
|
+
| validationMessage | string | `''` | Error message — default `'Please select a date range.'` when required and either bound is empty. |
|
|
33
|
+
| error | boolean | `false` | Error state (gated by `validateOn`). |
|
|
34
|
+
| hint | string | `undefined` | Helper text. |
|
|
35
|
+
| touched / dirty / submitted | boolean | — | Validation state. `dirty` tracks `dateFrom.value` / `dateTo.value` against the snapshots taken at first render. |
|
|
30
36
|
|
|
31
37
|
## Events
|
|
32
38
|
| Event | Detail | Description |
|
|
@@ -48,3 +54,16 @@
|
|
|
48
54
|
```
|
|
49
55
|
|
|
50
56
|
Built-in presets include Today, Yesterday, This Week, This Month, This Year, and more.
|
|
57
|
+
|
|
58
|
+
## Form association
|
|
59
|
+
|
|
60
|
+
Extends `SchmancyFormField()`. Multi-entry FormData contribution:
|
|
61
|
+
- Set `name="dates"` and FormData receives **two** keys: `datesFrom` and `datesTo` (flat suffix shape, no bracket-key encoding).
|
|
62
|
+
- `internals.setFormValue(formData)` accepts a `FormData` object whose entries get appended to the parent form — native `new FormData(form)` sees both keys without consumer-side parsing.
|
|
63
|
+
- `<schmancy-form>`'s registry consumer also sees both keys via `toFormEntries()`.
|
|
64
|
+
|
|
65
|
+
`resetForm()` restores both `dateFrom.value` and `dateTo.value` to the snapshots captured at first render. Validity: required + either bound empty → `internals.setValidity({ valueMissing: true })`. Order/range constraints (start ≤ end, future-only, etc.) are out of scope — handle those in a domain schema layer.
|
|
66
|
+
|
|
67
|
+
Auto-discovered by `<schmancy-form>` via `FIELD_CONNECT_EVENT`. Public API: `markTouched()`, `markSubmitted()`, `checkValidity()`, `setCustomValidity()`, `resetForm()`.
|
|
68
|
+
|
|
69
|
+
See `form.md` and `form-ux-rules.md` for the binding 4-phase validation contract.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Form UX rules — Revolute pattern → schmancy mixin API
|
|
2
|
+
|
|
3
|
+
This is the mapping between the Revolute interview-bar form UX (the source-of-truth checklist for state-of-the-art form behaviour) and the `SchmancyFormField()` API that implements each rule. When you hit a UX question, find the rule here and use the named API — don't re-derive from scratch.
|
|
4
|
+
|
|
5
|
+
## The four-phase validation contract
|
|
6
|
+
|
|
7
|
+
| Phase | UX rule | Implementation |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 1 — Pristine | A field the user hasn't typed in shows no error, even if focused-then-blurred. The autofocus+cancel anti-pattern (focus a required field, click cancel, see "required") is forbidden. | `validateOn: 'dirty'` (default). `_shouldShowError()` returns `false` while `dirty === false`. |
|
|
10
|
+
| 2 — Dirty | User types and blurs → validate the field. Show error only if invalid. Empty required fields blurred-without-typing remain pristine. | `markTouched()` called on blur. Mixin's `willUpdate` sees `touched` change, sees `dirty` is true, calls `checkValidity()` which sets `this.error` because the gate is open. |
|
|
11
|
+
| 3 — Live correction | User returns to a field with an error → re-validate on every keystroke. Error clears immediately when valid. | Mixin's `willUpdate` re-runs `checkValidity()` on every value change while `_shouldShowError()` is true. |
|
|
12
|
+
| 4 — Submit | Submit click → validate **all** fields regardless of dirty. Focus the first invalid field. | `<schmancy-form>` calls `markSubmitted()` on every registered field. `submitted = true` overrides every `validateOn` mode and forces error display. The form then focuses the first field with `error === true`. |
|
|
13
|
+
|
|
14
|
+
After Phase 4, the field stays in Phase 3 (live re-validation) for the rest of the session because `submitted` persists until `resetForm()`.
|
|
15
|
+
|
|
16
|
+
## Per-rule cross-reference
|
|
17
|
+
|
|
18
|
+
| UX rule | API | Notes |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Default mode is "errors after the user has interacted" | `validateOn: 'dirty'` | Pristine fields stay clean; submit forces all errors. |
|
|
21
|
+
| Live search shows errors immediately | `validateOn="always"` | Bypass the dirty/touched gate entirely. |
|
|
22
|
+
| Wizard step holds errors until Next | `validateOn="submitted"` | Only `markSubmitted()` opens the gate. |
|
|
23
|
+
| Server-side error → specific field | `form.setFieldError(name, msg)` | RHF `setError` parity. Sets custom validity + marks submitted so the error shows. |
|
|
24
|
+
| Server-side error → form banner | `form.setFormError(msg, code?)` | Flips `formSubmitState` to `{ status: 'error', error: { message, code } }`. Top-of-form banners read this. |
|
|
25
|
+
| Async server-side check gating submit | `e.detail.until(promise)` | Form's `submit` event detail. Submit status reflects the promise outcome (`success` on resolve, `error` on reject). |
|
|
26
|
+
| Submit button must stay focusable while busy | `aria-busy="true"` on the host (auto) + `:state(submitting)` on `<schmancy-button[type=submit]>` (auto) | WCAG 2.2 AA. Disabled buttons drop from the tab order; we never disable. |
|
|
27
|
+
| Screen reader announces invalid fields | `internals.ariaInvalid` (auto) + `internals.ariaRequired` (auto) | Reflected through `ElementInternals` so AT reaches into shadow DOM. Not on the host as `aria-*` attributes. |
|
|
28
|
+
| Reset returns the form to pristine | `<schmancy-button type="reset">` or `form.reset()` | Mixin's `formResetCallback` → `resetForm()` clears `value`, `error`, `validationMessage`, `touched`, `submitted`. |
|
|
29
|
+
| Cross-field rules (password confirm) | `schema` prop with zod `.refine` | Validation seam is the schema, not the mixin. Cross-field violations surface on submit; map to a specific field via `form.setFieldError`. |
|
|
30
|
+
| Server-shape transforms (IBAN uppercase, trim) | `schema` prop with zod `.transform` | Runs during `schema.parse(rawFormData)` in `_onSubmit`. The `data` in the submit event is post-transform. |
|
|
31
|
+
| Multi-entry FormData (date range, tag input) | Override `toFormEntries()` + `internals.setFormValue(formData)` | Flat suffix keys (`${name}From`, `${name}To`) — no bracket-key encoding. Native `new FormData(form)` sees both keys. |
|
|
32
|
+
| Boolean state semantics (checkbox, switch) | Override `checkValidity()` | Required validity is `checked === true`, not the mixin's "non-empty value." |
|
|
33
|
+
| Numeric value (range) | `override value: number` | Narrows the mixin's wide union. Mixin's `dirty` getter uses `value !== _defaultValue` which works for primitives. |
|
|
34
|
+
| Two independent forms on one page | Each `<schmancy-form>` is its own context provider | `<schmancy-context .provides=${[formSubmitState]}>` is rendered inside each form. Submit state is isolated per instance — no cross-contamination. |
|
|
35
|
+
| Nested forms forbidden | `<schmancy-form>` `connectedCallback` checks for an ancestor `schmancy-form` and `console.error`s if found | Nested forms are platform-undefined; we surface the error early. |
|
|
36
|
+
|
|
37
|
+
## What's NOT in scope
|
|
38
|
+
|
|
39
|
+
These are deferred to v2 and **not** in the current contract:
|
|
40
|
+
|
|
41
|
+
- `<schmancy-form-summary>` (top-of-form error banner with anchor links to invalid fields).
|
|
42
|
+
- `validateOn: 'length'` (validate once `value.length === maxlength` — Stripe card-number pattern).
|
|
43
|
+
- Per-field `errorMessages` i18n hook (the default `'This field is required'` is hardcoded English).
|
|
44
|
+
- First-class `useFieldArray` API for dynamic fields.
|
|
45
|
+
- Async-validation helpers (`isValidating`, `runAsyncValidator`, `:state(validating)`).
|
|
46
|
+
- Wizard `clearSubmitted()` (post-submit state currently persists until `resetForm`).
|
|
47
|
+
- Structured `ValidityStateFlags` exposure beyond `valueMissing` + `customError`.
|
|
48
|
+
|
|
49
|
+
If a consumer needs one of these, file an issue with the use case before adding it — every addition to the binding contract has a propagation cost across all 9 form components.
|
|
50
|
+
|
|
51
|
+
## See also
|
|
52
|
+
|
|
53
|
+
- `form.md` — the `<schmancy-form>` API reference.
|
|
54
|
+
- `mixins.md` — `SchmancyFormField()` factory composition.
|
|
55
|
+
- `state.md` — `formSubmitState` singleton + per-form isolation.
|