@expressivecss/expressive 0.16.0 → 0.17.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/CHANGELOG.md +306 -1
- package/README.md +1 -1
- package/dist/css/expressive.css +540 -646
- package/dist/css/expressive.css.map +1 -1
- package/dist/css/expressive.min.css +1 -1
- package/dist/css/expressive.min.css.map +1 -1
- package/dist/js/chunks/{chunk-PA7NU5AK.mjs → chunk-26VUH75M.mjs} +6 -3
- package/dist/js/chunks/chunk-26VUH75M.mjs.map +7 -0
- package/dist/js/chunks/{chunk-NBJAUGPP.mjs → chunk-2LFCOGC3.mjs} +12 -23
- package/dist/js/chunks/chunk-2LFCOGC3.mjs.map +7 -0
- package/dist/js/chunks/{chunk-OP43LLIC.mjs → chunk-36GELQGX.mjs} +4 -4
- package/dist/js/chunks/{chunk-OP43LLIC.mjs.map → chunk-36GELQGX.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-EC26RU2D.mjs → chunk-3FKWIRHW.mjs} +6 -2
- package/dist/js/chunks/chunk-3FKWIRHW.mjs.map +7 -0
- package/dist/js/chunks/{chunk-Y6H2X7ZC.mjs → chunk-3KF65KGL.mjs} +3 -3
- package/dist/js/chunks/{chunk-4QUCNFSD.mjs → chunk-3KLZRQZI.mjs} +31 -10
- package/dist/js/chunks/chunk-3KLZRQZI.mjs.map +7 -0
- package/dist/js/chunks/{chunk-75E5EDJF.mjs → chunk-3XORNED5.mjs} +2 -2
- package/dist/js/chunks/{chunk-NNHVANQA.mjs → chunk-4WBGAQN3.mjs} +3 -3
- package/dist/js/chunks/{chunk-ZJNLAGY5.mjs → chunk-5F4URJHL.mjs} +2 -2
- package/dist/js/chunks/{chunk-ZVLOUDIJ.mjs → chunk-67UNY7RH.mjs} +50 -2
- package/dist/js/chunks/chunk-67UNY7RH.mjs.map +7 -0
- package/dist/js/chunks/{chunk-P6DRQ6KS.mjs → chunk-7CXNATJS.mjs} +6 -5
- package/dist/js/chunks/chunk-7CXNATJS.mjs.map +7 -0
- package/dist/js/chunks/{chunk-4TRZ2AER.mjs → chunk-7X44MKEQ.mjs} +15 -14
- package/dist/js/chunks/chunk-7X44MKEQ.mjs.map +7 -0
- package/dist/js/chunks/{chunk-GWZ76SR4.mjs → chunk-7ZAMOMGY.mjs} +12 -7
- package/dist/js/chunks/chunk-7ZAMOMGY.mjs.map +7 -0
- package/dist/js/chunks/{chunk-GVZQH2H5.mjs → chunk-AEG6LOAJ.mjs} +2 -2
- package/dist/js/chunks/{chunk-Y6TLRA3U.mjs → chunk-AQNFCJJ5.mjs} +27 -21
- package/dist/js/chunks/chunk-AQNFCJJ5.mjs.map +7 -0
- package/dist/js/chunks/{chunk-OUCHKSAW.mjs → chunk-B55LWKBM.mjs} +16 -4
- package/dist/js/chunks/chunk-B55LWKBM.mjs.map +7 -0
- package/dist/js/chunks/{chunk-AI73GJKG.mjs → chunk-BB5VXYXT.mjs} +2 -2
- package/dist/js/chunks/{chunk-2RLBDFH4.mjs → chunk-BDATLH3H.mjs} +4 -4
- package/dist/js/chunks/{chunk-2CS6TITK.mjs → chunk-CWMQOT4O.mjs} +23 -10
- package/dist/js/chunks/chunk-CWMQOT4O.mjs.map +7 -0
- package/dist/js/chunks/{chunk-ZKWUWBUB.mjs → chunk-CY2CTOVM.mjs} +2 -2
- package/dist/js/chunks/{chunk-CSYWKEK3.mjs → chunk-DWA5UYLJ.mjs} +58 -27
- package/dist/js/chunks/chunk-DWA5UYLJ.mjs.map +7 -0
- package/dist/js/chunks/{chunk-CMGXGYUC.mjs → chunk-EB2V4OKL.mjs} +14 -9
- package/dist/js/chunks/chunk-EB2V4OKL.mjs.map +7 -0
- package/dist/js/chunks/{chunk-Z4GICXR2.mjs → chunk-EWO662ZC.mjs} +5 -7
- package/dist/js/chunks/chunk-EWO662ZC.mjs.map +7 -0
- package/dist/js/chunks/{chunk-VGY4M57S.mjs → chunk-F7LAKFMX.mjs} +67 -34
- package/dist/js/chunks/chunk-F7LAKFMX.mjs.map +7 -0
- package/dist/js/chunks/{chunk-67ZPPKXS.mjs → chunk-FNIIH2LU.mjs} +3 -3
- package/dist/js/chunks/chunk-GE3YMFS6.mjs +15 -0
- package/dist/js/chunks/chunk-GE3YMFS6.mjs.map +7 -0
- package/dist/js/chunks/{chunk-DZYVU75J.mjs → chunk-GFWYMWYN.mjs} +18 -14
- package/dist/js/chunks/chunk-GFWYMWYN.mjs.map +7 -0
- package/dist/js/chunks/{chunk-3L3QIYLC.mjs → chunk-H5ISU45X.mjs} +8 -6
- package/dist/js/chunks/chunk-H5ISU45X.mjs.map +7 -0
- package/dist/js/chunks/{chunk-QXKWVHBD.mjs → chunk-H6YIKFAQ.mjs} +67 -46
- package/dist/js/chunks/chunk-H6YIKFAQ.mjs.map +7 -0
- package/dist/js/chunks/{chunk-KZPSAXIU.mjs → chunk-HEH7E3KO.mjs} +22 -12
- package/dist/js/chunks/chunk-HEH7E3KO.mjs.map +7 -0
- package/dist/js/chunks/{chunk-T3BNSRP3.mjs → chunk-HQRLBTNM.mjs} +3 -3
- package/dist/js/chunks/{chunk-NAC72P7R.mjs → chunk-I2F7MLUU.mjs} +2 -2
- package/dist/js/chunks/{chunk-ZM3XBHWV.mjs → chunk-IQOTGGHX.mjs} +86 -35
- package/dist/js/chunks/chunk-IQOTGGHX.mjs.map +7 -0
- package/dist/js/chunks/{chunk-QACZ6TUP.mjs → chunk-JH3YWKA6.mjs} +5 -4
- package/dist/js/chunks/chunk-JH3YWKA6.mjs.map +7 -0
- package/dist/js/chunks/chunk-JOLJAYIJ.mjs +7 -0
- package/dist/js/chunks/{chunk-4IKVPCSU.mjs.map → chunk-JOLJAYIJ.mjs.map} +1 -1
- package/dist/js/chunks/{chunk-BPF3I2MN.mjs → chunk-KOTGMCMI.mjs} +9 -2
- package/dist/js/chunks/chunk-KOTGMCMI.mjs.map +7 -0
- package/dist/js/chunks/{chunk-ANKFWNWT.mjs → chunk-LC4O5LFS.mjs} +3 -3
- package/dist/js/chunks/{chunk-4PIXLDNJ.mjs → chunk-LEF33SCP.mjs} +2 -2
- package/dist/js/chunks/{chunk-ILDRRG7T.mjs → chunk-LFORZRPH.mjs} +3 -3
- package/dist/js/chunks/{chunk-Z4OARDZ7.mjs → chunk-LWLNV5EC.mjs} +26 -17
- package/dist/js/chunks/chunk-LWLNV5EC.mjs.map +7 -0
- package/dist/js/chunks/{chunk-WU7XJ4RR.mjs → chunk-LXRHUQAX.mjs} +66 -25
- package/dist/js/chunks/chunk-LXRHUQAX.mjs.map +7 -0
- package/dist/js/chunks/{chunk-VMY55K2X.mjs → chunk-MA2SZZFB.mjs} +11 -7
- package/dist/js/chunks/chunk-MA2SZZFB.mjs.map +7 -0
- package/dist/js/chunks/{chunk-VJVQIXHC.mjs → chunk-MDBZAMIV.mjs} +8 -10
- package/dist/js/chunks/chunk-MDBZAMIV.mjs.map +7 -0
- package/dist/js/chunks/{chunk-4EWGD4V2.mjs → chunk-NODZ5VAD.mjs} +2 -2
- package/dist/js/chunks/{chunk-UGJHCHXG.mjs → chunk-O2WJB6TH.mjs} +79 -55
- package/dist/js/chunks/chunk-O2WJB6TH.mjs.map +7 -0
- package/dist/js/chunks/{chunk-7YZ5CN3V.mjs → chunk-PCABY5BN.mjs} +5 -3
- package/dist/js/chunks/chunk-PCABY5BN.mjs.map +7 -0
- package/dist/js/chunks/{chunk-X7SDA2J5.mjs → chunk-PWKYVMND.mjs} +9 -5
- package/dist/js/chunks/{chunk-X7SDA2J5.mjs.map → chunk-PWKYVMND.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-SPPHF7YZ.mjs → chunk-QHZZT7YO.mjs} +35 -14
- package/dist/js/chunks/chunk-QHZZT7YO.mjs.map +7 -0
- package/dist/js/chunks/{chunk-LDXCC2L3.mjs → chunk-QND56EW2.mjs} +5 -4
- package/dist/js/chunks/chunk-QND56EW2.mjs.map +7 -0
- package/dist/js/chunks/{chunk-NVQ5EHCU.mjs → chunk-QNODAANY.mjs} +4 -4
- package/dist/js/chunks/{chunk-7NLUI3RR.mjs → chunk-QRO5FV3I.mjs} +23 -13
- package/dist/js/chunks/chunk-QRO5FV3I.mjs.map +7 -0
- package/dist/js/chunks/{chunk-FCNF4GRJ.mjs → chunk-R7A7JRC5.mjs} +6 -11
- package/dist/js/chunks/chunk-R7A7JRC5.mjs.map +7 -0
- package/dist/js/chunks/{chunk-S23RJ3XY.mjs → chunk-RKG2CBFE.mjs} +4 -16
- package/dist/js/chunks/chunk-RKG2CBFE.mjs.map +7 -0
- package/dist/js/chunks/{chunk-6FSEUFVW.mjs → chunk-RSNS5OYS.mjs} +6 -6
- package/dist/js/chunks/chunk-RSNS5OYS.mjs.map +7 -0
- package/dist/js/chunks/{chunk-ROH2HDB4.mjs → chunk-RUDYMEJ3.mjs} +2 -2
- package/dist/js/chunks/{chunk-BIGU5RCJ.mjs → chunk-STJR62SU.mjs} +13 -5
- package/dist/js/chunks/chunk-STJR62SU.mjs.map +7 -0
- package/dist/js/chunks/{chunk-752ADW4M.mjs → chunk-SUSD4UTN.mjs} +27 -32
- package/dist/js/chunks/chunk-SUSD4UTN.mjs.map +7 -0
- package/dist/js/chunks/{chunk-B7DQFHA5.mjs → chunk-SYBOA5HC.mjs} +7 -12
- package/dist/js/chunks/chunk-SYBOA5HC.mjs.map +7 -0
- package/dist/js/chunks/{chunk-TFABTUAZ.mjs → chunk-T4MOBP3J.mjs} +2 -2
- package/dist/js/chunks/{chunk-GCWWEZKO.mjs → chunk-TM7HPGTV.mjs} +3 -3
- package/dist/js/chunks/{chunk-5KIHWJI2.mjs → chunk-TMQDZSDC.mjs} +34 -12
- package/dist/js/chunks/chunk-TMQDZSDC.mjs.map +7 -0
- package/dist/js/chunks/{chunk-ZO6ZLHFF.mjs → chunk-TVSX5YA2.mjs} +3 -3
- package/dist/js/chunks/{chunk-INLZWHLY.mjs → chunk-UHO5CV6K.mjs} +2 -2
- package/dist/js/chunks/{chunk-INLZWHLY.mjs.map → chunk-UHO5CV6K.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-7YQBHTK3.mjs → chunk-UKCNELUN.mjs} +6 -8
- package/dist/js/chunks/chunk-UKCNELUN.mjs.map +7 -0
- package/dist/js/chunks/{chunk-VH4BMAZS.mjs → chunk-UKQK5GWR.mjs} +13 -13
- package/dist/js/chunks/chunk-UKQK5GWR.mjs.map +7 -0
- package/dist/js/chunks/{chunk-OYNUDOHR.mjs → chunk-UTSLYQ65.mjs} +7 -6
- package/dist/js/chunks/{chunk-OYNUDOHR.mjs.map → chunk-UTSLYQ65.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-7W7TV6NY.mjs → chunk-VS5OJBOD.mjs} +2 -2
- package/dist/js/chunks/{chunk-7W7TV6NY.mjs.map → chunk-VS5OJBOD.mjs.map} +2 -2
- package/dist/js/chunks/{chunk-KYIEG4U3.mjs → chunk-VZMROW5A.mjs} +43 -14
- package/dist/js/chunks/chunk-VZMROW5A.mjs.map +7 -0
- package/dist/js/chunks/{chunk-VFV75DHF.mjs → chunk-XB6Y7P5W.mjs} +11 -12
- package/dist/js/chunks/chunk-XB6Y7P5W.mjs.map +7 -0
- package/dist/js/chunks/{chunk-CZSQHXEV.mjs → chunk-XDAMOMVD.mjs} +20 -21
- package/dist/js/chunks/chunk-XDAMOMVD.mjs.map +7 -0
- package/dist/js/chunks/{chunk-EVCVFEV3.mjs → chunk-XJPMW5SP.mjs} +21 -35
- package/dist/js/chunks/chunk-XJPMW5SP.mjs.map +7 -0
- package/dist/js/chunks/{chunk-VR3L2WN5.mjs → chunk-YKANQILZ.mjs} +7 -6
- package/dist/js/chunks/chunk-YKANQILZ.mjs.map +7 -0
- package/dist/js/chunks/{chunk-JUKBBN6P.mjs → chunk-YLZOJLDI.mjs} +2 -2
- package/dist/js/chunks/{chunk-U4B3VHFS.mjs → chunk-ZK7YY322.mjs} +28 -21
- package/dist/js/chunks/chunk-ZK7YY322.mjs.map +7 -0
- package/dist/js/expressive.cjs +2739 -2387
- package/dist/js/expressive.cjs.map +4 -4
- package/dist/js/expressive.js +2739 -2387
- package/dist/js/expressive.js.map +4 -4
- package/dist/js/expressive.min.js +7 -7
- package/dist/js/expressive.min.js.map +4 -4
- package/dist/js/expressive.mjs +76 -75
- package/dist/js/expressive.mjs.map +1 -1
- package/dist/js/internal/behaviors/bottomSheets.mjs +3 -3
- package/dist/js/internal/behaviors/copy.mjs +3 -3
- package/dist/js/internal/behaviors/dataTables.mjs +2 -4
- package/dist/js/internal/behaviors/dialogs.mjs +2 -2
- package/dist/js/internal/behaviors/forms.mjs +2 -2
- package/dist/js/internal/behaviors/imageCompare.mjs +2 -2
- package/dist/js/internal/behaviors/lists.mjs +2 -2
- package/dist/js/internal/behaviors/paneResize.mjs +2 -2
- package/dist/js/internal/behaviors/sheetDrag.mjs +2 -2
- package/dist/js/internal/behaviors/sideSheets.mjs +3 -3
- package/dist/js/internal/behaviors/sortable.mjs +2 -2
- package/dist/js/internal/behaviors/theme.mjs +2 -2
- package/dist/js/internal/behaviors/tour.mjs +2 -2
- package/dist/js/internal/behaviors/transferList.mjs +2 -2
- package/dist/js/internal/components/appBar.mjs +3 -3
- package/dist/js/internal/components/autocomplete.mjs +5 -4
- package/dist/js/internal/components/backToTop.mjs +3 -3
- package/dist/js/internal/components/barChart.mjs +5 -5
- package/dist/js/internal/components/buttonGroup.mjs +3 -3
- package/dist/js/internal/components/buttons.mjs +3 -3
- package/dist/js/internal/components/cards.mjs +3 -3
- package/dist/js/internal/components/carousel.mjs +3 -3
- package/dist/js/internal/components/cascader.mjs +4 -4
- package/dist/js/internal/components/characterCounter.mjs +3 -3
- package/dist/js/internal/components/chips.mjs +7 -6
- package/dist/js/internal/components/colorPicker.mjs +3 -3
- package/dist/js/internal/components/columnChart.mjs +4 -4
- package/dist/js/internal/components/commandPalette.mjs +3 -3
- package/dist/js/internal/components/confetti.mjs +3 -3
- package/dist/js/internal/components/currencyInput.mjs +3 -3
- package/dist/js/internal/components/datepicker.mjs +8 -7
- package/dist/js/internal/components/expandingCard.mjs +3 -3
- package/dist/js/internal/components/funnelChart.mjs +4 -4
- package/dist/js/internal/components/heatmapChart.mjs +4 -4
- package/dist/js/internal/components/hoverCard.mjs +3 -3
- package/dist/js/internal/components/index.mjs +60 -59
- package/dist/js/internal/components/inputMask.mjs +3 -3
- package/dist/js/internal/components/keypad.mjs +3 -3
- package/dist/js/internal/components/lightbox.mjs +3 -3
- package/dist/js/internal/components/lineChart.mjs +23 -7
- package/dist/js/internal/components/loadingOverlay.mjs +3 -3
- package/dist/js/internal/components/mapView.mjs +3 -3
- package/dist/js/internal/components/menu.mjs +3 -3
- package/dist/js/internal/components/menubar.mjs +4 -4
- package/dist/js/internal/components/messageScroller.mjs +3 -3
- package/dist/js/internal/components/mixedChart.mjs +5 -5
- package/dist/js/internal/components/navigationRail.mjs +3 -3
- package/dist/js/internal/components/particles.mjs +3 -3
- package/dist/js/internal/components/pieChart.mjs +4 -4
- package/dist/js/internal/components/pullToRefresh.mjs +3 -3
- package/dist/js/internal/components/questionnaire.mjs +3 -3
- package/dist/js/internal/components/radarChart.mjs +4 -4
- package/dist/js/internal/components/radialChart.mjs +4 -4
- package/dist/js/internal/components/registry.mjs +59 -58
- package/dist/js/internal/components/richTextEditor.mjs +3 -3
- package/dist/js/internal/components/sankeyChart.mjs +4 -4
- package/dist/js/internal/components/scatterChart.mjs +4 -4
- package/dist/js/internal/components/scrollspy.mjs +3 -3
- package/dist/js/internal/components/select.mjs +5 -4
- package/dist/js/internal/components/showMore.mjs +3 -3
- package/dist/js/internal/components/slider.mjs +3 -3
- package/dist/js/internal/components/snackbar.mjs +2 -2
- package/dist/js/internal/components/swipeActions.mjs +3 -3
- package/dist/js/internal/components/tabs.mjs +3 -3
- package/dist/js/internal/components/timepicker.mjs +5 -5
- package/dist/js/internal/components/tooltip.mjs +3 -3
- package/dist/js/internal/components/treeSelect.mjs +6 -5
- package/dist/js/internal/components/treemapChart.mjs +4 -4
- package/dist/js/internal/core/component.mjs +6 -4
- package/dist/js/internal/core/formControl.mjs +2 -2
- package/dist/js/internal/core/imageURL.mjs +7 -0
- package/dist/js/internal/core/imageURL.mjs.map +7 -0
- package/dist/js/internal/core/utils.mjs +11 -1
- package/dist/js/internal/plugins/dockedDisplayPlugin.mjs +2 -2
- package/dist/js/internal/plugins/modalDisplayPlugin.mjs +3 -3
- package/dist/js/modular.mjs +76 -75
- package/dist/types/behaviors/bottomSheets.d.ts +5 -3
- package/dist/types/behaviors/dataTables.d.ts +2 -6
- package/dist/types/behaviors/forms.d.ts +7 -7
- package/dist/types/behaviors/imageCompare.d.ts +2 -1
- package/dist/types/behaviors/transferList.d.ts +2 -0
- package/dist/types/components/autocomplete.d.ts +14 -3
- package/dist/types/components/carousel.d.ts +7 -0
- package/dist/types/components/chips.d.ts +15 -0
- package/dist/types/components/colorPicker.d.ts +14 -0
- package/dist/types/components/currencyInput.d.ts +5 -4
- package/dist/types/components/datepicker.d.ts +54 -27
- package/dist/types/components/funnelChart.d.ts +1 -4
- package/dist/types/components/heatmapChart.d.ts +3 -0
- package/dist/types/components/hoverCard.d.ts +1 -0
- package/dist/types/components/inputMask.d.ts +0 -2
- package/dist/types/components/lineChart.d.ts +43 -5
- package/dist/types/components/mapView.d.ts +113 -11
- package/dist/types/components/menu.d.ts +14 -0
- package/dist/types/components/particles.d.ts +14 -1
- package/dist/types/components/pieChart.d.ts +1 -2
- package/dist/types/components/richTextEditor.d.ts +40 -9
- package/dist/types/components/select.d.ts +1 -3
- package/dist/types/components/showMore.d.ts +2 -0
- package/dist/types/components/slider.d.ts +0 -1
- package/dist/types/components/snackbar.d.ts +7 -1
- package/dist/types/components/tabs.d.ts +3 -3
- package/dist/types/components/timepicker.d.ts +15 -6
- package/dist/types/components/tooltip.d.ts +1 -1
- package/dist/types/components/treemapChart.d.ts +2 -2
- package/dist/types/core/component.d.ts +9 -3
- package/dist/types/core/imageURL.d.ts +2 -0
- package/dist/types/core/utils.d.ts +39 -1
- package/dist/types/modular.d.ts +1 -1
- package/dist/types/plugins/modalDisplayPlugin.d.ts +8 -1
- package/package.json +8 -6
- package/src/sass/abstracts/_mixins.scss +459 -59
- package/src/sass/abstracts/_variables.scss +25 -0
- package/src/sass/components/_accordion.scss +1 -3
- package/src/sass/components/_aura.scss +1 -0
- package/src/sass/components/_back-to-top.scss +3 -3
- package/src/sass/components/_bar-chart.scss +2 -83
- package/src/sass/components/_bottom-sheet.scss +7 -0
- package/src/sass/components/_breadcrumb.scss +1 -7
- package/src/sass/components/_button-groups.scss +15 -38
- package/src/sass/components/_buttons.scss +24 -74
- package/src/sass/components/_calendar.scss +1 -3
- package/src/sass/components/_carousel.scss +10 -10
- package/src/sass/components/_cascader.scss +3 -14
- package/src/sass/components/_column-chart.scss +1 -19
- package/src/sass/components/_confetti.scss +3 -4
- package/src/sass/components/_data-table.scss +10 -0
- package/src/sass/components/_dialog.scss +10 -18
- package/src/sass/components/_expanding-card.scss +1 -5
- package/src/sass/components/_fab-menu.scss +4 -12
- package/src/sass/components/_funnel-chart.scss +1 -62
- package/src/sass/components/_heatmap-chart.scss +2 -6
- package/src/sass/components/_icon-buttons.scss +1 -4
- package/src/sass/components/_line-chart.scss +2 -70
- package/src/sass/components/_map.scss +4 -0
- package/src/sass/components/_marquee.scss +3 -3
- package/src/sass/components/_mega-menu.scss +8 -17
- package/src/sass/components/_menu.scss +10 -11
- package/src/sass/components/_menubar.scss +1 -13
- package/src/sass/components/_mixed-chart.scss +1 -37
- package/src/sass/components/_navbar.scss +11 -16
- package/src/sass/components/_navigation-bar.scss +2 -5
- package/src/sass/components/_navigation-rail.scss +25 -15
- package/src/sass/components/_page-footer.scss +1 -3
- package/src/sass/components/_pagination.scss +3 -14
- package/src/sass/components/_panes.scss +3 -16
- package/src/sass/components/_particles.scss +3 -4
- package/src/sass/components/_pie-chart.scss +8 -70
- package/src/sass/components/_progress.scss +31 -13
- package/src/sass/components/_radar-chart.scss +2 -32
- package/src/sass/components/_radial-chart.scss +2 -48
- package/src/sass/components/_sankey-chart.scss +5 -28
- package/src/sass/components/_scatter-chart.scss +2 -11
- package/src/sass/components/_search.scss +1 -4
- package/src/sass/components/_side-sheet.scss +9 -14
- package/src/sass/components/_skeleton.scss +24 -15
- package/src/sass/components/_snackbar.scss +11 -13
- package/src/sass/components/_table_of_contents.scss +1 -3
- package/src/sass/components/_tabs.scss +4 -20
- package/src/sass/components/_timepicker.scss +2 -1
- package/src/sass/components/_toolbar.scss +1 -4
- package/src/sass/components/_tooltip.scss +9 -5
- package/src/sass/components/_tree.scss +5 -17
- package/src/sass/components/_treemap-chart.scss +2 -26
- package/src/sass/components/forms/_checkboxes.scss +2 -21
- package/src/sass/components/forms/_drop-zone.scss +1 -4
- package/src/sass/components/forms/_fieldset.scss +1 -4
- package/src/sass/components/forms/_file-input.scss +6 -0
- package/src/sass/components/forms/_input-fields.scss +21 -4
- package/src/sass/components/forms/_radio-buttons.scss +2 -24
- package/src/sass/components/forms/_select.scss +6 -7
- package/src/sass/components/forms/_slider.scss +1 -4
- package/src/sass/components/forms/_switches.scss +14 -21
- package/src/sass/utilities/_helpers.scss +1 -1
- package/src/sass/utilities/_visibility.scss +1 -3
- package/dist/js/chunks/chunk-2CS6TITK.mjs.map +0 -7
- package/dist/js/chunks/chunk-3L3QIYLC.mjs.map +0 -7
- package/dist/js/chunks/chunk-4IKVPCSU.mjs +0 -7
- package/dist/js/chunks/chunk-4QUCNFSD.mjs.map +0 -7
- package/dist/js/chunks/chunk-4TRZ2AER.mjs.map +0 -7
- package/dist/js/chunks/chunk-5KIHWJI2.mjs.map +0 -7
- package/dist/js/chunks/chunk-6FSEUFVW.mjs.map +0 -7
- package/dist/js/chunks/chunk-752ADW4M.mjs.map +0 -7
- package/dist/js/chunks/chunk-7NLUI3RR.mjs.map +0 -7
- package/dist/js/chunks/chunk-7YQBHTK3.mjs.map +0 -7
- package/dist/js/chunks/chunk-7YZ5CN3V.mjs.map +0 -7
- package/dist/js/chunks/chunk-B7DQFHA5.mjs.map +0 -7
- package/dist/js/chunks/chunk-BIGU5RCJ.mjs.map +0 -7
- package/dist/js/chunks/chunk-BPF3I2MN.mjs.map +0 -7
- package/dist/js/chunks/chunk-CMGXGYUC.mjs.map +0 -7
- package/dist/js/chunks/chunk-CSYWKEK3.mjs.map +0 -7
- package/dist/js/chunks/chunk-CZSQHXEV.mjs.map +0 -7
- package/dist/js/chunks/chunk-DZYVU75J.mjs.map +0 -7
- package/dist/js/chunks/chunk-EC26RU2D.mjs.map +0 -7
- package/dist/js/chunks/chunk-EVCVFEV3.mjs.map +0 -7
- package/dist/js/chunks/chunk-FCNF4GRJ.mjs.map +0 -7
- package/dist/js/chunks/chunk-GWZ76SR4.mjs.map +0 -7
- package/dist/js/chunks/chunk-KYIEG4U3.mjs.map +0 -7
- package/dist/js/chunks/chunk-KZPSAXIU.mjs.map +0 -7
- package/dist/js/chunks/chunk-LDXCC2L3.mjs.map +0 -7
- package/dist/js/chunks/chunk-NBJAUGPP.mjs.map +0 -7
- package/dist/js/chunks/chunk-OUCHKSAW.mjs.map +0 -7
- package/dist/js/chunks/chunk-P6DRQ6KS.mjs.map +0 -7
- package/dist/js/chunks/chunk-PA7NU5AK.mjs.map +0 -7
- package/dist/js/chunks/chunk-QACZ6TUP.mjs.map +0 -7
- package/dist/js/chunks/chunk-QXKWVHBD.mjs.map +0 -7
- package/dist/js/chunks/chunk-S23RJ3XY.mjs.map +0 -7
- package/dist/js/chunks/chunk-SPPHF7YZ.mjs.map +0 -7
- package/dist/js/chunks/chunk-U4B3VHFS.mjs.map +0 -7
- package/dist/js/chunks/chunk-UGJHCHXG.mjs.map +0 -7
- package/dist/js/chunks/chunk-VFV75DHF.mjs.map +0 -7
- package/dist/js/chunks/chunk-VGY4M57S.mjs.map +0 -7
- package/dist/js/chunks/chunk-VH4BMAZS.mjs.map +0 -7
- package/dist/js/chunks/chunk-VJVQIXHC.mjs.map +0 -7
- package/dist/js/chunks/chunk-VMY55K2X.mjs.map +0 -7
- package/dist/js/chunks/chunk-VR3L2WN5.mjs.map +0 -7
- package/dist/js/chunks/chunk-WU7XJ4RR.mjs.map +0 -7
- package/dist/js/chunks/chunk-Y6TLRA3U.mjs.map +0 -7
- package/dist/js/chunks/chunk-Z4GICXR2.mjs.map +0 -7
- package/dist/js/chunks/chunk-Z4OARDZ7.mjs.map +0 -7
- package/dist/js/chunks/chunk-ZM3XBHWV.mjs.map +0 -7
- package/dist/js/chunks/chunk-ZVLOUDIJ.mjs.map +0 -7
- /package/dist/js/chunks/{chunk-Y6H2X7ZC.mjs.map → chunk-3KF65KGL.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-75E5EDJF.mjs.map → chunk-3XORNED5.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-NNHVANQA.mjs.map → chunk-4WBGAQN3.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-ZJNLAGY5.mjs.map → chunk-5F4URJHL.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-GVZQH2H5.mjs.map → chunk-AEG6LOAJ.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-AI73GJKG.mjs.map → chunk-BB5VXYXT.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-2RLBDFH4.mjs.map → chunk-BDATLH3H.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-ZKWUWBUB.mjs.map → chunk-CY2CTOVM.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-67ZPPKXS.mjs.map → chunk-FNIIH2LU.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-T3BNSRP3.mjs.map → chunk-HQRLBTNM.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-NAC72P7R.mjs.map → chunk-I2F7MLUU.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-ANKFWNWT.mjs.map → chunk-LC4O5LFS.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-4PIXLDNJ.mjs.map → chunk-LEF33SCP.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-ILDRRG7T.mjs.map → chunk-LFORZRPH.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-4EWGD4V2.mjs.map → chunk-NODZ5VAD.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-NVQ5EHCU.mjs.map → chunk-QNODAANY.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-ROH2HDB4.mjs.map → chunk-RUDYMEJ3.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-TFABTUAZ.mjs.map → chunk-T4MOBP3J.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-GCWWEZKO.mjs.map → chunk-TM7HPGTV.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-ZO6ZLHFF.mjs.map → chunk-TVSX5YA2.mjs.map} +0 -0
- /package/dist/js/chunks/{chunk-JUKBBN6P.mjs.map → chunk-YLZOJLDI.mjs.map} +0 -0
|
@@ -4,13 +4,47 @@ function restoreAttribute(el, name, value) {
|
|
|
4
4
|
if (value === null) el.removeAttribute(name);
|
|
5
5
|
else el.setAttribute(name, value);
|
|
6
6
|
}
|
|
7
|
+
function numberValue(raw) {
|
|
8
|
+
const text = raw.replace(/−/g, "-").replace(/[\s%\p{Sc}]/gu, "");
|
|
9
|
+
if (!/^[+-]?(\d{1,3}(,\d{3})+|\d+)?(\.\d+)?(e[+-]?\d+)?$/i.test(text)) return null;
|
|
10
|
+
const value = parseFloat(text.replace(/,/g, ""));
|
|
11
|
+
return Number.isFinite(value) ? value : null;
|
|
12
|
+
}
|
|
13
|
+
function formEventScope(el) {
|
|
14
|
+
const root = el.getRootNode();
|
|
15
|
+
return "host" in root ? root : el.ownerDocument;
|
|
16
|
+
}
|
|
17
|
+
function afterFormReset(el, restored, signal) {
|
|
18
|
+
const handled = /* @__PURE__ */ new WeakSet();
|
|
19
|
+
const onReset = (e) => {
|
|
20
|
+
if (e.target !== el.form || handled.has(e)) return;
|
|
21
|
+
handled.add(e);
|
|
22
|
+
setTimeout(() => signal.aborted || e.defaultPrevented || restored());
|
|
23
|
+
};
|
|
24
|
+
const bind = () => {
|
|
25
|
+
for (const target of [formEventScope(el), el.form]) target?.addEventListener("reset", onReset, { capture: true, signal });
|
|
26
|
+
};
|
|
27
|
+
bind();
|
|
28
|
+
return bind;
|
|
29
|
+
}
|
|
30
|
+
function afterAnyFormReset(restored) {
|
|
31
|
+
document.addEventListener("reset", (e) => {
|
|
32
|
+
const form = e.target;
|
|
33
|
+
if (form instanceof HTMLFormElement) setTimeout(() => e.defaultPrevented || restored(form));
|
|
34
|
+
}, true);
|
|
35
|
+
}
|
|
36
|
+
function deepActiveElement(root) {
|
|
37
|
+
let active = root.activeElement;
|
|
38
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
39
|
+
return active;
|
|
40
|
+
}
|
|
7
41
|
function footerButton(parent, text, className, onClick, visible = true) {
|
|
8
42
|
const button = document.createElement("button");
|
|
9
43
|
button.className = `${className} text`;
|
|
10
44
|
button.style.visibility = visible ? "visible" : "hidden";
|
|
11
45
|
button.type = "button";
|
|
12
46
|
button.tabIndex = visible ? 0 : -1;
|
|
13
|
-
button.innerText = text;
|
|
47
|
+
button.innerText = text ?? "";
|
|
14
48
|
button.addEventListener("click", onClick);
|
|
15
49
|
parent.append(button);
|
|
16
50
|
}
|
|
@@ -123,6 +157,15 @@ var Utils = class _Utils {
|
|
|
123
157
|
const root = el.getRootNode();
|
|
124
158
|
return root.getElementById ? root.getElementById(id) : document.getElementById(id);
|
|
125
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* `querySelector` against the tree `el` lives in, then the document, so a
|
|
162
|
+
* selector option finds an element in the same shadow root and still finds
|
|
163
|
+
* one outside it, such as `body`.
|
|
164
|
+
*/
|
|
165
|
+
static querySelector(el, selector) {
|
|
166
|
+
const root = el.getRootNode();
|
|
167
|
+
return "getElementById" in root && root.querySelector(selector) || document.querySelector(selector);
|
|
168
|
+
}
|
|
126
169
|
/**
|
|
127
170
|
* Adds or removes one name in an element's inline `anchor-name` list. A
|
|
128
171
|
* trigger can anchor a Tooltip and a Menu at once, and writing the whole
|
|
@@ -243,9 +286,14 @@ var Utils = class _Utils {
|
|
|
243
286
|
|
|
244
287
|
export {
|
|
245
288
|
restoreAttribute,
|
|
289
|
+
numberValue,
|
|
290
|
+
formEventScope,
|
|
291
|
+
afterFormReset,
|
|
292
|
+
afterAnyFormReset,
|
|
293
|
+
deepActiveElement,
|
|
246
294
|
pickerFooter,
|
|
247
295
|
controllerFor,
|
|
248
296
|
svgNode,
|
|
249
297
|
Utils
|
|
250
298
|
};
|
|
251
|
-
//# sourceMappingURL=chunk-
|
|
299
|
+
//# sourceMappingURL=chunk-67UNY7RH.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/ts/core/utils.ts"],
|
|
4
|
+
"sourcesContent": ["/** One axis of a viewport clamp: `start` moved so `size` fits in `view`. */\nconst fit = (start: number, size: number, view: number, offset: number) =>\n start < offset ? offset : start + size > view - offset ? view - size : start;\n\n/** Sets an attribute, or removes it for null, as when restoring a value read with getAttribute. */\nexport function restoreAttribute(el: Element, name: string, value: string | null) {\n if (value === null) el.removeAttribute(name);\n else el.setAttribute(name, value);\n}\n\n/**\n * Reads text as one number, which currency or percent signs, grouping commas\n * and a typographic minus may surround, or null for anything else. Charts and\n * data tables read their cells with it.\n */\nexport function numberValue(raw: string): number | null {\n const text = raw.replace(/\u2212/g, '-').replace(/[\\s%\\p{Sc}]/gu, '');\n if (!/^[+-]?(\\d{1,3}(,\\d{3})+|\\d+)?(\\.\\d+)?(e[+-]?\\d+)?$/i.test(text)) return null;\n // The pattern also lets through \"e5\" (NaN) and \"1e400\" (Infinity).\n const value = parseFloat(text.replace(/,/g, ''));\n return Number.isFinite(value) ? value : null;\n}\n\n/**\n * Where a field listens for its form's events. Neither formdata nor reset\n * leaves a shadow root, so a field in one listens there. Otherwise the\n * document, so the listener follows the field into another form.\n */\nexport function formEventScope(el: Element): Document | ShadowRoot {\n const root = el.getRootNode() as Document | ShadowRoot;\n return 'host' in root ? root : el.ownerDocument;\n}\n\n/**\n * Runs `restored` after a reset of the field's form. The form restores its\n * fields after the reset event, so it waits a task. It skips a cancelled\n * reset, and a field whose listeners `signal` has removed since.\n *\n * It listens on the field's root, which hears a form the field moves into\n * later, and on the form itself, which still hears it after the form moves\n * into a shadow root. Reset does not leave a shadow root. The returned\n * function binds both again, for a field that moved into another root;\n * listeners already bound are not added twice.\n */\nexport function afterFormReset(\n el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,\n restored: () => void,\n signal: AbortSignal\n) {\n const handled = new WeakSet<Event>();\n const onReset = (e: Event) => {\n if (e.target !== el.form || handled.has(e)) return;\n handled.add(e);\n setTimeout(() => signal.aborted || e.defaultPrevented || restored());\n };\n const bind = () => {\n for (const target of [formEventScope(el), el.form]) target?.addEventListener('reset', onReset, { capture: true, signal });\n };\n bind();\n return bind;\n}\n\n/**\n * Runs `restored(form)` a task after any form in the document resets, once\n * the form has restored its fields, unless the reset was cancelled. For\n * document-level behaviors; a component bound to one field uses afterFormReset.\n */\nexport function afterAnyFormReset(restored: (form: HTMLFormElement) => void) {\n document.addEventListener('reset', (e) => {\n const form = e.target;\n if (form instanceof HTMLFormElement) setTimeout(() => e.defaultPrevented || restored(form));\n }, true);\n}\n\n/** The focused element, followed down through open shadow roots. */\nexport function deepActiveElement(root: Document | ShadowRoot): Element | null {\n let active = root.activeElement;\n while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;\n return active;\n}\n\n/** A picker footer text button; a hidden one is also out of the tab order. */\nfunction footerButton(parent: HTMLElement, text: string | undefined, className: string, onClick: () => void, visible = true) {\n const button = document.createElement('button');\n button.className = `${className} text`;\n button.style.visibility = visible ? 'visible' : 'hidden';\n button.type = 'button';\n button.tabIndex = visible ? 0 : -1;\n button.innerText = text ?? '';\n button.addEventListener('click', onClick);\n parent.append(button);\n}\n\n/**\n * Fills a picker's footer: a Clear button, hidden unless `showClearBtn`,\n * then Cancel and Done unless the picker writes its value without them.\n */\nexport function pickerFooter(\n footer: HTMLElement,\n { i18n, showClearBtn, autoSubmit }: { i18n: Partial<Record<'clear' | 'cancel' | 'done', string>>; showClearBtn: boolean; autoSubmit: boolean },\n clearClass: string,\n onClear: () => void,\n onConfirm: () => void,\n onCancel: () => void\n) {\n footerButton(footer, i18n.clear, clearClass, onClear, showClearBtn);\n if (autoSubmit) return;\n const buttons = document.createElement('div');\n buttons.classList.add('confirmation-btns');\n footer.append(buttons);\n footerButton(buttons, i18n.cancel, 'btn-cancel', onCancel);\n footerButton(buttons, i18n.done, 'btn-confirm', onConfirm);\n}\n\n/**\n * An AbortController from the window `node` is in. Its signal removes\n * listeners added there, and jsdom rejects a signal from another window.\n */\nexport function controllerFor(node: Node): AbortController {\n return new (node.ownerDocument?.defaultView ?? window).AbortController();\n}\n\n/** An SVG element with its attributes. */\nexport function svgNode(name: string, attributes: Record<string, string> = {}): SVGElement {\n const element = document.createElementNS('http://www.w3.org/2000/svg', name);\n for (const key in attributes) element.setAttribute(key, attributes[key]);\n return element;\n}\n\n/**\n * Class with utilitary functions for global usage.\n */\nexport class Utils {\n /**\n * Whether a keydown belongs to an IME composition, such as the Enter that\n * confirms a candidate. Safari reports `isComposing` false for that Enter,\n * so keyCode 229 covers it.\n */\n static isComposing(e: KeyboardEvent): boolean {\n return e.isComposing || e.keyCode === 229;\n }\n\n /**\n * `handler`, skipped for every key of an IME composition: while the user\n * picks a candidate, Enter, the arrows and Escape belong to the IME.\n */\n static ignoringComposition(handler: (e: KeyboardEvent) => void): (e: KeyboardEvent) => void {\n return (e) => {\n if (!Utils.isComposing(e)) handler(e);\n };\n }\n\n /**\n * Run `callback` once the document is parsed.\n *\n * The document-level behaviors all used a bare DOMContentLoaded listener,\n * which never fires if the bundle is loaded after that event - an async or\n * deferred script tag, or a dynamic import. Checking readyState first covers\n * both orders.\n */\n static onDocumentReady(callback: () => void): void {\n if (typeof document === 'undefined') return;\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', callback, { once: true });\n } else {\n callback();\n }\n }\n\n /**\n * The language an element is written in, from the nearest `lang`, or\n * undefined without one or for a tag the browser does not know.\n */\n static locale(el: Element): string | undefined {\n // Inside a shadow root the language comes from the host's tree, unless\n // something nearer sets one.\n let [node, lang]: [Element | undefined, string | undefined] = [el, undefined];\n while (node && lang === undefined) {\n lang = node.closest<HTMLElement>('[lang]')?.lang;\n node = (node.getRootNode() as ShadowRoot).host;\n }\n try {\n return lang ? Intl.getCanonicalLocales(lang)[0] : undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Generates a unique string identifier. Every caller prefixes the result and\n * uses it as an element id, and a UUID is valid in that position.\n *\n * `randomUUID` is exposed only in secure contexts, and a dev server on a\n * plain-HTTP LAN address - the usual way a page is opened on a phone - is\n * not one. Without the fallback every component that stamps an id would\n * throw there. The ids only have to be unique within one document, so the\n * fallback does not need to be cryptographic.\n */\n static guid(): string {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n );\n }\n\n /**\n * Where an element created at runtime has to be appended to stay in the same\n * tree as `el`. A stylesheet adopted into a shadow root cannot match a node\n * in the document, so a portal that lands on `document.body` is unstyled\n * whenever its origin lives in a shadow root (adr/0002).\n *\n * Light-DOM elements still get `document.body`, which is what escapes an\n * ancestor's `overflow: hidden` and stacking context. Inside a shadow root\n * both hazards come back: the host's own ancestors still form the containing\n * block, so a transformed ancestor re-anchors `position: fixed` and a\n * positioned one re-anchors `position: absolute`.\n */\n static portalRoot(el: Node): HTMLElement | ShadowRoot {\n const root = el.getRootNode() as ShadowRoot;\n // Only a ShadowRoot has a host; a Document or a detached subtree does not.\n return root.host ? root : document.body;\n }\n\n /**\n * `getElementById` against the tree `el` lives in. The mirror of\n * `portalRoot`: an id in a shadow root is invisible to\n * `document.getElementById`, so a trigger and its target inside the same\n * root could not find each other at all.\n *\n * Falls back to the document for a detached `el`, whose root is a plain\n * element with no lookup of its own.\n */\n static getElementById(el: Node, id: string): HTMLElement {\n const root = el.getRootNode() as Document | ShadowRoot;\n return (root.getElementById ? root.getElementById(id) : document.getElementById(id)) as HTMLElement;\n }\n\n /**\n * `querySelector` against the tree `el` lives in, then the document, so a\n * selector option finds an element in the same shadow root and still finds\n * one outside it, such as `body`.\n */\n static querySelector(el: Node, selector: string): HTMLElement | null {\n const root = el.getRootNode() as Document | ShadowRoot;\n return ('getElementById' in root && root.querySelector<HTMLElement>(selector)) || document.querySelector<HTMLElement>(selector);\n }\n\n /**\n * Adds or removes one name in an element's inline `anchor-name` list. A\n * trigger can anchor a Tooltip and a Menu at once, and writing the whole\n * property let the last writer, or the first to be destroyed, wipe the other.\n */\n static setAnchorName(el: HTMLElement, name: string, on: boolean) {\n const names = el.style.getPropertyValue('anchor-name').split(',').map((n) => n.trim())\n .filter((n) => n && n !== 'none' && n !== name);\n if (on) names.push(name);\n if (names.length) el.style.setProperty('anchor-name', names.join(', '));\n else el.style.removeProperty('anchor-name');\n }\n\n /**\n * Fires `expressive:<type>` from `el`. It bubbles and crosses shadow roots,\n * so a page can listen on the element, an ancestor or the document. A\n * `cancelable` event returns false once a listener calls preventDefault().\n */\n static emit(el: Element, type: string, detail?: unknown, cancelable = false) {\n return el.dispatchEvent(new CustomEvent(`expressive:${type}`, { bubbles: true, composed: true, cancelable, detail }));\n }\n\n /**\n * The element an event really started on, for a listener on the document.\n * There `event.target` is retargeted to the shadow host, so delegation that\n * reads it never sees anything inside an open shadow root. A closed root\n * keeps its inside private and still yields the host.\n */\n static eventTarget(event: Event): Element | null {\n const target = event.composedPath()[0] ?? event.target;\n return target instanceof Element ? target : null;\n }\n\n /** Whether a key was pressed without Alt, Ctrl or Meta, which belong to the browser and the system. */\n static plainKey(e: KeyboardEvent): boolean {\n return !(e.altKey || e.ctrlKey || e.metaKey);\n }\n\n /** Where a key moves from `index` among `count` items: by `steps`, or to an end for Home and End. NaN for any other key. */\n static _keyIndex(key: string, index: number, count: number, steps: Record<string, number>) {\n if (key === 'Home') return 0;\n if (key === 'End') return count - 1;\n return index + (steps[key] ?? NaN);\n }\n\n /**\n * The item a key moves focus to, from the focused one among `items`: by\n * the offset `steps` gives the key, or to the first or last for Home and\n * End. Disabled items are skipped. Undefined for other keys, for a key\n * with a modifier, when focus is on none of the items, and past either end\n * unless `wrap`.\n */\n static focusTarget(items: HTMLElement[], e: KeyboardEvent, steps: Record<string, number>, wrap = false): HTMLElement | undefined {\n const enabled = items.filter((item) => !item.matches(':disabled, [aria-disabled=\"true\"], .disabled'));\n const index = enabled.indexOf(Utils.eventTarget(e) as HTMLElement);\n if (index < 0 || !Utils.plainKey(e)) return undefined;\n const to = Utils._keyIndex(e.key, index, enabled.length, steps);\n return enabled[wrap ? (to + enabled.length) % enabled.length : to];\n }\n\n /**\n * Whether focus is on or inside any of `els`, read from each element's own\n * root. A document listener cannot tell from `composedPath()` when the\n * element sits in a closed shadow root, but the element's root still knows\n * what has focus.\n */\n static focusIsIn(...els: (Element | null | undefined)[]): boolean {\n return els.some((el) => {\n const active = el && (el.getRootNode() as Document | ShadowRoot).activeElement;\n return !!active && el.contains(active);\n });\n }\n\n /**\n * `closest()` for a document listener: the nearest element on the event's\n * composed path that matches `selector`. Unlike `eventTarget(e).closest()`\n * it climbs out of shadow roots too, so a press on a shadow-root icon inside\n * a light-DOM button still finds the button.\n */\n static closestInPath<T extends Element = Element>(event: Event, selector: string): T | null {\n for (const node of event.composedPath()) {\n if (node instanceof Element && node.matches(selector)) return node as T;\n }\n return null;\n }\n\n /**\n * Retrieves target element id from trigger.\n * @param trigger Trigger element.\n */\n static getIdFromTrigger(trigger: HTMLElement): string {\n return trigger.dataset.target || (trigger.getAttribute('href')?.slice(1) ?? '');\n }\n\n /**\n * Writes document coordinates as `top`/`left` on an absolutely positioned\n * element. A static body uses document coordinates; positioned parents,\n * such as a shadow host, use their padding edge, including borders and\n * local scrolling.\n */\n static _placeInOffsetParent(el: HTMLElement, coordinates: { x: number; y: number }) {\n let { x, y } = coordinates;\n const parent = el.offsetParent as HTMLElement;\n if (parent && (parent !== document.body || getComputedStyle(parent).position !== 'static')) {\n const rect = parent.getBoundingClientRect();\n const root = parent === document.scrollingElement;\n x -= rect.left + window.scrollX + parent.clientLeft - (root ? 0 : parent.scrollLeft);\n y -= rect.top + window.scrollY + parent.clientTop - (root ? 0 : parent.scrollTop);\n }\n el.style.top = y + 'px';\n el.style.left = x + 'px';\n }\n\n /**\n * Moves document coordinates so a `width` by `height` box stays in the\n * viewport. A box past the left or top edge, or within `offset` of it,\n * starts `offset` in; a box past the right or bottom edge, or within\n * `offset` of it, ends at that edge. `offset` is the margin plus the slide\n * distance, or minus it for right alignment.\n */\n static _repositionWithinScreen(\n x: number,\n y: number,\n width: number,\n height: number,\n margin: number,\n transitionMovement: number,\n align: string\n ) {\n const offset = margin + (align === 'right' ? -transitionMovement : transitionMovement);\n return {\n x: window.scrollX + fit(x - window.scrollX, width, window.innerWidth, offset),\n y: window.scrollY + fit(y - window.scrollY, height, window.innerHeight, offset)\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Component
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EB2V4OKL.mjs";
|
|
4
4
|
import {
|
|
5
5
|
Utils,
|
|
6
6
|
restoreAttribute
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-67UNY7RH.mjs";
|
|
8
8
|
|
|
9
9
|
// src/ts/components/appBar.ts
|
|
10
10
|
var _defaults = {};
|
|
11
11
|
var OPEN_KEYS = ["Enter", "ArrowDown"];
|
|
12
|
+
var opensSearch = (e) => OPEN_KEYS.includes(e.key) && Utils.plainKey(e) && !Utils.isComposing(e);
|
|
12
13
|
var AppBar = class extends Component {
|
|
13
14
|
constructor(el, options) {
|
|
14
15
|
super(el, options);
|
|
@@ -19,7 +20,7 @@ var AppBar = class extends Component {
|
|
|
19
20
|
// The docked view's state before the first open, for destroy() to restore.
|
|
20
21
|
this._restore = null;
|
|
21
22
|
this._onIntersect = (entries) => {
|
|
22
|
-
const entry = entries
|
|
23
|
+
const entry = entries.at(-1);
|
|
23
24
|
if (!entry) return;
|
|
24
25
|
this.el.classList.toggle(
|
|
25
26
|
"collapsed",
|
|
@@ -33,7 +34,7 @@ var AppBar = class extends Component {
|
|
|
33
34
|
if (!(this._view instanceof HTMLDialogElement)) this._openSearch();
|
|
34
35
|
};
|
|
35
36
|
this._onSearchKeydown = (e) => {
|
|
36
|
-
if (!
|
|
37
|
+
if (!opensSearch(e) || !(this._view instanceof HTMLDialogElement)) return;
|
|
37
38
|
e.preventDefault();
|
|
38
39
|
this._openSearch();
|
|
39
40
|
};
|
|
@@ -93,4 +94,4 @@ AppBar.key = "Expressive_AppBar";
|
|
|
93
94
|
export {
|
|
94
95
|
AppBar
|
|
95
96
|
};
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-7CXNATJS.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/ts/components/appBar.ts"],
|
|
4
|
+
"sourcesContent": ["import { Component, BaseOptions } from '../core/component';\nimport { Utils, restoreAttribute } from '../core/utils';\n\nconst _defaults: BaseOptions = {};\n// Keys that open a full-screen search view from its field.\nconst OPEN_KEYS = ['Enter', 'ArrowDown'];\n/** Whether a key opens the view. Alt, Ctrl and Meta stay the browser's, and a composing key the IME's. */\nconst opensSearch = (e: KeyboardEvent) => OPEN_KEYS.includes(e.key) && Utils.plainKey(e) && !Utils.isComposing(e);\n\n/**\n * Collapse medium/large flexible app bars on scroll, and open the related\n * search view when the search field in a search app bar is selected.\n *\n * Observation is IntersectionObserver on a sentinel after the header.\n * A scroll listener would re-enter layout on every tick; this does not.\n */\nexport class AppBar extends Component<BaseOptions> {\n private _observer: IntersectionObserver | null = null;\n private _sentinel: HTMLElement | null = null;\n private _input: HTMLInputElement | null = null;\n private _view: HTMLElement | null = null;\n // The docked view's state before the first open, for destroy() to restore.\n private _restore: { hidden: HTMLElement['hidden']; expanded: string | null } | null = null;\n\n constructor(el: HTMLElement, options: Partial<BaseOptions>) {\n super(el, options);\n this._setupCollapse();\n this._setupSearch();\n }\n\n static get defaults(): BaseOptions {\n return _defaults;\n }\n\n static key = 'Expressive_AppBar';\n\n destroy() {\n this._observer?.disconnect();\n this._observer = null;\n this._sentinel?.remove();\n this._sentinel = null;\n if (this._restore) {\n this._view.hidden = this._restore.hidden;\n restoreAttribute(this._input, 'aria-expanded', this._restore.expanded);\n this._restore = null;\n }\n this.el.classList.remove('collapsed');\n super.destroy();\n }\n\n private _setupCollapse() {\n if (!this.el.classList.contains('medium') && !this.el.classList.contains('large')) {\n return;\n }\n // Collapsing shrinks the header, and scroll anchoring then scrolls the page\n // back by the height it lost. The sentinel follows the header, so it moves\n // with the page and that correction cannot bring it back into view. It sits\n // 64px, the small bar's height, above the header's bottom edge. An expanded\n // bar therefore collapses once the page has scrolled by the height that\n // collapsing removes, and a collapsed bar expands when the page is back at\n // its top. Zero height and no margins keep it out of the layout.\n this._sentinel = document.createElement('span');\n this._sentinel.setAttribute('aria-hidden', 'true');\n this._sentinel.style.cssText =\n 'display:block;height:0;position:relative;top:-64px;pointer-events:none;visibility:hidden';\n this.el.insertAdjacentElement('afterend', this._sentinel);\n this._observer = new IntersectionObserver(this._onIntersect);\n this._observer.observe(this._sentinel);\n }\n\n private _onIntersect = (entries: IntersectionObserverEntry[]) => {\n // The newest: a busy frame can queue several.\n const entry = entries.at(-1);\n if (!entry) return;\n // Collapse only when the sentinel has gone above the pinned header. A\n // sentinel below the viewport would move back into view as the bar shrank,\n // and the bar would switch sizes on every frame.\n this.el.classList.toggle(\n 'collapsed',\n !entry.isIntersecting && entry.boundingClientRect.top < this.el.getBoundingClientRect().top\n );\n };\n\n private _setupSearch() {\n this._input = this.el.querySelector('input[type=\"search\"]');\n if (!this._input) return;\n\n const id = this._input.getAttribute('aria-controls');\n this._view = (id && Utils.getElementById(this.el, id)) || this.el.querySelector('.search-view');\n if (!this._view) return;\n\n const { signal } = this._events;\n this._input.addEventListener('focus', this._onSearchFocus, { signal });\n this._input.addEventListener('click', this._openSearch, { signal });\n this._input.addEventListener('keydown', this._onSearchKeydown, { signal });\n }\n\n // A docked view opens on focus, like a combobox popup. A full-screen\n // dialog takes over the page, so reaching the field with Tab only focuses\n // it (WCAG 3.2.1); a click, Enter or ArrowDown opens it.\n private _onSearchFocus = () => {\n if (!(this._view instanceof HTMLDialogElement)) this._openSearch();\n };\n\n private _onSearchKeydown = (e: KeyboardEvent) => {\n if (!opensSearch(e) || !(this._view instanceof HTMLDialogElement)) return;\n e.preventDefault();\n this._openSearch();\n };\n\n private _openSearch = () => {\n if (this._view instanceof HTMLDialogElement) {\n if (!this._view.open) this._view.showModal();\n return;\n }\n this._restore ??= { hidden: this._view.hidden, expanded: this._input.getAttribute('aria-expanded') };\n this._view.hidden = false;\n this._input.setAttribute('aria-expanded', 'true');\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Component
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EB2V4OKL.mjs";
|
|
4
4
|
import {
|
|
5
5
|
Utils
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-67UNY7RH.mjs";
|
|
7
7
|
|
|
8
8
|
// src/ts/components/commandPalette.ts
|
|
9
9
|
var _defaults = {
|
|
@@ -11,6 +11,9 @@ var _defaults = {
|
|
|
11
11
|
closeOnRun: true
|
|
12
12
|
};
|
|
13
13
|
var APPLE = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
14
|
+
var commandKey = (e) => APPLE ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey;
|
|
15
|
+
var isLetter = (e, letter) => e.key.toLowerCase() === letter || !/^[a-z]$/i.test(e.key) && /^[a-z]$/.test(letter) && e.code === `Key${letter.toUpperCase()}`;
|
|
16
|
+
var MOVE_KEYS = { ArrowDown: 1, ArrowUp: -1 };
|
|
14
17
|
var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
|
|
15
18
|
var CommandPalette = class extends Component {
|
|
16
19
|
// HTMLElement, not HTMLDialogElement: init() takes the element type from
|
|
@@ -32,15 +35,16 @@ var CommandPalette = class extends Component {
|
|
|
32
35
|
if (this.el.open) this.el.close();
|
|
33
36
|
};
|
|
34
37
|
this._handleInput = () => this._filter();
|
|
35
|
-
this._handleKeydown = (e) => {
|
|
36
|
-
|
|
38
|
+
this._handleKeydown = Utils.ignoringComposition((e) => {
|
|
39
|
+
const step = Utils.plainKey(e) ? MOVE_KEYS[e.key] : void 0;
|
|
40
|
+
if (step) {
|
|
37
41
|
e.preventDefault();
|
|
38
|
-
this._move(
|
|
39
|
-
} else if (e.key === "Enter" &&
|
|
42
|
+
this._move(step);
|
|
43
|
+
} else if (e.key === "Enter" && this._active) {
|
|
40
44
|
e.preventDefault();
|
|
41
45
|
this._run(this._active);
|
|
42
46
|
}
|
|
43
|
-
};
|
|
47
|
+
});
|
|
44
48
|
/**
|
|
45
49
|
* The first Escape clears a typed query and the next closes the palette.
|
|
46
50
|
* Chromium and Firefox clear a search input on their own; WebKit closed at
|
|
@@ -79,13 +83,10 @@ var CommandPalette = class extends Component {
|
|
|
79
83
|
};
|
|
80
84
|
this._handleShortcut = (e) => {
|
|
81
85
|
const { shortcut } = this.options;
|
|
82
|
-
if (!shortcut || e.defaultPrevented || e.altKey || e.shiftKey) return;
|
|
83
|
-
if (!(
|
|
84
|
-
const letter = shortcut.toLowerCase();
|
|
85
|
-
const latin = /^[a-z]$/i.test(e.key);
|
|
86
|
-
const hit = e.key.toLowerCase() === letter || !latin && /^[a-z]$/.test(letter) && e.code === `Key${letter.toUpperCase()}`;
|
|
87
|
-
if (!hit) return;
|
|
86
|
+
if (!shortcut || e.defaultPrevented || e.altKey || e.shiftKey || !commandKey(e)) return;
|
|
87
|
+
if (!isLetter(e, shortcut.toLowerCase())) return;
|
|
88
88
|
e.preventDefault();
|
|
89
|
+
if (e.repeat) return;
|
|
89
90
|
if (this.el.open) this.close();
|
|
90
91
|
else this.open();
|
|
91
92
|
};
|
|
@@ -253,4 +254,4 @@ CommandPalette.key = "Expressive_CommandPalette";
|
|
|
253
254
|
export {
|
|
254
255
|
CommandPalette
|
|
255
256
|
};
|
|
256
|
-
//# sourceMappingURL=chunk-
|
|
257
|
+
//# sourceMappingURL=chunk-7X44MKEQ.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/ts/components/commandPalette.ts"],
|
|
4
|
+
"sourcesContent": ["import { Utils } from '../core/utils';\nimport { Component, BaseOptions } from '../core/component';\n\nexport interface CommandPaletteOptions extends BaseOptions {\n /**\n * Letter that toggles the palette together with Command on Apple platforms\n * or Ctrl elsewhere. A key event the page has already handled is left\n * alone. `null` turns the shortcut off. `data-shortcut` on the dialog sets\n * it; an empty `data-shortcut` turns it off.\n * @default 'k'\n */\n shortcut: string | null;\n /**\n * If true, the palette closes after a command runs.\n * @default true\n */\n closeOnRun: boolean;\n}\n\nconst _defaults: CommandPaletteOptions = {\n shortcut: 'k',\n closeOnRun: true\n};\n\n// On Apple platforms Ctrl+letter is text editing (Ctrl+K deletes to the end\n// of the line), so only Command opens the palette there.\nconst APPLE = typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/.test(navigator.platform);\n\n/** Whether the platform's shortcut modifier is held, and not the other one. */\nconst commandKey = (e: KeyboardEvent) => (APPLE ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey);\n\n/**\n * Whether a key event is `letter`. A non-Latin layout reports its own letter\n * in e.key, so that falls back to the physical key. A Latin layout matches on\n * the letter it types.\n */\nconst isLetter = (e: KeyboardEvent, letter: string) =>\n e.key.toLowerCase() === letter || (!/^[a-z]$/i.test(e.key) && /^[a-z]$/.test(letter) && e.code === `Key${letter.toUpperCase()}`);\n\n/** How the arrows move the active command. */\nconst MOVE_KEYS: Record<string, number> = { ArrowDown: 1, ArrowUp: -1 };\n\n/** Case- and accent-insensitive text for matching. */\nconst fold = (text: string) =>\n text.normalize('NFD').replace(/\\p{Diacritic}/gu, '').toLowerCase();\n\n/**\n * A command palette: a native modal <dialog class=\"command-palette\"> holding\n * a search input and a list of commands. Typing filters the list; the arrow\n * keys move through what is left and Enter runs it, while focus stays in the\n * input. The dialog owns modality, Escape and returning focus.\n *\n * The input is a combobox and the list a listbox of options, with the active\n * option reported through aria-activedescendant. A command is the link or\n * button inside an option; running it clicks that element, so a page wires\n * commands with ordinary hrefs and click handlers.\n */\nexport class CommandPalette extends Component<CommandPaletteOptions> {\n declare el: HTMLDialogElement;\n /** The search input. */\n input: HTMLInputElement;\n /** The list of commands. */\n list: HTMLElement;\n private _active: HTMLElement | null = null;\n private _generatedIds: HTMLElement[] = [];\n /** Wires and refilters commands the page adds or removes later. */\n private _observer: MutationObserver | null = null;\n\n // HTMLElement, not HTMLDialogElement: init() takes the element type from\n // here, and has always accepted any HTMLElement.\n constructor(el: HTMLElement, options: Partial<CommandPaletteOptions>) {\n const shortcut = el.dataset.shortcut;\n super(el, { ...(shortcut !== undefined ? { shortcut: shortcut || null } : {}), ...options });\n this.input = el.querySelector(':scope > input');\n this.list = el.querySelector(':scope > :is(ul, ol)');\n if (!this.input || !this.list) {\n console.error('CommandPalette needs an <input> and a <ul> as children of the dialog.');\n return;\n }\n this._setupAccessibility();\n this._filter();\n // A page adding commands while the user arrows through the list keeps\n // their place.\n this._observer = new MutationObserver(() => this._filter(true));\n // Also refilter when the page hides, shows, disables or enables a command.\n // Not aria-disabled: _filter writes that itself, which would loop.\n this._observer.observe(this.list, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['hidden', 'disabled']\n });\n const { signal } = this._events;\n this.input.addEventListener('input', this._handleInput, { signal });\n this.input.addEventListener('keydown', this._handleKeydown, { signal });\n this.input.addEventListener('keydown', this._handleEscape, { signal });\n this.list.addEventListener('click', this._handleListClick, { signal });\n this.list.addEventListener('pointermove', this._handlePointerMove, { signal });\n this.el.addEventListener('close', this._handleClose, { signal });\n document.addEventListener('keydown', this._handleShortcut, { signal });\n }\n\n static get defaults(): CommandPaletteOptions {\n return _defaults;\n }\n\n static key = 'Expressive_CommandPalette';\n\n /** If the palette is open. */\n get isOpen() {\n return this.el.open;\n }\n\n /** Opens the palette as a modal dialog with the search input focused. */\n open = () => {\n if (!this.el.open) this.el.showModal();\n this.input?.focus();\n };\n\n /** Closes the palette. The dialog returns focus to where it was. */\n close = () => {\n if (this.el.open) this.el.close();\n };\n\n destroy() {\n this._observer?.disconnect();\n if (this.input && this.list) {\n for (const name of ['role', 'aria-controls', 'aria-expanded', 'aria-autocomplete', 'aria-activedescendant']) {\n this.input.removeAttribute(name);\n }\n this.list.removeAttribute('role');\n for (const item of this._items()) {\n for (const name of ['role', 'aria-selected', 'aria-disabled', 'data-filtered']) item.removeAttribute(name);\n item.classList.remove('active');\n this._action(item)?.removeAttribute('tabindex');\n }\n for (const label of this._labels()) {\n label.removeAttribute('role');\n label.removeAttribute('data-filtered');\n }\n for (const el of this._generatedIds) el.removeAttribute('id');\n const empty = this._empty();\n if (empty) empty.hidden = false;\n }\n super.destroy();\n }\n\n private _items() {\n return Array.from(this.list.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.classList.contains('label')\n );\n }\n\n private _labels() {\n return Array.from(this.list.querySelectorAll<HTMLElement>(':scope > .label'));\n }\n\n private _empty() {\n return this.el.querySelector<HTMLElement>(':scope > .command-palette-empty');\n }\n\n private _action(item: HTMLElement) {\n return item.querySelector<HTMLElement>(':scope > :is(a[href], button)');\n }\n\n private _ensureId(el: HTMLElement, prefix: string) {\n if (!el.id) {\n el.id = `${prefix}-${Utils.guid()}`;\n this._generatedIds.push(el);\n }\n return el.id;\n }\n\n private _setupAccessibility() {\n this.list.setAttribute('role', 'listbox');\n this.input.setAttribute('role', 'combobox');\n this.input.setAttribute('aria-controls', this._ensureId(this.list, 'command-palette-list'));\n this.input.setAttribute('aria-expanded', 'true');\n this.input.setAttribute('aria-autocomplete', 'list');\n }\n\n /** Every command, including ones added since the last pass, is an option. */\n private _wireItems() {\n for (const item of this._items()) {\n if (item.getAttribute('role') === 'option') continue;\n item.setAttribute('role', 'option');\n this._ensureId(item, 'command-palette-option');\n // Focus stays in the input; the commands are reached with the arrows.\n this._action(item)?.setAttribute('tabindex', '-1');\n }\n // A listbox holds only options; a group heading is a visual divider.\n for (const label of this._labels()) label.setAttribute('role', 'none');\n }\n\n /** Commands on screen: neither hidden by the page nor filtered out. */\n private _shown() {\n return this._items().filter((item) => !item.hidden && !item.hasAttribute('data-filtered'));\n }\n\n /** Shown commands the arrows and Enter can reach: disabled ones are skipped. */\n private _visible() {\n return this._shown().filter((item) => item.getAttribute('aria-disabled') !== 'true');\n }\n\n /** A disabled link or button cannot run, so its option says so. */\n private _isDisabled(item: HTMLElement) {\n const action = this._action(item);\n return !!action && (action.matches(':disabled') || action.getAttribute('aria-disabled') === 'true');\n }\n\n private _setActive(item: HTMLElement | null) {\n if (this._active === item) return;\n this._active?.classList.remove('active');\n this._active?.removeAttribute('aria-selected');\n this._active = item;\n if (item) {\n item.classList.add('active');\n item.setAttribute('aria-selected', 'true');\n this.input.setAttribute('aria-activedescendant', item.id);\n item.scrollIntoView({ block: 'nearest' });\n } else {\n this.input.removeAttribute('aria-activedescendant');\n }\n }\n\n /**\n * Shows the commands matching the query. Filtering marks rows with\n * data-filtered rather than hidden, so a command the page hides stays\n * hidden. `keepActive` holds the active command while it is still\n * reachable, for list changes the user did not make.\n */\n private _filter(keepActive = false) {\n this._wireItems();\n const terms = fold(this.input.value).split(/\\s+/).filter(Boolean);\n for (const item of this._items()) {\n // data-keywords may sit on the row or on its link or button.\n const keywords = item.dataset.keywords ?? this._action(item)?.dataset.keywords ?? '';\n const text = fold(`${item.textContent} ${keywords}`);\n item.toggleAttribute('data-filtered', !terms.every((term) => text.includes(term)));\n if (this._isDisabled(item)) item.setAttribute('aria-disabled', 'true');\n else item.removeAttribute('aria-disabled');\n }\n // A heading stays only while one of the commands under it does.\n for (const label of this._labels()) {\n let next = label.nextElementSibling as HTMLElement | null;\n let any = false;\n while (next && !next.classList.contains('label')) {\n if (!next.hidden && !next.hasAttribute('data-filtered')) any = true;\n next = next.nextElementSibling as HTMLElement | null;\n }\n label.toggleAttribute('data-filtered', !any);\n }\n const visible = this._visible();\n const empty = this._empty();\n if (empty) empty.hidden = this._shown().length > 0;\n this._setActive(keepActive && visible.includes(this._active) ? this._active : visible[0] ?? null);\n }\n\n private _move(step: number) {\n const visible = this._visible();\n if (!visible.length) return;\n const index = visible.indexOf(this._active);\n this._setActive(visible[(index + step + visible.length) % visible.length]);\n }\n\n /** Runs a command by clicking its link or button; the click handler closes. */\n private _run(item: HTMLElement) {\n (this._action(item) ?? item).click();\n }\n\n _handleInput = () => this._filter();\n\n _handleKeydown = Utils.ignoringComposition((e: KeyboardEvent) => {\n const step = Utils.plainKey(e) ? MOVE_KEYS[e.key] : undefined;\n if (step) {\n e.preventDefault();\n this._move(step);\n } else if (e.key === 'Enter' && this._active) {\n e.preventDefault();\n this._run(this._active);\n }\n });\n\n /**\n * The first Escape clears a typed query and the next closes the palette.\n * Chromium and Firefox clear a search input on their own; WebKit closed at\n * once, and so did every engine for a text input. Escape that cancels an\n * IME composition is left to the IME.\n */\n _handleEscape = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || !this.input.value || Utils.isComposing(e)) return;\n e.preventDefault();\n this.input.value = '';\n this.input.dispatchEvent(new Event('input', { bubbles: true }));\n };\n\n _handleListClick = (e: MouseEvent) => {\n const item = (<Element>e.target).closest<HTMLElement>('[role=\"option\"]');\n if (!item || !this.list.contains(item) || item.getAttribute('aria-disabled') === 'true') return;\n const action = this._action(item);\n // A click on the row's padding runs its command; that click comes back\n // through here and closes the palette.\n if (action && !action.contains(<Node>e.target)) {\n action.click();\n return;\n }\n if (this.options.closeOnRun) this.close();\n };\n\n _handlePointerMove = (e: PointerEvent) => {\n const item = (<Element>e.target).closest<HTMLElement>('[role=\"option\"]');\n if (item && this.list.contains(item) && item.getAttribute('aria-disabled') !== 'true') this._setActive(item);\n };\n\n /**\n * Each opening starts from the full list. The close event is queued, so a\n * palette reopened straight away may already be open again; its new search\n * is left alone.\n */\n _handleClose = () => {\n if (this.el.open) return;\n this.input.value = '';\n this._filter();\n };\n\n _handleShortcut = (e: KeyboardEvent) => {\n const { shortcut } = this.options;\n if (!shortcut || e.defaultPrevented || e.altKey || e.shiftKey || !commandKey(e)) return;\n if (!isLetter(e, shortcut.toLowerCase())) return;\n e.preventDefault();\n // A held shortcut stays away from the browser but toggles only once.\n if (e.repeat) return;\n if (this.el.open) this.close();\n else this.open();\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;AAmBA,IAAM,YAAmC;AAAA,EACvC,UAAU;AAAA,EACV,YAAY;AACd;AAIA,IAAM,QAAQ,OAAO,cAAc,eAAe,uBAAuB,KAAK,UAAU,QAAQ;AAGhG,IAAM,aAAa,CAAC,MAAsB,QAAQ,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE;AAO5F,IAAM,WAAW,CAAC,GAAkB,WAClC,EAAE,IAAI,YAAY,MAAM,UAAW,CAAC,WAAW,KAAK,EAAE,GAAG,KAAK,UAAU,KAAK,MAAM,KAAK,EAAE,SAAS,MAAM,OAAO,YAAY,CAAC;AAG/H,IAAM,YAAoC,EAAE,WAAW,GAAG,SAAS,GAAG;AAGtE,IAAM,OAAO,CAAC,SACZ,KAAK,UAAU,KAAK,EAAE,QAAQ,mBAAmB,EAAE,EAAE,YAAY;AAa5D,IAAM,iBAAN,cAA6B,UAAiC;AAAA;AAAA;AAAA,EAanE,YAAY,IAAiB,SAAyC;AACpE,UAAM,WAAW,GAAG,QAAQ;AAC5B,UAAM,IAAI,EAAE,GAAI,aAAa,SAAY,EAAE,UAAU,YAAY,KAAK,IAAI,CAAC,GAAI,GAAG,QAAQ,CAAC;AAT7F,SAAQ,UAA8B;AACtC,SAAQ,gBAA+B,CAAC;AAExC;AAAA,SAAQ,YAAqC;AAgD7C;AAAA,gBAAO,MAAM;AACX,UAAI,CAAC,KAAK,GAAG,KAAM,MAAK,GAAG,UAAU;AACrC,WAAK,OAAO,MAAM;AAAA,IACpB;AAGA;AAAA,iBAAQ,MAAM;AACZ,UAAI,KAAK,GAAG,KAAM,MAAK,GAAG,MAAM;AAAA,IAClC;AAoJA,wBAAe,MAAM,KAAK,QAAQ;AAElC,0BAAiB,MAAM,oBAAoB,CAAC,MAAqB;AAC/D,YAAM,OAAO,MAAM,SAAS,CAAC,IAAI,UAAU,EAAE,GAAG,IAAI;AACpD,UAAI,MAAM;AACR,UAAE,eAAe;AACjB,aAAK,MAAM,IAAI;AAAA,MACjB,WAAW,EAAE,QAAQ,WAAW,KAAK,SAAS;AAC5C,UAAE,eAAe;AACjB,aAAK,KAAK,KAAK,OAAO;AAAA,MACxB;AAAA,IACF,CAAC;AAQD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAgB,CAAC,MAAqB;AACpC,UAAI,EAAE,QAAQ,YAAY,CAAC,KAAK,MAAM,SAAS,MAAM,YAAY,CAAC,EAAG;AACrE,QAAE,eAAe;AACjB,WAAK,MAAM,QAAQ;AACnB,WAAK,MAAM,cAAc,IAAI,MAAM,SAAS,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,IAChE;AAEA,4BAAmB,CAAC,MAAkB;AACpC,YAAM,OAAiB,EAAE,OAAQ,QAAqB,iBAAiB;AACvE,UAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,aAAa,eAAe,MAAM,OAAQ;AACzF,YAAM,SAAS,KAAK,QAAQ,IAAI;AAGhC,UAAI,UAAU,CAAC,OAAO,SAAe,EAAE,MAAM,GAAG;AAC9C,eAAO,MAAM;AACb;AAAA,MACF;AACA,UAAI,KAAK,QAAQ,WAAY,MAAK,MAAM;AAAA,IAC1C;AAEA,8BAAqB,CAAC,MAAoB;AACxC,YAAM,OAAiB,EAAE,OAAQ,QAAqB,iBAAiB;AACvE,UAAI,QAAQ,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,aAAa,eAAe,MAAM,OAAQ,MAAK,WAAW,IAAI;AAAA,IAC7G;AAOA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAe,MAAM;AACnB,UAAI,KAAK,GAAG,KAAM;AAClB,WAAK,MAAM,QAAQ;AACnB,WAAK,QAAQ;AAAA,IACf;AAEA,2BAAkB,CAAC,MAAqB;AACtC,YAAM,EAAE,SAAS,IAAI,KAAK;AAC1B,UAAI,CAAC,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,YAAY,CAAC,WAAW,CAAC,EAAG;AACjF,UAAI,CAAC,SAAS,GAAG,SAAS,YAAY,CAAC,EAAG;AAC1C,QAAE,eAAe;AAEjB,UAAI,EAAE,OAAQ;AACd,UAAI,KAAK,GAAG,KAAM,MAAK,MAAM;AAAA,UACxB,MAAK,KAAK;AAAA,IACjB;AArQE,SAAK,QAAQ,GAAG,cAAc,gBAAgB;AAC9C,SAAK,OAAO,GAAG,cAAc,sBAAsB;AACnD,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,MAAM;AAC7B,cAAQ,MAAM,uEAAuE;AACrF;AAAA,IACF;AACA,SAAK,oBAAoB;AACzB,SAAK,QAAQ;AAGb,SAAK,YAAY,IAAI,iBAAiB,MAAM,KAAK,QAAQ,IAAI,CAAC;AAG9D,SAAK,UAAU,QAAQ,KAAK,MAAM;AAAA,MAChC,WAAW;AAAA,MACX,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,iBAAiB,CAAC,UAAU,UAAU;AAAA,IACxC,CAAC;AACD,UAAM,EAAE,OAAO,IAAI,KAAK;AACxB,SAAK,MAAM,iBAAiB,SAAS,KAAK,cAAc,EAAE,OAAO,CAAC;AAClE,SAAK,MAAM,iBAAiB,WAAW,KAAK,gBAAgB,EAAE,OAAO,CAAC;AACtE,SAAK,MAAM,iBAAiB,WAAW,KAAK,eAAe,EAAE,OAAO,CAAC;AACrE,SAAK,KAAK,iBAAiB,SAAS,KAAK,kBAAkB,EAAE,OAAO,CAAC;AACrE,SAAK,KAAK,iBAAiB,eAAe,KAAK,oBAAoB,EAAE,OAAO,CAAC;AAC7E,SAAK,GAAG,iBAAiB,SAAS,KAAK,cAAc,EAAE,OAAO,CAAC;AAC/D,aAAS,iBAAiB,WAAW,KAAK,iBAAiB,EAAE,OAAO,CAAC;AAAA,EACvE;AAAA,EAEA,WAAW,WAAkC;AAC3C,WAAO;AAAA,EACT;AAAA;AAAA,EAKA,IAAI,SAAS;AACX,WAAO,KAAK,GAAG;AAAA,EACjB;AAAA,EAaA,UAAU;AACR,SAAK,WAAW,WAAW;AAC3B,QAAI,KAAK,SAAS,KAAK,MAAM;AAC3B,iBAAW,QAAQ,CAAC,QAAQ,iBAAiB,iBAAiB,qBAAqB,uBAAuB,GAAG;AAC3G,aAAK,MAAM,gBAAgB,IAAI;AAAA,MACjC;AACA,WAAK,KAAK,gBAAgB,MAAM;AAChC,iBAAW,QAAQ,KAAK,OAAO,GAAG;AAChC,mBAAW,QAAQ,CAAC,QAAQ,iBAAiB,iBAAiB,eAAe,EAAG,MAAK,gBAAgB,IAAI;AACzG,aAAK,UAAU,OAAO,QAAQ;AAC9B,aAAK,QAAQ,IAAI,GAAG,gBAAgB,UAAU;AAAA,MAChD;AACA,iBAAW,SAAS,KAAK,QAAQ,GAAG;AAClC,cAAM,gBAAgB,MAAM;AAC5B,cAAM,gBAAgB,eAAe;AAAA,MACvC;AACA,iBAAW,MAAM,KAAK,cAAe,IAAG,gBAAgB,IAAI;AAC5D,YAAM,QAAQ,KAAK,OAAO;AAC1B,UAAI,MAAO,OAAM,SAAS;AAAA,IAC5B;AACA,UAAM,QAAQ;AAAA,EAChB;AAAA,EAEQ,SAAS;AACf,WAAO,MAAM,KAAK,KAAK,KAAK,QAAQ,EAAE;AAAA,MACpC,CAAC,UAAgC,iBAAiB,eAAe,CAAC,MAAM,UAAU,SAAS,OAAO;AAAA,IACpG;AAAA,EACF;AAAA,EAEQ,UAAU;AAChB,WAAO,MAAM,KAAK,KAAK,KAAK,iBAA8B,iBAAiB,CAAC;AAAA,EAC9E;AAAA,EAEQ,SAAS;AACf,WAAO,KAAK,GAAG,cAA2B,iCAAiC;AAAA,EAC7E;AAAA,EAEQ,QAAQ,MAAmB;AACjC,WAAO,KAAK,cAA2B,+BAA+B;AAAA,EACxE;AAAA,EAEQ,UAAU,IAAiB,QAAgB;AACjD,QAAI,CAAC,GAAG,IAAI;AACV,SAAG,KAAK,GAAG,MAAM,IAAI,MAAM,KAAK,CAAC;AACjC,WAAK,cAAc,KAAK,EAAE;AAAA,IAC5B;AACA,WAAO,GAAG;AAAA,EACZ;AAAA,EAEQ,sBAAsB;AAC5B,SAAK,KAAK,aAAa,QAAQ,SAAS;AACxC,SAAK,MAAM,aAAa,QAAQ,UAAU;AAC1C,SAAK,MAAM,aAAa,iBAAiB,KAAK,UAAU,KAAK,MAAM,sBAAsB,CAAC;AAC1F,SAAK,MAAM,aAAa,iBAAiB,MAAM;AAC/C,SAAK,MAAM,aAAa,qBAAqB,MAAM;AAAA,EACrD;AAAA;AAAA,EAGQ,aAAa;AACnB,eAAW,QAAQ,KAAK,OAAO,GAAG;AAChC,UAAI,KAAK,aAAa,MAAM,MAAM,SAAU;AAC5C,WAAK,aAAa,QAAQ,QAAQ;AAClC,WAAK,UAAU,MAAM,wBAAwB;AAE7C,WAAK,QAAQ,IAAI,GAAG,aAAa,YAAY,IAAI;AAAA,IACnD;AAEA,eAAW,SAAS,KAAK,QAAQ,EAAG,OAAM,aAAa,QAAQ,MAAM;AAAA,EACvE;AAAA;AAAA,EAGQ,SAAS;AACf,WAAO,KAAK,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,UAAU,CAAC,KAAK,aAAa,eAAe,CAAC;AAAA,EAC3F;AAAA;AAAA,EAGQ,WAAW;AACjB,WAAO,KAAK,OAAO,EAAE,OAAO,CAAC,SAAS,KAAK,aAAa,eAAe,MAAM,MAAM;AAAA,EACrF;AAAA;AAAA,EAGQ,YAAY,MAAmB;AACrC,UAAM,SAAS,KAAK,QAAQ,IAAI;AAChC,WAAO,CAAC,CAAC,WAAW,OAAO,QAAQ,WAAW,KAAK,OAAO,aAAa,eAAe,MAAM;AAAA,EAC9F;AAAA,EAEQ,WAAW,MAA0B;AAC3C,QAAI,KAAK,YAAY,KAAM;AAC3B,SAAK,SAAS,UAAU,OAAO,QAAQ;AACvC,SAAK,SAAS,gBAAgB,eAAe;AAC7C,SAAK,UAAU;AACf,QAAI,MAAM;AACR,WAAK,UAAU,IAAI,QAAQ;AAC3B,WAAK,aAAa,iBAAiB,MAAM;AACzC,WAAK,MAAM,aAAa,yBAAyB,KAAK,EAAE;AACxD,WAAK,eAAe,EAAE,OAAO,UAAU,CAAC;AAAA,IAC1C,OAAO;AACL,WAAK,MAAM,gBAAgB,uBAAuB;AAAA,IACpD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,QAAQ,aAAa,OAAO;AAClC,SAAK,WAAW;AAChB,UAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AAChE,eAAW,QAAQ,KAAK,OAAO,GAAG;AAEhC,YAAM,WAAW,KAAK,QAAQ,YAAY,KAAK,QAAQ,IAAI,GAAG,QAAQ,YAAY;AAClF,YAAM,OAAO,KAAK,GAAG,KAAK,WAAW,IAAI,QAAQ,EAAE;AACnD,WAAK,gBAAgB,iBAAiB,CAAC,MAAM,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC;AACjF,UAAI,KAAK,YAAY,IAAI,EAAG,MAAK,aAAa,iBAAiB,MAAM;AAAA,UAChE,MAAK,gBAAgB,eAAe;AAAA,IAC3C;AAEA,eAAW,SAAS,KAAK,QAAQ,GAAG;AAClC,UAAI,OAAO,MAAM;AACjB,UAAI,MAAM;AACV,aAAO,QAAQ,CAAC,KAAK,UAAU,SAAS,OAAO,GAAG;AAChD,YAAI,CAAC,KAAK,UAAU,CAAC,KAAK,aAAa,eAAe,EAAG,OAAM;AAC/D,eAAO,KAAK;AAAA,MACd;AACA,YAAM,gBAAgB,iBAAiB,CAAC,GAAG;AAAA,IAC7C;AACA,UAAM,UAAU,KAAK,SAAS;AAC9B,UAAM,QAAQ,KAAK,OAAO;AAC1B,QAAI,MAAO,OAAM,SAAS,KAAK,OAAO,EAAE,SAAS;AACjD,SAAK,WAAW,cAAc,QAAQ,SAAS,KAAK,OAAO,IAAI,KAAK,UAAU,QAAQ,CAAC,KAAK,IAAI;AAAA,EAClG;AAAA,EAEQ,MAAM,MAAc;AAC1B,UAAM,UAAU,KAAK,SAAS;AAC9B,QAAI,CAAC,QAAQ,OAAQ;AACrB,UAAM,QAAQ,QAAQ,QAAQ,KAAK,OAAO;AAC1C,SAAK,WAAW,SAAS,QAAQ,OAAO,QAAQ,UAAU,QAAQ,MAAM,CAAC;AAAA,EAC3E;AAAA;AAAA,EAGQ,KAAK,MAAmB;AAC9B,KAAC,KAAK,QAAQ,IAAI,KAAK,MAAM,MAAM;AAAA,EACrC;AAmEF;AAtRa,eAiDJ,MAAM;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Component
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EB2V4OKL.mjs";
|
|
4
4
|
import {
|
|
5
5
|
Utils
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-67UNY7RH.mjs";
|
|
7
7
|
|
|
8
8
|
// src/ts/components/questionnaire.ts
|
|
9
9
|
var _defaults = {
|
|
@@ -22,10 +22,15 @@ function labelText(control) {
|
|
|
22
22
|
label?.querySelectorAll("input, small").forEach((el) => el.remove());
|
|
23
23
|
return label?.textContent.trim() || control.value;
|
|
24
24
|
}
|
|
25
|
+
var MASKED = {
|
|
26
|
+
hidden: () => "",
|
|
27
|
+
password: (control) => control.value ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "",
|
|
28
|
+
file: (control) => Array.from(control.files ?? [], (file) => file.name).join(", ")
|
|
29
|
+
};
|
|
25
30
|
function answerText(control) {
|
|
26
31
|
if (control instanceof HTMLSelectElement) return Array.from(control.selectedOptions, (option) => option.text).join(", ");
|
|
27
32
|
if (isChoice(control)) return control.checked ? labelText(control) : "";
|
|
28
|
-
return control.value.trim();
|
|
33
|
+
return MASKED[control.type]?.(control) ?? control.value.trim();
|
|
29
34
|
}
|
|
30
35
|
var savedState = (control) => {
|
|
31
36
|
if (control instanceof HTMLSelectElement) return Array.from(control.selectedOptions, (option) => option.value);
|
|
@@ -159,7 +164,7 @@ var Questionnaire = class extends Component {
|
|
|
159
164
|
};
|
|
160
165
|
// An answer clears the question's error.
|
|
161
166
|
this._handleInput = (e) => {
|
|
162
|
-
this._syncRequired();
|
|
167
|
+
this._syncRequired(this._fieldsets().filter((item2) => item2.contains(e.target)));
|
|
163
168
|
this._save();
|
|
164
169
|
const item = e.target.closest?.("fieldset");
|
|
165
170
|
if (item instanceof HTMLFieldSetElement && item.classList.contains("invalid")) this._setInvalid(item, false);
|
|
@@ -362,8 +367,8 @@ var Questionnaire = class extends Component {
|
|
|
362
367
|
* every way of submitting, sees it. A disabled checkbox is not sent, so it
|
|
363
368
|
* does not count.
|
|
364
369
|
*/
|
|
365
|
-
_syncRequired() {
|
|
366
|
-
for (const item of
|
|
370
|
+
_syncRequired(items = this._fieldsets()) {
|
|
371
|
+
for (const item of items) {
|
|
367
372
|
if (!item.hasAttribute("data-required")) continue;
|
|
368
373
|
const boxes = this._controls(item).filter(
|
|
369
374
|
(control) => control instanceof HTMLInputElement && control.type === "checkbox"
|
|
@@ -432,4 +437,4 @@ Questionnaire.key = "Expressive_Questionnaire";
|
|
|
432
437
|
export {
|
|
433
438
|
Questionnaire
|
|
434
439
|
};
|
|
435
|
-
//# sourceMappingURL=chunk-
|
|
440
|
+
//# sourceMappingURL=chunk-7ZAMOMGY.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/ts/components/questionnaire.ts"],
|
|
4
|
+
"sourcesContent": ["import { Utils } from '../core/utils';\nimport { Component, BaseOptions } from '../core/component';\n\nexport interface QuestionnaireOptions extends BaseOptions {\n /**\n * Text above the progress bar. `{current}` and `{total}` are replaced with\n * the question number and the number of questions. `data-progress-label`\n * on the form sets it.\n * @default 'Question {current} of {total}'\n */\n progressLabel: string;\n /**\n * Error shown when a question has no `.questionnaire-error` of its own and\n * the browser gives no validation message, as for a `data-required` group\n * of checkboxes.\n * @default 'Choose an answer to continue.'\n */\n requiredMessage: string;\n /**\n * With `data-summary`, the heading of the review step and the text above\n * the progress bar there.\n * @default 'Review your answers'\n */\n summaryLabel: string;\n /** With `data-summary`, the text of each answer's edit button. @default 'Edit' */\n editLabel: string;\n /** With `data-summary`, the answer shown for a question left blank. @default 'Not answered' */\n skippedLabel: string;\n}\n\nconst _defaults: QuestionnaireOptions = {\n progressLabel: 'Question {current} of {total}',\n requiredMessage: 'Choose an answer to continue.',\n summaryLabel: 'Review your answers',\n editLabel: 'Edit',\n skippedLabel: 'Not answered'\n};\n\nconst NAVIGATION = ['previous', 'skip', 'next'];\nconst UNSAVED = ['file', 'password', 'hidden'];\nconst LETTERS = 'abcdefghijklmnopqrstuvwxyz';\n\ntype Control = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n/** A control's answer as saved: whether a choice is checked, a select's chosen values, or a field's value. */\ntype Saved = boolean | string | string[];\n\nconst isChoice = (control: Control): control is HTMLInputElement =>\n control instanceof HTMLInputElement && (control.type === 'radio' || control.type === 'checkbox');\n\n/** A choice's label text, without its input or the description in <small>. */\nfunction labelText(control: HTMLInputElement) {\n const label = control.closest('label')?.cloneNode(true) as HTMLElement | undefined;\n label?.querySelectorAll('input, small').forEach((el) => el.remove());\n return label?.textContent.trim() || control.value;\n}\n\n/**\n * Summary text for the inputs whose value must not be shown: a hidden input is\n * the server's, a password is masked at a fixed length, and a file shows its\n * names rather than the browser's fake path.\n */\nconst MASKED: Record<string, (control: HTMLInputElement | HTMLTextAreaElement) => string> = {\n hidden: () => '',\n password: (control) => (control.value ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : ''),\n file: (control) => Array.from((control as HTMLInputElement).files ?? [], (file) => file.name).join(', ')\n};\n\n/** A control's answer as text for the summary. */\nfunction answerText(control: Control) {\n if (control instanceof HTMLSelectElement) return Array.from(control.selectedOptions, (option) => option.text).join(', ');\n if (isChoice(control)) return control.checked ? labelText(control) : '';\n return MASKED[control.type]?.(control) ?? control.value.trim();\n}\n\nconst savedState = (control: Control): Saved => {\n if (control instanceof HTMLSelectElement) return Array.from(control.selectedOptions, (option) => option.value);\n return isChoice(control) ? control.checked : control.value;\n};\n\n/** Saved state with a list of answers, or null. */\nconst validSaved = (saved: { answers?: unknown } | null) =>\n (Array.isArray(saved?.answers) ? saved : null) as { index: number; answers: Saved[] } | null;\n\n/** Puts a saved answer back, ignoring one of the wrong type, as a hand-edited store may hold. */\nfunction restoreState(control: Control, saved: Saved) {\n if (control instanceof HTMLSelectElement) return restoreSelect(control, saved);\n if (isChoice(control)) control.checked = saved === true;\n else if (typeof saved === 'string') control.value = saved;\n}\n\nfunction restoreSelect(select: HTMLSelectElement, saved: Saved) {\n if (!Array.isArray(saved)) return;\n for (const option of select.options) option.selected = saved.includes(option.value);\n}\n\n/**\n * Restores one control and fires `change` if its answer moved, so a page\n * that switches questions on from an answer sees the restored one.\n */\nfunction restoreControl(control: Control, saved: Saved) {\n const before = JSON.stringify(savedState(control));\n restoreState(control, saved);\n if (JSON.stringify(savedState(control)) !== before) control.dispatchEvent(new Event('change', { bubbles: true }));\n}\n\n/**\n * The answer a shortcut key picks, counted from 0: a digit 1 to 9 or a\n * letter. Keys typed into a text field, or with a modifier, pick nothing.\n */\nfunction shortcut(e: KeyboardEvent) {\n if (typingIn(e.target) || !Utils.plainKey(e)) return -1;\n const digit = '123456789'.indexOf(e.key);\n return digit >= 0 ? digit : letterIndex(e.key);\n}\n\nconst TYPING = 'textarea, select, [contenteditable], input:not([type=\"radio\"], [type=\"checkbox\"], [type=\"button\"], [type=\"submit\"], [type=\"reset\"])';\nconst typingIn = (target: EventTarget | null) => !!(target as Element).matches?.(TYPING);\n\nconst letterIndex = (key: string) => (key.length === 1 ? LETTERS.indexOf(key.toLowerCase()) : -1);\n\n/**\n * A multi-step questionnaire: a <form class=\"questionnaire\"> whose\n * <fieldset> children are the questions, shown one at a time, and a <footer>\n * of buttons. `value=\"previous\"`, `value=\"skip\"` and `value=\"next\"` mark the\n * navigation buttons; a submit button sends the form on the last question.\n *\n * A question is required when one of its controls is `required`, or when the\n * fieldset has `data-required` (at least one enabled checkbox, held as a\n * custom validity on the first one). Next and every way of submitting check\n * it with the browser's own validation; an optional question gets Skip, which\n * clears its answer. A disabled fieldset is left out, so a page can switch\n * questions on and off from earlier answers.\n *\n * Three attributes on the form add to that: `data-shortcuts` lets a letter or\n * digit pick an answer, `data-summary` adds a review step listing every\n * answer before the submit button, and `data-save=\"key\"` keeps the answers\n * and the shown question in localStorage under that key until the form is\n * submitted.\n *\n * Without JavaScript every question shows at once and the submit button sends\n * the form. The answers are ordinary form controls, so FormData reads them.\n */\nexport class Questionnaire extends Component<QuestionnaireOptions> {\n declare el: HTMLFormElement;\n private _active: HTMLFieldSetElement | null = null;\n private _progress: HTMLElement;\n private _progressText: HTMLElement;\n private _progressBar: HTMLProgressElement;\n private _generated: HTMLElement[] = [];\n private _generatedIds: HTMLElement[] = [];\n private _observer: MutationObserver;\n /** Set while one submission's invalid events arrive, so only the first opens. */\n private _reporting = false;\n /** With data-summary, the review step, and whether it is shown. */\n private _summary: HTMLElement | null = null;\n private _reviewing = false;\n\n // HTMLElement, not HTMLFormElement: init() takes the element type from\n // here, and has always accepted any HTMLElement.\n constructor(el: HTMLElement, options: Partial<QuestionnaireOptions>) {\n const progressLabel = el.dataset.progressLabel;\n super(el, { ...(progressLabel ? { progressLabel } : {}), ...options });\n\n this._progress = document.createElement('div');\n this._progress.className = 'questionnaire-progress';\n this._progressText = document.createElement('span');\n this._progressText.id = `questionnaire-progress-${Utils.guid()}`;\n this._progressBar = document.createElement('progress');\n this._progressBar.className = 'progress';\n this._progressBar.setAttribute('aria-labelledby', this._progressText.id);\n this._progress.append(this._progressText, this._progressBar);\n this.el.prepend(this._progress);\n\n for (const item of this._fieldsets()) {\n const ids = Array.from(item.querySelectorAll<HTMLElement>(':scope > p:not(.questionnaire-error)'))\n .map((p) => this._ensureId(p, 'questionnaire-description'));\n if (ids.length && !item.hasAttribute('aria-describedby')) {\n item.setAttribute('aria-describedby', ids.join(' '));\n this._generatedIds.push(item);\n }\n }\n\n this._active = this.items[0] ?? null;\n this._summary = this._createSummary();\n this._restore();\n this._syncRequired();\n this._render();\n // A page enabling or disabling a question from an earlier answer changes\n // the count and the buttons.\n this._observer = new MutationObserver(() => {\n this._syncRequired();\n this._render();\n });\n this._observer.observe(this.el, { attributes: true, subtree: true, attributeFilter: ['disabled'] });\n const { signal } = this._events;\n this.el.addEventListener('click', this._handleClick, { signal });\n this.el.addEventListener('keydown', this._handleKeydown, { signal });\n this.el.addEventListener('input', this._handleInput, { signal });\n this.el.addEventListener('change', this._save, { signal });\n this.el.addEventListener('submit', this._forget, { signal });\n // invalid does not bubble.\n this.el.addEventListener('invalid', this._handleInvalid, { capture: true, signal });\n }\n\n static get defaults(): QuestionnaireOptions {\n return _defaults;\n }\n\n static key = 'Expressive_Questionnaire';\n\n /** The questions that apply: every fieldset child that is not disabled. */\n get items(): HTMLFieldSetElement[] {\n return this._fieldsets().filter((item) => !item.disabled);\n }\n\n /** Position of the shown question in `items`. */\n get index(): number {\n return this.items.indexOf(this._active);\n }\n\n /** Shows the question at `index` without checking the current one. */\n goTo = (index: number) => {\n const item = this.items[index];\n if (!item) return;\n this._active = item;\n this._reviewing = false;\n this._render();\n this._save();\n const controls = this._controls(item).filter((control) => !control.disabled);\n (controls.find((control) => (control as HTMLInputElement).checked) ?? controls[0])?.focus();\n };\n\n /** Checks the shown question and moves to the next one if it is answered. */\n next = () => {\n if (this._validate(this._active)) this._advance();\n };\n\n /** Moves to the previous question, or from the review step back to the last one. */\n previous = () => this.goTo(this._reviewing ? this.items.length - 1 : this.index - 1);\n\n /** Shows the review step of a questionnaire with data-summary. */\n review = () => {\n if (!this._summary) return;\n this._fillSummary(this._summary);\n this._reviewing = true;\n this._render();\n this._summary.focus();\n };\n\n /** The next question, or after the last one the review step. */\n private _advance() {\n if (this._summary && this.index === this.items.length - 1) this.review();\n else this.goTo(this.index + 1);\n }\n\n /** Clears the answer to the shown question and moves to the next one. */\n skip = () => {\n if (!this._active) return;\n for (const control of this._controls(this._active)) {\n const choice = isChoice(control);\n if (choice ? !control.checked : !control.value) continue;\n if (choice) control.checked = false;\n else control.value = '';\n control.dispatchEvent(new Event('change', { bubbles: true }));\n }\n // A skipped question has no answer left to be wrong about. Clearing fires\n // only `change`, not the `input` that clears an error, so clear it here.\n this._setInvalid(this._active, false);\n this._advance();\n };\n\n destroy() {\n this._observer.disconnect();\n this._summary?.remove();\n for (const item of this._fieldsets()) {\n if (item.hasAttribute('data-required')) for (const control of this._controls(item)) control.setCustomValidity('');\n item.hidden = false;\n this._setInvalid(item, false);\n }\n for (const button of this._buttons()) button.hidden = false;\n this._progress.remove();\n for (const el of this._generated) el.remove();\n for (const el of this._generatedIds) {\n el.removeAttribute(el instanceof HTMLFieldSetElement ? 'aria-describedby' : 'id');\n }\n super.destroy();\n }\n\n private _fieldsets() {\n return Array.from(this.el.querySelectorAll<HTMLFieldSetElement>(':scope > fieldset'));\n }\n\n /** The footer's navigation and submit buttons. */\n private _buttons() {\n return Array.from(this.el.querySelectorAll<HTMLButtonElement>(':scope > footer > button')).filter(\n (button) => NAVIGATION.includes(button.value) || button.type === 'submit'\n );\n }\n\n private _controls(item: HTMLFieldSetElement) {\n return Array.from(item.elements).filter(\n (el): el is Control =>\n el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement ||\n (el instanceof HTMLInputElement && el.type !== 'button' && el.type !== 'submit')\n );\n }\n\n private _required(item: HTMLFieldSetElement) {\n return item.hasAttribute('data-required') || this._controls(item).some((control) => control.required);\n }\n\n private _ensureId(el: HTMLElement, prefix: string) {\n if (!el.id) {\n el.id = `${prefix}-${Utils.guid()}`;\n this._generatedIds.push(el);\n }\n return el.id;\n }\n\n private _render() {\n const items = this.items;\n // The shown question was switched off: carry on from the next one.\n if (!items.includes(this._active)) this._active = this._after(items);\n const index = items.indexOf(this._active);\n for (const item of this._fieldsets()) item.hidden = this._reviewing || item !== this._active;\n this._summary?.toggleAttribute('hidden', !this._reviewing);\n for (const button of this._buttons()) button.hidden = !this._shows(button, index);\n this._renderProgress(index, items.length);\n }\n\n /** The question after the shown one, which was switched off, or the last that applies. */\n private _after(items: HTMLFieldSetElement[]) {\n const all = this._fieldsets();\n const after = all.slice(all.indexOf(this._active) + 1).find((item) => !item.disabled);\n return after ?? items[items.length - 1] ?? null;\n }\n\n /** Whether the shown step is the one that submits: the review step, or without one the last question. */\n private get _end() {\n return this._summary ? this._reviewing : this.index === this.items.length - 1;\n }\n\n /** Whether a footer button applies to the shown step. */\n private _shows(button: HTMLButtonElement, index: number) {\n const end = this._end;\n const shown: Record<string, boolean> = {\n previous: index > 0 || this._reviewing,\n skip: !end && this._skippable(),\n next: !end\n };\n return shown[button.value] ?? end;\n }\n\n /** Whether the shown question may be skipped: it applies and needs no answer. */\n private _skippable() {\n return !!this._active && !this._required(this._active);\n }\n\n private _renderProgress(index: number, total: number) {\n this._progressText.textContent = this._reviewing\n ? this.options.summaryLabel\n : this.options.progressLabel.replace('{current}', String(index + 1)).replace('{total}', String(total));\n this._progressBar.max = Math.max(total, 1);\n this._progressBar.value = this._reviewing ? total : index + 1;\n }\n\n /** With data-summary, the review step: a list of the questions and their answers, before the footer. */\n private _createSummary() {\n if (!this.el.hasAttribute('data-summary')) return null;\n const summary = document.createElement('section');\n summary.className = 'questionnaire-summary';\n summary.tabIndex = -1;\n summary.setAttribute('aria-label', this.options.summaryLabel);\n summary.hidden = true;\n summary.addEventListener('click', (e) => {\n const edit = (e.target as Element).closest<HTMLButtonElement>('button[data-question]');\n if (edit) this.goTo(Number(edit.dataset.question));\n });\n const footer = this.el.querySelector(':scope > footer');\n if (footer) footer.before(summary);\n else this.el.append(summary);\n return summary;\n }\n\n /** Lists each question that applies with its answer and a button back to it. */\n private _fillSummary(summary: HTMLElement) {\n const list = document.createElement('dl');\n list.append(...this.items.map((item, i) => {\n const row = document.createElement('div');\n const [term, answer, action] = [document.createElement('dt'), document.createElement('dd'), document.createElement('dd')];\n term.id = `questionnaire-summary-${Utils.guid()}`;\n term.textContent = item.querySelector(':scope > legend')?.textContent.trim() ?? '';\n answer.textContent = this._controls(item).map(answerText).filter(Boolean).join(', ') || this.options.skippedLabel;\n const edit = document.createElement('button');\n Object.assign(edit, { type: 'button', className: 'text', textContent: this.options.editLabel });\n edit.dataset.question = String(i);\n edit.setAttribute('aria-describedby', term.id);\n action.append(edit);\n row.append(term, answer, action);\n return row;\n }));\n summary.replaceChildren(list);\n }\n\n /** The storage key from data-save, or '' without it. */\n private get _key() {\n const key = this.el.dataset.save;\n return key ? `questionnaire:${key}` : '';\n }\n\n /**\n * Every answer control that can be saved, in order. A file cannot be put back,\n * a password must not sit in storage, and a hidden input is the server's.\n */\n private _allControls() {\n return this._fieldsets().flatMap((item) => this._controls(item)).filter((control) => !UNSAVED.includes(control.type));\n }\n\n /** With data-save, stores the answers and the shown question. */\n private _save = () => {\n const key = this._key;\n if (!key) return;\n const state = { index: this.index, answers: this._allControls().map(savedState) };\n try {\n localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // Storage full or blocked: the questionnaire still works unsaved.\n }\n };\n\n /** Puts back saved answers and the question they reached, if the questions are the same. */\n private _restore() {\n const saved = this._read();\n const controls = this._allControls();\n if (saved?.answers.length !== controls.length) return;\n controls.forEach((control, i) => restoreControl(control, saved!.answers[i]));\n this._active = this.items[saved!.index] ?? this._active;\n }\n\n /** The saved state, or null for none or a broken one. */\n private _read(): { index: number; answers: Saved[] } | null {\n try {\n return validSaved(JSON.parse(localStorage.getItem(this._key) ?? 'null'));\n } catch {\n return null;\n }\n }\n\n /** A submitted questionnaire has nothing left to resume. */\n private _forget = () => {\n try {\n if (this._key) localStorage.removeItem(this._key);\n } catch {\n // Blocked storage has nothing saved.\n }\n };\n\n /**\n * Puts \"choose at least one\" on the first enabled checkbox of each\n * data-required group with none checked, so the browser's validation, and\n * every way of submitting, sees it. A disabled checkbox is not sent, so it\n * does not count.\n */\n private _syncRequired(items = this._fieldsets()) {\n for (const item of items) {\n if (!item.hasAttribute('data-required')) continue;\n const boxes = this._controls(item).filter(\n (control): control is HTMLInputElement => control instanceof HTMLInputElement && control.type === 'checkbox'\n );\n const enabled = boxes.filter((box) => !box.disabled);\n for (const box of boxes) box.setCustomValidity('');\n if (enabled.length && !enabled.some((box) => box.checked)) {\n const error = item.querySelector<HTMLElement>(':scope > .questionnaire-error:not([data-generated])');\n enabled[0].setCustomValidity(error?.textContent.trim() || this.options.requiredMessage);\n }\n }\n }\n\n /** The first control in the question that still needs an answer. */\n private _invalidControl(item: HTMLFieldSetElement): Control | undefined {\n return this._controls(item).find((control) => control.willValidate && !control.validity.valid);\n }\n\n /** Shows the question's error and returns false when it needs an answer. */\n private _validate(item: HTMLFieldSetElement | null) {\n this._syncRequired();\n const invalid = item && this._invalidControl(item);\n if (!invalid) return true;\n let error = item.querySelector<HTMLElement>(':scope > .questionnaire-error');\n if (!error) {\n error = document.createElement('p');\n error.className = 'questionnaire-error';\n error.dataset.generated = '';\n error.textContent = invalid.validationMessage || this.options.requiredMessage;\n item.append(error);\n this._generated.push(error);\n }\n this._setInvalid(item, true);\n invalid.focus();\n return false;\n }\n\n private _setInvalid(item: HTMLFieldSetElement, invalid: boolean) {\n item.classList.toggle('invalid', invalid);\n const error = item.querySelector<HTMLElement>(':scope > .questionnaire-error');\n const described = (item.getAttribute('aria-describedby') ?? '').split(' ').filter((id) => id && id !== error?.id);\n if (invalid && error) described.push(this._ensureId(error, 'questionnaire-error'));\n if (described.length) item.setAttribute('aria-describedby', described.join(' '));\n else item.removeAttribute('aria-describedby');\n for (const control of this._controls(item)) {\n if (invalid) control.setAttribute('aria-invalid', 'true');\n else control.removeAttribute('aria-invalid');\n }\n if (!invalid && this._generated.includes(error)) {\n error.remove();\n this._generated.splice(this._generated.indexOf(error), 1);\n }\n }\n\n private _handleClick = (e: MouseEvent) => {\n const button = (e.target as Element).closest?.('button');\n if (!button || !this._buttons().includes(button)) return;\n if (NAVIGATION.includes(button.value)) {\n // Also stops a navigation button written without type=\"button\" from\n // submitting the form.\n e.preventDefault();\n this[button.value as 'previous' | 'skip' | 'next']();\n return;\n }\n // Submit by click or Enter: catch answers a script set without an event.\n this._syncRequired();\n };\n\n // Enter in a field moves on instead of submitting the form early. With\n // data-shortcuts, a letter or digit picks an answer.\n private _handleKeydown = (e: KeyboardEvent) => {\n if (e.key !== 'Enter') return this._handleShortcut(e);\n if (e.defaultPrevented || Utils.isComposing(e)) return;\n if (!(e.target instanceof HTMLInputElement) || this._end) return;\n e.preventDefault();\n this.next();\n };\n\n /** Whether a shortcut key can pick an answer now: data-shortcuts is set and a question shows. */\n private get _shortcuts() {\n return this.el.hasAttribute('data-shortcuts') && !!this._active && !this._reviewing;\n }\n\n private _handleShortcut(e: KeyboardEvent) {\n if (!this._shortcuts) return;\n // Counted as the letters are, disabled choices included.\n const choice = this._controls(this._active!).filter(isChoice)[shortcut(e)] as HTMLInputElement | undefined;\n if (!choice || choice.disabled) return;\n e.preventDefault();\n choice.click();\n choice.focus();\n }\n\n // A submission found an unanswered question, possibly a hidden one. Open the\n // first instead of letting the browser report a control it cannot show.\n // One submission fires its invalid events in a single task.\n private _handleInvalid = (e: Event) => {\n const item = this.items.find((item) => item.contains(e.target as Node));\n if (!item) return;\n e.preventDefault();\n if (this._reporting) return;\n this._reporting = true;\n setTimeout(() => (this._reporting = false));\n this._active = item;\n this._reviewing = false;\n this._render();\n this._validate(item);\n };\n\n // An answer clears the question's error.\n private _handleInput = (e: Event) => {\n this._syncRequired(this._fieldsets().filter((item) => item.contains(e.target as Node)));\n this._save();\n const item = (e.target as Element).closest?.('fieldset');\n if (item instanceof HTMLFieldSetElement && item.classList.contains('invalid')) this._setInvalid(item, false);\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["item"]
|
|
7
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Utils
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-67UNY7RH.mjs";
|
|
4
4
|
|
|
5
5
|
// src/ts/behaviors/theme.ts
|
|
6
6
|
var themes = ["light", "dark", "auto"];
|
|
@@ -51,4 +51,4 @@ var Theme = _Theme;
|
|
|
51
51
|
export {
|
|
52
52
|
Theme
|
|
53
53
|
};
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
54
|
+
//# sourceMappingURL=chunk-AEG6LOAJ.mjs.map
|
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Component
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EB2V4OKL.mjs";
|
|
4
4
|
import {
|
|
5
|
-
Utils
|
|
6
|
-
|
|
5
|
+
Utils,
|
|
6
|
+
afterFormReset,
|
|
7
|
+
formEventScope
|
|
8
|
+
} from "./chunk-67UNY7RH.mjs";
|
|
7
9
|
|
|
8
10
|
// src/ts/components/currencyInput.ts
|
|
9
11
|
var _defaults = Object.freeze({ i18n: Object.freeze({ invalid: "Enter an amount." }) });
|
|
10
12
|
var LATIN = "9876543210";
|
|
11
|
-
function
|
|
13
|
+
function groupsThree(clean, mark, decimals) {
|
|
14
|
+
if (clean.length - mark !== 4) return false;
|
|
15
|
+
const separator = clean[mark];
|
|
16
|
+
const earlier = clean.slice(0, mark).replace(/^\D+|\d/g, "");
|
|
17
|
+
if (earlier.split(separator).join("")) return false;
|
|
18
|
+
return earlier.includes(separator) || !decimals.includes(separator);
|
|
19
|
+
}
|
|
20
|
+
function split(clean, digits, decimals) {
|
|
12
21
|
const mark = Math.max(clean.lastIndexOf("."), clean.lastIndexOf(","));
|
|
13
22
|
const fraction = clean.slice(mark + 1);
|
|
14
|
-
return mark >= 0 && fraction.length <= digits ? [clean.slice(0, mark), fraction] : [clean, ""];
|
|
23
|
+
return mark >= 0 && fraction.length <= digits && !groupsThree(clean, mark, decimals) ? [clean.slice(0, mark), fraction] : [clean, ""];
|
|
24
|
+
}
|
|
25
|
+
function decimalMarks(formats) {
|
|
26
|
+
return formats.flatMap((format) => format.formatToParts(1.5).filter((part) => part.type === "decimal").map((part) => part.value));
|
|
15
27
|
}
|
|
16
|
-
function parse(text, digits) {
|
|
28
|
+
function parse(text, digits, decimals) {
|
|
17
29
|
const clean = text.replace(/[^\d.,]/g, "").replace(/[.,]+$/, "");
|
|
18
30
|
if (!/\d/.test(clean)) return null;
|
|
19
|
-
const [whole, fraction] = split(clean, digits);
|
|
31
|
+
const [whole, fraction] = split(clean, digits, decimals);
|
|
20
32
|
const sign = /^\D*[-−]/.test(text) ? -1 : 1;
|
|
21
33
|
return sign * Number(`${whole.replace(/[.,]/g, "")}.${fraction}`);
|
|
22
34
|
}
|
|
@@ -48,12 +60,6 @@ var CurrencyInput = class extends Component {
|
|
|
48
60
|
const invalid = el.value.trim() !== "" && this.value === null;
|
|
49
61
|
el.setCustomValidity(invalid ? this.options.i18n.invalid : "");
|
|
50
62
|
};
|
|
51
|
-
this._onReset = (e) => {
|
|
52
|
-
if (e.target === this.el.form) setTimeout(this._afterReset);
|
|
53
|
-
};
|
|
54
|
-
this._afterReset = () => {
|
|
55
|
-
if (!this._events.signal.aborted) this._start();
|
|
56
|
-
};
|
|
57
63
|
/** Puts the plain number in the form data in place of the formatted text. */
|
|
58
64
|
this._onFormData = (e) => {
|
|
59
65
|
if (!this._submitsTo(e.target)) return;
|
|
@@ -69,16 +75,16 @@ var CurrencyInput = class extends Component {
|
|
|
69
75
|
this._edit = new Intl.NumberFormat(locale, { useGrouping: false, maximumFractionDigits: this._digits });
|
|
70
76
|
this._plain = new Intl.NumberFormat("en", { useGrouping: false, maximumFractionDigits: this._digits });
|
|
71
77
|
this._native = new Intl.NumberFormat(locale, { useGrouping: false }).format(9876543210);
|
|
72
|
-
|
|
78
|
+
const decimals = decimalMarks([this._display, this._edit]);
|
|
79
|
+
this._marks = Object.fromEntries(decimals.filter((mark) => !".,".includes(mark)).map((mark) => [mark, "."]));
|
|
80
|
+
this._decimals = decimals.map((mark) => this._marks[mark] ?? mark);
|
|
73
81
|
const signal = this._events.signal;
|
|
74
82
|
el.addEventListener("change", this._onChange, { capture: true, signal });
|
|
75
83
|
el.addEventListener("blur", this._format, { signal });
|
|
76
84
|
el.addEventListener("focus", this._onFocus, { signal });
|
|
77
85
|
el.addEventListener("input", this._validate, { signal });
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
scope.addEventListener("formdata", this._onFormData, { capture: true, signal });
|
|
81
|
-
scope.addEventListener("reset", this._onReset, { capture: true, signal });
|
|
86
|
+
formEventScope(el).addEventListener("formdata", this._onFormData, { capture: true, signal });
|
|
87
|
+
afterFormReset(el, () => this._start(), signal);
|
|
82
88
|
this._start();
|
|
83
89
|
}
|
|
84
90
|
static get defaults() {
|
|
@@ -90,8 +96,8 @@ var CurrencyInput = class extends Component {
|
|
|
90
96
|
}
|
|
91
97
|
/** Reads text in the locale's digits, or full-width ones from an IME, as an amount. */
|
|
92
98
|
_read(text) {
|
|
93
|
-
const latin = Array.from(text.normalize("NFKC"), (ch) =>
|
|
94
|
-
return parse(latin.join(""), this._digits);
|
|
99
|
+
const latin = Array.from(text.normalize("NFKC"), (ch) => this._marks[ch] ?? LATIN[this._native.indexOf(ch)] ?? ch);
|
|
100
|
+
return parse(latin.join(""), this._digits, this._decimals);
|
|
95
101
|
}
|
|
96
102
|
_round(amount) {
|
|
97
103
|
return amount === null ? null : Number(this._plain.format(amount));
|
|
@@ -126,4 +132,4 @@ CurrencyInput.key = "Expressive_CurrencyInput";
|
|
|
126
132
|
export {
|
|
127
133
|
CurrencyInput
|
|
128
134
|
};
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
135
|
+
//# sourceMappingURL=chunk-AQNFCJJ5.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/ts/components/currencyInput.ts"],
|
|
4
|
+
"sourcesContent": ["import { Component, BaseOptions } from '../core/component';\nimport { Utils, afterFormReset, formEventScope } from '../core/utils';\n\nexport interface CurrencyInputOptions extends BaseOptions {\n /** `invalid` is the validity message for text that holds no amount. */\n i18n: Partial<{ invalid: string }>;\n}\n\nconst _defaults: CurrencyInputOptions = Object.freeze({ i18n: Object.freeze({ invalid: 'Enter an amount.' }) });\n\n/** Latin digits in the order `_native` holds them, to map a locale's own digits back. */\nconst LATIN = '9876543210';\n\n/**\n * Whether the separator at `mark` groups even though the currency could show\n * three digits after it. After a separator of the other kind it is a decimal,\n * as in `1.234,567`. A repeated separator groups, and so does a lone one that\n * is not one of the locale's `decimals`. The earlier separators are read from\n * the first digit, since a symbol such as `\u062F.\u0628.` may hold marks.\n */\nfunction groupsThree(clean: string, mark: number, decimals: string[]) {\n if (clean.length - mark !== 4) return false;\n const separator = clean[mark];\n const earlier = clean.slice(0, mark).replace(/^\\D+|\\d/g, '');\n if (earlier.split(separator).join('')) return false;\n return earlier.includes(separator) || !decimals.includes(separator);\n}\n\n/**\n * Splits digits and separators into whole and fraction digits. The last `.`\n * or `,` is the decimal mark when no more than `digits` digits follow it and\n * it does not group, so `1.234,5` and `1,234.50` both split at the end. Any\n * other separator groups.\n */\nfunction split(clean: string, digits: number, decimals: string[]): [string, string] {\n const mark = Math.max(clean.lastIndexOf('.'), clean.lastIndexOf(','));\n const fraction = clean.slice(mark + 1);\n return mark >= 0 && fraction.length <= digits && !groupsThree(clean, mark, decimals) ? [clean.slice(0, mark), fraction] : [clean, ''];\n}\n\n/**\n * The marks a locale writes decimals with. Some use one for currency and\n * another for plain numbers: fr-CH shows `1.234 KWD` and edits `1,234`.\n */\nfunction decimalMarks(formats: Intl.NumberFormat[]) {\n return formats.flatMap((format) => format.formatToParts(1.5).filter((part) => part.type === 'decimal').map((part) => part.value));\n}\n\n/**\n * Reads text in Latin digits as an amount, or null when it holds no digit.\n * Signs, spaces and letters are ignored, and a `-` or `\u2212` before the first\n * digit makes the amount negative. A separator after the last digit is\n * dropped, since it belongs to a symbol such as `kr.`.\n */\nfunction parse(text: string, digits: number, decimals: string[]): number | null {\n const clean = text.replace(/[^\\d.,]/g, '').replace(/[.,]+$/, '');\n if (!/\\d/.test(clean)) return null;\n const [whole, fraction] = split(clean, digits, decimals);\n const sign = /^\\D*[-\u2212]/.test(text) ? -1 : 1;\n return sign * Number(`${whole.replace(/[.,]/g, '')}.${fraction}`);\n}\n\n/**\n * Currency input. Shows a text field's amount in the page's locale and the\n * currency its `data-currency` attribute names once the field loses focus,\n * shows the plain number while it has focus, and puts the plain number in the\n * form data.\n */\nexport class CurrencyInput extends Component<CurrencyInputOptions> {\n declare el: HTMLInputElement;\n static key = 'Expressive_CurrencyInput';\n private _display: Intl.NumberFormat;\n /** Writes an amount for editing: the locale's digits and mark, no grouping or sign. */\n private _edit: Intl.NumberFormat;\n /** Writes an amount as the form submits it: Latin digits, a `.` mark, no grouping. */\n private _plain: Intl.NumberFormat;\n private _digits: number;\n /** The locale's digits for 9876543210. */\n private _native: string;\n /** Rewrites a decimal mark that is neither `.` nor `,`, such as Arabic's, to `.`. */\n private _marks: Record<string, string>;\n /** The locale's decimal marks, after `_marks`. */\n private _decimals: string[];\n\n constructor(el: HTMLInputElement, options: Partial<CurrencyInputOptions>) {\n super(el, options);\n const locale = Utils.locale(el);\n try {\n this._display = new Intl.NumberFormat(locale, { style: 'currency', currency: el.dataset.currency });\n } catch {\n // A malformed code leaves the field a plain text input.\n return;\n }\n this._digits = this._display.resolvedOptions().maximumFractionDigits;\n this._edit = new Intl.NumberFormat(locale, { useGrouping: false, maximumFractionDigits: this._digits });\n this._plain = new Intl.NumberFormat('en', { useGrouping: false, maximumFractionDigits: this._digits });\n this._native = new Intl.NumberFormat(locale, { useGrouping: false }).format(9876543210);\n const decimals = decimalMarks([this._display, this._edit]);\n this._marks = Object.fromEntries(decimals.filter((mark) => !'.,'.includes(mark)).map((mark) => [mark, '.']));\n this._decimals = decimals.map((mark) => this._marks[mark] ?? mark);\n const signal = this._events.signal;\n // Capture, so a page's change listener on the field reads the formatted\n // text. Blur covers a visit that changed nothing, which fires no change.\n el.addEventListener('change', this._onChange, { capture: true, signal });\n el.addEventListener('blur', this._format, { signal });\n el.addEventListener('focus', this._onFocus, { signal });\n el.addEventListener('input', this._validate, { signal });\n formEventScope(el).addEventListener('formdata', this._onFormData, { capture: true, signal });\n // The field formats the value a reset restores.\n afterFormReset(el, () => this._start(), signal);\n this._start();\n }\n\n static get defaults(): CurrencyInputOptions {\n return _defaults;\n }\n\n /** The amount the field holds, rounded as it shows, or null when it is empty or holds no digit. */\n get value(): number | null {\n return this._round(this._read(this.el.value));\n }\n\n /** Reads text in the locale's digits, or full-width ones from an IME, as an amount. */\n private _read(text: string) {\n const latin = Array.from(text.normalize('NFKC'), (ch) => this._marks[ch] ?? LATIN[this._native.indexOf(ch)] ?? ch);\n return parse(latin.join(''), this._digits, this._decimals);\n }\n\n private _round(amount: number | null) {\n return amount === null ? null : Number(this._plain.format(amount));\n }\n\n /**\n * Formats the starting value without events. It reads as a plain number\n * first, the form the field submits, so a saved `1.234` stays 1.234.\n */\n private _start() {\n const text = this.el.value;\n const number = Number(text);\n this._show(this._round(text.trim() && Number.isFinite(number) ? number : this._read(text)));\n }\n\n private _format = () => {\n this._show(this.value);\n };\n\n /**\n * Enter fires change while the field keeps focus. Formatting then would\n * put the next keystroke after the currency sign, so the field waits for\n * blur.\n */\n private _onChange = () => {\n if (!this.el.matches(':focus')) this._format();\n };\n\n /** Shows `amount` in the locale and currency, leaving text that reads as no amount as it is. */\n private _show(amount: number | null) {\n if (amount !== null) this.el.value = this._display.format(amount);\n this._validate();\n }\n\n /** Shows the amount as a plain number to edit, with the caret at the end. */\n private _onFocus = () => {\n const amount = this.value;\n if (amount === null) return;\n const text = this._edit.format(amount);\n this.el.value = text;\n this.el.setSelectionRange(text.length, text.length);\n };\n\n /** Marks text that holds no amount invalid, and clears the mark once it holds one or is empty. */\n private _validate = () => {\n const { el } = this;\n const invalid = el.value.trim() !== '' && this.value === null;\n el.setCustomValidity(invalid ? this.options.i18n.invalid : '');\n };\n\n /** Puts the plain number in the form data in place of the formatted text. */\n private _onFormData = (e: FormDataEvent) => {\n if (!this._submitsTo(e.target)) return;\n // ponytail: set() collapses fields that share this name into one entry; a\n // per-field swap if a form ever repeats a currency field's name.\n e.formData.set(this.el.name, String(this.value ?? this.el.value));\n };\n\n /** Whether the field has an entry in `form`'s data. */\n private _submitsTo(form: EventTarget) {\n const { el } = this;\n return form === el.form && el.name !== '' && !el.matches(':disabled');\n }\n\n /** Stops formatting and clears the validity message, leaving the text as it is. */\n destroy(): void {\n super.destroy();\n this.el.setCustomValidity('');\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;AAQA,IAAM,YAAkC,OAAO,OAAO,EAAE,MAAM,OAAO,OAAO,EAAE,SAAS,mBAAmB,CAAC,EAAE,CAAC;AAG9G,IAAM,QAAQ;AASd,SAAS,YAAY,OAAe,MAAc,UAAoB;AACpE,MAAI,MAAM,SAAS,SAAS,EAAG,QAAO;AACtC,QAAM,YAAY,MAAM,IAAI;AAC5B,QAAM,UAAU,MAAM,MAAM,GAAG,IAAI,EAAE,QAAQ,YAAY,EAAE;AAC3D,MAAI,QAAQ,MAAM,SAAS,EAAE,KAAK,EAAE,EAAG,QAAO;AAC9C,SAAO,QAAQ,SAAS,SAAS,KAAK,CAAC,SAAS,SAAS,SAAS;AACpE;AAQA,SAAS,MAAM,OAAe,QAAgB,UAAsC;AAClF,QAAM,OAAO,KAAK,IAAI,MAAM,YAAY,GAAG,GAAG,MAAM,YAAY,GAAG,CAAC;AACpE,QAAM,WAAW,MAAM,MAAM,OAAO,CAAC;AACrC,SAAO,QAAQ,KAAK,SAAS,UAAU,UAAU,CAAC,YAAY,OAAO,MAAM,QAAQ,IAAI,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,QAAQ,IAAI,CAAC,OAAO,EAAE;AACtI;AAMA,SAAS,aAAa,SAA8B;AAClD,SAAO,QAAQ,QAAQ,CAAC,WAAW,OAAO,cAAc,GAAG,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,SAAS,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;AAClI;AAQA,SAAS,MAAM,MAAc,QAAgB,UAAmC;AAC9E,QAAM,QAAQ,KAAK,QAAQ,YAAY,EAAE,EAAE,QAAQ,UAAU,EAAE;AAC/D,MAAI,CAAC,KAAK,KAAK,KAAK,EAAG,QAAO;AAC9B,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,OAAO,QAAQ,QAAQ;AACvD,QAAM,OAAO,WAAW,KAAK,IAAI,IAAI,KAAK;AAC1C,SAAO,OAAO,OAAO,GAAG,MAAM,QAAQ,SAAS,EAAE,CAAC,IAAI,QAAQ,EAAE;AAClE;AAQO,IAAM,gBAAN,cAA4B,UAAgC;AAAA,EAgBjE,YAAY,IAAsB,SAAwC;AACxE,UAAM,IAAI,OAAO;AAyDnB,SAAQ,UAAU,MAAM;AACtB,WAAK,MAAM,KAAK,KAAK;AAAA,IACvB;AAOA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,YAAY,MAAM;AACxB,UAAI,CAAC,KAAK,GAAG,QAAQ,QAAQ,EAAG,MAAK,QAAQ;AAAA,IAC/C;AASA;AAAA,SAAQ,WAAW,MAAM;AACvB,YAAM,SAAS,KAAK;AACpB,UAAI,WAAW,KAAM;AACrB,YAAM,OAAO,KAAK,MAAM,OAAO,MAAM;AACrC,WAAK,GAAG,QAAQ;AAChB,WAAK,GAAG,kBAAkB,KAAK,QAAQ,KAAK,MAAM;AAAA,IACpD;AAGA;AAAA,SAAQ,YAAY,MAAM;AACxB,YAAM,EAAE,GAAG,IAAI;AACf,YAAM,UAAU,GAAG,MAAM,KAAK,MAAM,MAAM,KAAK,UAAU;AACzD,SAAG,kBAAkB,UAAU,KAAK,QAAQ,KAAK,UAAU,EAAE;AAAA,IAC/D;AAGA;AAAA,SAAQ,cAAc,CAAC,MAAqB;AAC1C,UAAI,CAAC,KAAK,WAAW,EAAE,MAAM,EAAG;AAGhC,QAAE,SAAS,IAAI,KAAK,GAAG,MAAM,OAAO,KAAK,SAAS,KAAK,GAAG,KAAK,CAAC;AAAA,IAClE;AAjGE,UAAM,SAAS,MAAM,OAAO,EAAE;AAC9B,QAAI;AACF,WAAK,WAAW,IAAI,KAAK,aAAa,QAAQ,EAAE,OAAO,YAAY,UAAU,GAAG,QAAQ,SAAS,CAAC;AAAA,IACpG,QAAQ;AAEN;AAAA,IACF;AACA,SAAK,UAAU,KAAK,SAAS,gBAAgB,EAAE;AAC/C,SAAK,QAAQ,IAAI,KAAK,aAAa,QAAQ,EAAE,aAAa,OAAO,uBAAuB,KAAK,QAAQ,CAAC;AACtG,SAAK,SAAS,IAAI,KAAK,aAAa,MAAM,EAAE,aAAa,OAAO,uBAAuB,KAAK,QAAQ,CAAC;AACrG,SAAK,UAAU,IAAI,KAAK,aAAa,QAAQ,EAAE,aAAa,MAAM,CAAC,EAAE,OAAO,UAAU;AACtF,UAAM,WAAW,aAAa,CAAC,KAAK,UAAU,KAAK,KAAK,CAAC;AACzD,SAAK,SAAS,OAAO,YAAY,SAAS,OAAO,CAAC,SAAS,CAAC,KAAK,SAAS,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3G,SAAK,YAAY,SAAS,IAAI,CAAC,SAAS,KAAK,OAAO,IAAI,KAAK,IAAI;AACjE,UAAM,SAAS,KAAK,QAAQ;AAG5B,OAAG,iBAAiB,UAAU,KAAK,WAAW,EAAE,SAAS,MAAM,OAAO,CAAC;AACvE,OAAG,iBAAiB,QAAQ,KAAK,SAAS,EAAE,OAAO,CAAC;AACpD,OAAG,iBAAiB,SAAS,KAAK,UAAU,EAAE,OAAO,CAAC;AACtD,OAAG,iBAAiB,SAAS,KAAK,WAAW,EAAE,OAAO,CAAC;AACvD,mBAAe,EAAE,EAAE,iBAAiB,YAAY,KAAK,aAAa,EAAE,SAAS,MAAM,OAAO,CAAC;AAE3F,mBAAe,IAAI,MAAM,KAAK,OAAO,GAAG,MAAM;AAC9C,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,WAAW,WAAiC;AAC1C,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,QAAuB;AACzB,WAAO,KAAK,OAAO,KAAK,MAAM,KAAK,GAAG,KAAK,CAAC;AAAA,EAC9C;AAAA;AAAA,EAGQ,MAAM,MAAc;AAC1B,UAAM,QAAQ,MAAM,KAAK,KAAK,UAAU,MAAM,GAAG,CAAC,OAAO,KAAK,OAAO,EAAE,KAAK,MAAM,KAAK,QAAQ,QAAQ,EAAE,CAAC,KAAK,EAAE;AACjH,WAAO,MAAM,MAAM,KAAK,EAAE,GAAG,KAAK,SAAS,KAAK,SAAS;AAAA,EAC3D;AAAA,EAEQ,OAAO,QAAuB;AACpC,WAAO,WAAW,OAAO,OAAO,OAAO,KAAK,OAAO,OAAO,MAAM,CAAC;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,SAAS;AACf,UAAM,OAAO,KAAK,GAAG;AACrB,UAAM,SAAS,OAAO,IAAI;AAC1B,SAAK,MAAM,KAAK,OAAO,KAAK,KAAK,KAAK,OAAO,SAAS,MAAM,IAAI,SAAS,KAAK,MAAM,IAAI,CAAC,CAAC;AAAA,EAC5F;AAAA;AAAA,EAgBQ,MAAM,QAAuB;AACnC,QAAI,WAAW,KAAM,MAAK,GAAG,QAAQ,KAAK,SAAS,OAAO,MAAM;AAChE,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EA2BQ,WAAW,MAAmB;AACpC,UAAM,EAAE,GAAG,IAAI;AACf,WAAO,SAAS,GAAG,QAAQ,GAAG,SAAS,MAAM,CAAC,GAAG,QAAQ,WAAW;AAAA,EACtE;AAAA;AAAA,EAGA,UAAgB;AACd,UAAM,QAAQ;AACd,SAAK,GAAG,kBAAkB,EAAE;AAAA,EAC9B;AACF;AAhIa,cAEJ,MAAM;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|