@leavepulse/ui 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -3
- package/dist/assets/{LpCommandPalette-DCP5g2ik.css → LpCommandPalette-nGNnzXqQ.css} +21 -21
- package/dist/assets/{LpRollingText-CR1mb0Vt.css → LpRollingText-BGnE6q9j.css} +3 -0
- package/dist/assets/{LpShortcutsDialog-ChUyU7oV.css → LpShortcutsDialog-CqYX3yy6.css} +28 -7
- package/dist/assets/{LpTable-DDQNdf86.css → LpTable-CPwWxTJS.css} +49 -9
- package/dist/chunks/{ContextMenuItems-CrfeNPWT.js → ContextMenuItems-BtKU7074.js} +2 -1
- package/dist/chunks/{LayoutCanvas-D-jPOAvg.js → LayoutCanvas-BkaGXTjx.js} +1 -1
- package/dist/chunks/{LayoutNode-D8Xy9N-o.js → LayoutNode-B3_9E1Xa.js} +9 -5
- package/dist/chunks/{LpActionBar-BbwCF6l5.js → LpActionBar-CnfFVIwC.js} +3 -3
- package/dist/chunks/{LpAddressInput-BrOqkZqg.js → LpAddressInput-BwX8uo4_.js} +7 -7
- package/dist/chunks/{LpAlert-DkOIjsbq.js → LpAlert-B6ilrM7T.js} +1 -1
- package/dist/chunks/{LpAppShell-BUKPcHco.js → LpAppShell-C53_RaGF.js} +4 -4
- package/dist/chunks/{LpAutocomplete-DxV3Y9h4.js → LpAutocomplete-C8IgSMsi.js} +2 -1
- package/dist/chunks/{LpAvatar-ClBbEYZN.js → LpAvatar-C1XStdOo.js} +3 -3
- package/dist/chunks/{LpBreadcrumbs-B8IJgFIc.js → LpBreadcrumbs-CQJd73Gm.js} +1 -1
- package/dist/chunks/{LpButton-kqJBHIIb.js → LpButton-CKSwC6Mu.js} +1 -1
- package/dist/chunks/{LpCalendar-B7Ko5RdR.js → LpCalendar-DrA2McTU.js} +1 -1
- package/dist/chunks/{LpCard-CpRByR0q.js → LpCard-C2IvucmX.js} +1 -1
- package/dist/chunks/{LpCheckbox-DpqchzH4.js → LpCheckbox-Bkj13CbW.js} +1 -1
- package/dist/chunks/{LpCodeBlock-DeHJPq7p.js → LpCodeBlock-ca7ANFyW.js} +1 -1
- package/dist/chunks/{LpColorPicker-Bhf1qxhB.js → LpColorPicker-zz4VGkG4.js} +46 -20
- package/dist/chunks/{LpConfirmDialog-CIUOSnWo.js → LpConfirmDialog-DCw5jR1t.js} +3 -3
- package/dist/chunks/{LpConfirmHost-CDm0Wtpv.js → LpConfirmHost-CbywMpMP.js} +1 -1
- package/dist/chunks/{LpContextMenu-DQw5dGfG.js → LpContextMenu-BqkAYa9X.js} +2 -1
- package/dist/chunks/{LpDatePicker-Drirx1AK.js → LpDatePicker-DuusgT69.js} +13 -9
- package/dist/chunks/LpDateRangePicker-DHeuznfC.js +208 -0
- package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
- package/dist/chunks/{LpDrawer-bScx-KMO.js → LpDrawer-Dt4aB4Fr.js} +2 -2
- package/dist/chunks/{LpDropdownMenu-DJpoqRCb.js → LpDropdownMenu-DdJGQzPn.js} +2 -1
- package/dist/chunks/{LpEmptyState-Dsdqz1Wj.js → LpEmptyState-BJbZXC8D.js} +1 -1
- package/dist/chunks/LpFieldNotice-wtieLUTj.js +64 -0
- package/dist/chunks/{LpFileTree-CSI_5SK2.js → LpFileTree-CNurOvkV.js} +3 -3
- package/dist/chunks/{LpFileTreeNode-B4MiSQX2.js → LpFileTreeNode-D_qRRdsH.js} +3 -3
- package/dist/chunks/{LpFormField-CIk5qosh.js → LpFormField-CtVYj6tO.js} +29 -15
- package/dist/chunks/{LpIcon-CCnX5_2j.js → LpIcon-DYtaid0K.js} +20 -6
- package/dist/chunks/{LpInput-C0e8y8_d.js → LpInput-D-YGQbEv.js} +39 -16
- package/dist/chunks/{LpLightbox-Mr0W_6HK.js → LpLightbox-DSDahF23.js} +1 -1
- package/dist/chunks/{LpLogViewer-YBOaFnc_.js → LpLogViewer-KypbgOyo.js} +252 -58
- package/dist/chunks/{LpMapPicker-NUzS9e37.js → LpMapPicker-Cc84LaM3.js} +1 -1
- package/dist/chunks/{LpMediaGallery-DfJmMW0I.js → LpMediaGallery-YKr-cQPN.js} +2 -2
- package/dist/chunks/{LpModal-DPwGKBPv.js → LpModal-DMMDQHAu.js} +3 -3
- package/dist/chunks/{LpModalNav-DPg5H9f3.js → LpModalNav-BVPDyIup.js} +4 -4
- package/dist/chunks/{LpNotificationBell-t5yn-siX.js → LpNotificationBell-CCwyJP5C.js} +26 -14
- package/dist/chunks/{LpNumberField-DWnQKktS.js → LpNumberField-B77O-z28.js} +1 -1
- package/dist/chunks/{LpPagination-DFngX7Xo.js → LpPagination-BcDlo3r_.js} +1 -1
- package/dist/chunks/{LpPasswordInput-CWVDuBJ5.js → LpPasswordInput-eKzQhbQP.js} +2 -2
- package/dist/chunks/{LpPhoneInput-CD-ZTeSd.js → LpPhoneInput-BCbrrhgE.js} +126 -23
- package/dist/chunks/{LpPie-BLco31Nx.js → LpPie-BEJ2SfI6.js} +9 -10
- package/dist/chunks/{LpPopover-p_e_shN3.js → LpPopover-DCKeqiVn.js} +1 -0
- package/dist/chunks/LpRangeCalendar-DZuaRV0W.js +331 -0
- package/dist/chunks/{LpRollingText-CM5gkSeF.js → LpRollingText-DB7upf-5.js} +1 -1
- package/dist/chunks/{LpScrollArea-D8xyKXcF.js → LpScrollArea-Bry1fdOM.js} +14 -2
- package/dist/chunks/{LpSection-aM6J_u51.js → LpSection-tKEH0jYf.js} +1 -1
- package/dist/chunks/{LpSegmented-Dejd7cIx.js → LpSegmented-C09EEam2.js} +1 -1
- package/dist/chunks/{LpSelect-DrRD9I7L.js → LpSelect-DNIL6MB9.js} +20 -23
- package/dist/chunks/{LpSidebar-BgwUNWpJ.js → LpSidebar-CQNB0-yU.js} +3 -3
- package/dist/chunks/{LpSidebarNav-Bqxz8aWT.js → LpSidebarNav-kx-LdqNU.js} +3 -3
- package/dist/chunks/{LpStat-dXtyrdX9.js → LpStat-B-7udBRP.js} +1 -1
- package/dist/chunks/{LpStepper-Bk-XaQYU.js → LpStepper-C7XiVKZj.js} +34 -19
- package/dist/chunks/{LpSwitch-B1aLKulj.js → LpSwitch-OrcOJX9m.js} +1 -1
- package/dist/chunks/LpTableFilter-BV8PpLM9.js +158 -0
- package/dist/chunks/{LpTabs-_qMVaZJ6.js → LpTabs-29iW1XOx.js} +1 -1
- package/dist/chunks/LpTagsInput-D1SuqbhB.js +300 -0
- package/dist/chunks/{LpTextarea-yP8hGedR.js → LpTextarea-uuScWo6-.js} +1 -1
- package/dist/chunks/{LpThemeProvider-Wjm7Tuit.js → LpThemeProvider-Be4r2TQX.js} +1 -1
- package/dist/chunks/{LpThemeSwitcher-CKLc10KZ.js → LpThemeSwitcher-B4aVPbNC.js} +19 -22
- package/dist/chunks/{LpTimeField-C5HRgS5H.js → LpTimeField-Cqe7fXnh.js} +1 -1
- package/dist/chunks/{LpToaster--oT6lfhL.js → LpToaster-DpJvoCOh.js} +37 -3
- package/dist/chunks/{LpTooltip-CBdzeKNf.js → LpTooltip-CXVKcOXT.js} +3 -2
- package/dist/chunks/{LpTopologyCanvas-DocsLal3.js → LpTopologyCanvas-6ozNrQKh.js} +2 -1
- package/dist/chunks/{LpUptimeBar-DLtxOMRy.js → LpUptimeBar-ugcv25xj.js} +2 -2
- package/dist/chunks/icons-CkA0njzm.js +43 -0
- package/dist/chunks/{useTheme-C8JHUlPM.js → useTheme-DJm1JSsY.js} +47 -50
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +2 -2
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +16 -1
- package/dist/components/LpChart.vue.js +63 -5
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +2 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +6 -0
- package/dist/components/LpCommandPalette.vue.js +61 -10
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +60 -0
- package/dist/components/LpDateRangePicker.vue.js +4 -0
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.d.ts +8 -0
- package/dist/components/LpFieldNotice.vue.js +4 -0
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +4 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpIcon.vue.d.ts +0 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +2 -2
- package/dist/components/LpInput.vue.d.ts +19 -0
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +52 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.d.ts +13 -0
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.d.ts +1 -1
- package/dist/components/LpRangeCalendar.vue.d.ts +51 -0
- package/dist/components/LpRangeCalendar.vue.js +4 -0
- package/dist/components/LpRollingText.vue.d.ts +1 -1
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.d.ts +2 -2
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +6 -0
- package/dist/components/LpShortcutsDialog.vue.js +56 -14
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +84 -2
- package/dist/components/LpTable.vue.js +481 -107
- package/dist/components/LpTableFilter.vue.d.ts +31 -0
- package/dist/components/LpTableFilter.vue.js +4 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTagsInput.vue.d.ts +77 -0
- package/dist/components/LpTagsInput.vue.js +4 -0
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.d.ts +2 -2
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.js +3 -2
- package/dist/components/commandSearch.js +3 -0
- package/dist/components/dateRange.d.ts +43 -0
- package/dist/components/dateRange.js +76 -0
- package/dist/components/exportData.d.ts +58 -0
- package/dist/components/exportData.js +165 -0
- package/dist/components/logSearch.d.ts +18 -0
- package/dist/components/logSearch.js +36 -0
- package/dist/components/phone.d.ts +41 -0
- package/dist/components/phone.js +80 -0
- package/dist/components/tableFilter.d.ts +36 -0
- package/dist/components/tableFilter.js +84 -0
- package/dist/components/tags.d.ts +20 -0
- package/dist/components/tags.js +37 -0
- package/dist/composables/useField.d.ts +4 -0
- package/dist/composables/useFieldNotice.d.ts +17 -0
- package/dist/composables/useFieldNotice.js +28 -0
- package/dist/composables/useForm.d.ts +53 -0
- package/dist/composables/useForm.js +207 -0
- package/dist/composables/useHotkeys.d.ts +65 -5
- package/dist/composables/useHotkeys.js +170 -9
- package/dist/composables/useInputMask.d.ts +59 -0
- package/dist/composables/useInputMask.js +170 -0
- package/dist/composables/usePaintSelect.d.ts +22 -0
- package/dist/composables/usePaintSelect.js +169 -0
- package/dist/composables/useShift.js +7 -2
- package/dist/icons-all.d.ts +1 -0
- package/dist/icons-all.js +3 -0
- package/dist/icons.d.ts +14 -1
- package/dist/index.d.ts +29 -3
- package/dist/index.js +195 -138
- package/dist/kit-icons.d.ts +2 -0
- package/dist/theme/backdrop.d.ts +12 -0
- package/dist/theme/presets.d.ts +0 -2
- package/package.json +13 -4
- package/src/component-names.ts +4 -0
- package/src/components/ContextMenuItems.vue +1 -1
- package/src/components/LayoutNode.vue +11 -2
- package/src/components/LpActionBar.vue +1 -1
- package/src/components/LpAutocomplete.vue +1 -0
- package/src/components/LpAvatar.vue +2 -2
- package/src/components/LpButton.vue +1 -1
- package/src/components/LpChart.vue +80 -1
- package/src/components/LpColorPicker.vue +34 -1
- package/src/components/LpCommandPalette.vue +81 -7
- package/src/components/LpContextMenu.vue +1 -1
- package/src/components/LpDatePicker.vue +8 -3
- package/src/components/LpDateRangePicker.vue +224 -0
- package/src/components/LpDropdownMenu.vue +1 -0
- package/src/components/LpFieldNotice.vue +65 -0
- package/src/components/LpFormField.vue +33 -4
- package/src/components/LpIcon.vue +22 -4
- package/src/components/LpInput.vue +46 -6
- package/src/components/LpLogViewer.vue +225 -22
- package/src/components/LpModal.vue +1 -1
- package/src/components/LpNotificationBell.vue +15 -7
- package/src/components/LpPhoneInput.vue +179 -20
- package/src/components/LpPie.vue +8 -5
- package/src/components/LpPopover.vue +1 -0
- package/src/components/LpRangeCalendar.vue +365 -0
- package/src/components/LpRollingText.vue +3 -0
- package/src/components/LpScrollArea.vue +22 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +77 -8
- package/src/components/LpStepper.vue +23 -10
- package/src/components/LpSwitch.vue +1 -1
- package/src/components/LpTable.vue +584 -65
- package/src/components/LpTableFilter.vue +175 -0
- package/src/components/LpTagsInput.vue +364 -0
- package/src/components/LpThemeSwitcher.vue +19 -23
- package/src/components/LpToaster.vue +49 -1
- package/src/components/LpTooltip.vue +9 -2
- package/src/components/LpTopologyCanvas.vue +7 -1
- package/src/components/chart.ts +5 -3
- package/src/components/commandSearch.ts +6 -0
- package/src/components/dateRange.ts +134 -0
- package/src/components/exportData.ts +228 -0
- package/src/components/logSearch.ts +46 -0
- package/src/components/phone.ts +140 -0
- package/src/components/tableFilter.ts +122 -0
- package/src/components/tags.ts +62 -0
- package/src/composables/useField.ts +4 -0
- package/src/composables/useFieldNotice.ts +59 -0
- package/src/composables/useForm.ts +314 -0
- package/src/composables/useHotkeys.ts +269 -13
- package/src/composables/useInputMask.ts +254 -0
- package/src/composables/usePaintSelect.ts +243 -0
- package/src/composables/useShift.ts +13 -2
- package/src/iconSubset.d.mts +10 -0
- package/src/iconSubset.mjs +51 -0
- package/src/icons-all.ts +10 -0
- package/src/icons.ts +59 -8
- package/src/index.ts +37 -3
- package/src/kit-icons.ts +5 -0
- package/src/nuxt.ts +30 -2
- package/src/runtime/theme.plugin.ts +2 -2
- package/src/theme/backdrop.ts +69 -0
- package/src/theme/presets.ts +6 -64
- package/src/tokens/tokens.css +46 -0
|
@@ -13,8 +13,14 @@ export default { inheritAttrs: false }
|
|
|
13
13
|
* match wins). The model is the full string ("+380501234567"); a `change` event
|
|
14
14
|
* also carries the parsed { country, dialCode, number } for forms that want it.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Numbers are formatted as they are typed ("+380 50 123 4567") with
|
|
17
|
+
* libphonenumber-js, loaded on first use (see phone.ts). Digits past the
|
|
18
|
+
* longest number the country allows are dropped with a passing note, and a
|
|
19
|
+
* number that cannot be right is marked once the field is left, with the reason
|
|
20
|
+
* floating under it. v-model stays the formatted text the field shows; `change`
|
|
21
|
+
* also carries `e164` ("+380501234567"), `possible` and `valid`, which
|
|
22
|
+
* is what a form should store. Until the library arrives the field takes digits
|
|
23
|
+
* as before, unformatted.
|
|
18
24
|
*/
|
|
19
25
|
import {
|
|
20
26
|
ComboboxAnchor,
|
|
@@ -27,13 +33,24 @@ import {
|
|
|
27
33
|
ComboboxTrigger,
|
|
28
34
|
ComboboxViewport,
|
|
29
35
|
} from "reka-ui"
|
|
30
|
-
import { computed, onMounted, ref, useAttrs } from "vue"
|
|
31
|
-
import { useFieldControl } from "../composables/useField"
|
|
36
|
+
import { computed, onMounted, ref, shallowRef, useAttrs } from "vue"
|
|
37
|
+
import { useField, useFieldControl } from "../composables/useField"
|
|
38
|
+
import { useFieldNotice, type FieldNoticeOptions } from "../composables/useFieldNotice"
|
|
32
39
|
import { useInputFilter } from "../composables/useInputFilter"
|
|
33
40
|
import { useLabels } from "../composables/useLpConfig"
|
|
34
41
|
import { type Country, filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./countries"
|
|
35
42
|
import { OPTION_ITEM, POPOVER_PANEL } from "./dropdown"
|
|
43
|
+
import LpFieldNotice from "./LpFieldNotice.vue"
|
|
36
44
|
import LpIcon from "./LpIcon.vue"
|
|
45
|
+
import {
|
|
46
|
+
caretAfterDigits,
|
|
47
|
+
digitsBefore,
|
|
48
|
+
digitsOf,
|
|
49
|
+
formatPhone,
|
|
50
|
+
loadPhoneExamples,
|
|
51
|
+
loadPhoneLib,
|
|
52
|
+
type PhoneLib,
|
|
53
|
+
} from "./phone"
|
|
37
54
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
38
55
|
|
|
39
56
|
export interface PhoneDetail {
|
|
@@ -43,6 +60,17 @@ export interface PhoneDetail {
|
|
|
43
60
|
dialCode: string
|
|
44
61
|
/** National part (digits after the dial code). */
|
|
45
62
|
number: string
|
|
63
|
+
/** Normalised "+380501234567"; empty when there are no digits. */
|
|
64
|
+
e164: string
|
|
65
|
+
/** The length fits the country. False until the formatter has loaded. */
|
|
66
|
+
possible: boolean
|
|
67
|
+
/** A real number for the country. False until the formatter has loaded. */
|
|
68
|
+
valid: boolean
|
|
69
|
+
/**
|
|
70
|
+
* "MOBILE", "FIXED_LINE"… when known. The `min` metadata the field loads does
|
|
71
|
+
* not carry number types, so this stays undefined unless that changes.
|
|
72
|
+
*/
|
|
73
|
+
type?: string
|
|
46
74
|
}
|
|
47
75
|
|
|
48
76
|
const props = withDefaults(
|
|
@@ -53,6 +81,7 @@ const props = withDefaults(
|
|
|
53
81
|
only?: string[]
|
|
54
82
|
/** Default country (ISO2) used when the field is empty. */
|
|
55
83
|
defaultCountry?: string
|
|
84
|
+
/** Unset shows an example number for the active country. */
|
|
56
85
|
placeholder?: string
|
|
57
86
|
disabled?: boolean
|
|
58
87
|
invalid?: boolean
|
|
@@ -61,7 +90,7 @@ const props = withDefaults(
|
|
|
61
90
|
searchPlaceholder?: string
|
|
62
91
|
}>(),
|
|
63
92
|
{
|
|
64
|
-
placeholder:
|
|
93
|
+
placeholder: undefined,
|
|
65
94
|
size: "md",
|
|
66
95
|
emptyText: "No country",
|
|
67
96
|
searchPlaceholder: "Search country…",
|
|
@@ -106,13 +135,17 @@ const query = ref("")
|
|
|
106
135
|
|
|
107
136
|
const filtered = computed(() => filterCountries(countries.value, query.value))
|
|
108
137
|
|
|
138
|
+
const phoneLib = shallowRef<PhoneLib>()
|
|
139
|
+
|
|
109
140
|
function parse(value: string): PhoneDetail {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
141
|
+
const formatted = formatPhone(value, phoneLib.value)
|
|
142
|
+
const country =
|
|
143
|
+
(formatted.country && countries.value.find((c) => c.iso2 === formatted.country)) ??
|
|
144
|
+
matchCountryByValue(value, countries.value)
|
|
145
|
+
const digits = digitsOf(formatted.e164)
|
|
146
|
+
const base = { e164: formatted.e164, possible: formatted.possible, valid: formatted.valid, type: formatted.type }
|
|
147
|
+
if (country) return { ...base, country, dialCode: country.dial, number: digits.slice(country.dial.length) }
|
|
148
|
+
return { ...base, country: undefined, dialCode: "", number: digits }
|
|
116
149
|
}
|
|
117
150
|
|
|
118
151
|
function setValue(value: string) {
|
|
@@ -120,9 +153,85 @@ function setValue(value: string) {
|
|
|
120
153
|
emit("change", parse(value))
|
|
121
154
|
}
|
|
122
155
|
|
|
156
|
+
/*
|
|
157
|
+
* Notes float under the field: the LpFormField around it shows them when there
|
|
158
|
+
* is one, otherwise the field shows its own.
|
|
159
|
+
*/
|
|
160
|
+
const notice = useFieldNotice()
|
|
161
|
+
const outerField = useField()
|
|
162
|
+
const ownNotice = computed(() => !outerField?.value.notify)
|
|
163
|
+
function say(message: string, options?: FieldNoticeOptions) {
|
|
164
|
+
if (outerField?.value.notify) outerField.value.notify(message, options)
|
|
165
|
+
else notice.show(message, options)
|
|
166
|
+
}
|
|
167
|
+
function unsay() {
|
|
168
|
+
if (outerField?.value.clearNotice) outerField.value.clearNotice()
|
|
169
|
+
else notice.clear()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Shown invalid after the field was left with a number that cannot be right. */
|
|
173
|
+
const flagged = ref(false)
|
|
174
|
+
const countryName = (iso?: string) =>
|
|
175
|
+
(iso && countries.value.find((c) => c.iso2 === iso)?.name) || ""
|
|
176
|
+
|
|
177
|
+
/*
|
|
178
|
+
* Reformat after every edit and put the caret back after the same number of
|
|
179
|
+
* digits it followed, so spaces the formatter adds or moves never push it
|
|
180
|
+
* around.
|
|
181
|
+
*/
|
|
123
182
|
function onInput(e: Event) {
|
|
124
183
|
filterInput(e)
|
|
125
|
-
|
|
184
|
+
const el = e.target as HTMLInputElement
|
|
185
|
+
const before = digitsBefore(el.value, el.selectionStart ?? el.value.length)
|
|
186
|
+
const result = formatPhone(el.value, phoneLib.value)
|
|
187
|
+
if (el.value !== result.display) {
|
|
188
|
+
el.value = result.display
|
|
189
|
+
const caret = caretAfterDigits(result.display, Math.min(before, digitsOf(result.display).length))
|
|
190
|
+
el.setSelectionRange(caret, caret)
|
|
191
|
+
}
|
|
192
|
+
if (flagged.value) {
|
|
193
|
+
flagged.value = false
|
|
194
|
+
unsay()
|
|
195
|
+
}
|
|
196
|
+
if (result.dropped) {
|
|
197
|
+
const name = countryName(result.country)
|
|
198
|
+
say(
|
|
199
|
+
(name ? text.value.tooLongFor : text.value.tooLong)
|
|
200
|
+
.replace("{country}", name)
|
|
201
|
+
.replace("{max}", String(digitsOf(result.e164).length - (result.callingCode?.length ?? 0))),
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
setValue(result.display)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/*
|
|
208
|
+
* Backspace or Delete next to a space the formatter put there would only remove
|
|
209
|
+
* the space, and formatting would put it straight back: the key would seem
|
|
210
|
+
* dead. The digit beside it goes instead.
|
|
211
|
+
*/
|
|
212
|
+
function onKeyEdit(e: InputEvent) {
|
|
213
|
+
const el = e.target as HTMLInputElement
|
|
214
|
+
const start = el.selectionStart ?? 0
|
|
215
|
+
const end = el.selectionEnd ?? 0
|
|
216
|
+
const backward = e.inputType === "deleteContentBackward"
|
|
217
|
+
const forward = e.inputType === "deleteContentForward"
|
|
218
|
+
if ((!backward && !forward) || start !== end) return
|
|
219
|
+
const value = el.value
|
|
220
|
+
const at = backward ? start - 1 : start
|
|
221
|
+
if (at < 0 || at >= value.length || /\d/.test(value[at]!)) return
|
|
222
|
+
let i = at
|
|
223
|
+
while (i >= 0 && i < value.length && !/\d/.test(value[i]!)) i += backward ? -1 : 1
|
|
224
|
+
if (i < 0 || i >= value.length) return
|
|
225
|
+
e.preventDefault()
|
|
226
|
+
el.value = value.slice(0, i) + value.slice(i + 1)
|
|
227
|
+
const caret = backward ? i : start
|
|
228
|
+
el.setSelectionRange(caret, caret)
|
|
229
|
+
el.dispatchEvent(new Event("input", { bubbles: true }))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function onBeforeEdit(e: InputEvent) {
|
|
233
|
+
onKeyEdit(e)
|
|
234
|
+
if (!e.defaultPrevented) onBeforeInput(e)
|
|
126
235
|
}
|
|
127
236
|
|
|
128
237
|
/**
|
|
@@ -139,7 +248,31 @@ function chooseCountry(c: Country) {
|
|
|
139
248
|
}
|
|
140
249
|
|
|
141
250
|
onMounted(async () => {
|
|
142
|
-
|
|
251
|
+
const [list, phone] = await Promise.all([loadCountries(), loadPhoneLib().catch(() => undefined)])
|
|
252
|
+
allCountries.value = list
|
|
253
|
+
phoneLib.value = phone
|
|
254
|
+
// A value set before the formatter arrived is shown formatted from now on.
|
|
255
|
+
if (phone && props.modelValue) {
|
|
256
|
+
const display = formatPhone(props.modelValue, phone).display
|
|
257
|
+
if (display !== props.modelValue) setValue(display)
|
|
258
|
+
}
|
|
259
|
+
if (props.placeholder === undefined) examplesFor.value = await loadPhoneExamples().catch(() => undefined)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
/* An example number for the active country, when the consumer gave no text. */
|
|
263
|
+
const examplesFor = shallowRef<Awaited<ReturnType<typeof loadPhoneExamples>>>()
|
|
264
|
+
const shownPlaceholder = computed(() => {
|
|
265
|
+
if (props.placeholder !== undefined) return props.placeholder
|
|
266
|
+
const phone = phoneLib.value
|
|
267
|
+
const iso = activeCountry.value?.iso2
|
|
268
|
+
if (phone && examplesFor.value && iso) {
|
|
269
|
+
try {
|
|
270
|
+
return phone.getExampleNumber(iso as never, examplesFor.value)?.formatInternational() ?? text.value.placeholder
|
|
271
|
+
} catch {
|
|
272
|
+
return text.value.placeholder
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return text.value.placeholder
|
|
143
276
|
})
|
|
144
277
|
|
|
145
278
|
// The default country's "+code" is put in place when the user enters an empty
|
|
@@ -151,7 +284,21 @@ function onFocus() {
|
|
|
151
284
|
if (!props.modelValue && seeded.value) setValue(seeded.value)
|
|
152
285
|
}
|
|
153
286
|
function onBlur() {
|
|
154
|
-
if (seeded.value && props.modelValue?.trim() === seeded.value.trim())
|
|
287
|
+
if (seeded.value && props.modelValue?.trim() === seeded.value.trim()) {
|
|
288
|
+
setValue("")
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
const result = phoneLib.value && props.modelValue ? formatPhone(props.modelValue, phoneLib.value) : undefined
|
|
292
|
+
if (!result || result.valid || digitsOf(result.e164).length <= (activeCountry.value?.dial.length ?? 0)) return
|
|
293
|
+
flagged.value = true
|
|
294
|
+
const name = countryName(result.country) || activeCountry.value?.name || ""
|
|
295
|
+
const reason =
|
|
296
|
+
result.problem === "too-short"
|
|
297
|
+
? (name ? text.value.tooShortFor : text.value.tooShort)
|
|
298
|
+
: result.problem === "invalid-country"
|
|
299
|
+
? text.value.unknownCountry
|
|
300
|
+
: text.value.invalidNumber
|
|
301
|
+
say(reason.replace("{country}", name), { tone: "danger", duration: 0 })
|
|
155
302
|
}
|
|
156
303
|
|
|
157
304
|
const sizeClass = {
|
|
@@ -168,13 +315,23 @@ const attrs = useAttrs()
|
|
|
168
315
|
const field = useFieldControl(() => ({
|
|
169
316
|
wrapped: false,
|
|
170
317
|
id: typeof attrs.id === "string" ? attrs.id : undefined,
|
|
171
|
-
invalid: props.invalid,
|
|
318
|
+
invalid: props.invalid || flagged.value,
|
|
172
319
|
disabled: props.disabled,
|
|
173
320
|
}))
|
|
174
|
-
const text = useLabels("phoneInput", {
|
|
321
|
+
const text = useLabels("phoneInput", {
|
|
322
|
+
selectCountry: "Select country",
|
|
323
|
+
placeholder: "Phone number",
|
|
324
|
+
tooLong: "A phone number has at most {max} digits",
|
|
325
|
+
tooLongFor: "Numbers in {country} have at most {max} digits after the code",
|
|
326
|
+
tooShort: "This number is too short",
|
|
327
|
+
tooShortFor: "Too short for {country}",
|
|
328
|
+
invalidNumber: "Not a valid phone number",
|
|
329
|
+
unknownCountry: "Unknown country code",
|
|
330
|
+
})
|
|
331
|
+
const noticeId = `${field.value.id ?? "lp-phone"}-notice`
|
|
175
332
|
const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
176
333
|
[
|
|
177
|
-
"flex w-full items-stretch rounded-control border bg-surface-soft text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring",
|
|
334
|
+
"relative flex w-full items-stretch rounded-control border bg-surface-soft text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring",
|
|
178
335
|
sizeClass[props.size],
|
|
179
336
|
field.value.invalid
|
|
180
337
|
? "border-danger focus-within:border-danger focus-within:ring-danger-soft"
|
|
@@ -224,6 +381,7 @@ const inputAttrs = computed(() => {
|
|
|
224
381
|
|
|
225
382
|
<ComboboxPortal>
|
|
226
383
|
<ComboboxContent
|
|
384
|
+
:collision-padding="8"
|
|
227
385
|
position="popper"
|
|
228
386
|
align="start"
|
|
229
387
|
:side-offset="6"
|
|
@@ -272,18 +430,19 @@ const inputAttrs = computed(() => {
|
|
|
272
430
|
v-bind="inputAttrs"
|
|
273
431
|
:id="field.id"
|
|
274
432
|
:value="modelValue"
|
|
275
|
-
:placeholder="
|
|
433
|
+
:placeholder="shownPlaceholder"
|
|
276
434
|
:disabled="field.disabled"
|
|
277
435
|
:required="field.required || undefined"
|
|
278
436
|
:aria-invalid="field.invalid || undefined"
|
|
279
|
-
:aria-describedby="field.describedBy"
|
|
437
|
+
:aria-describedby="[field.describedBy, notice.message.value ? noticeId : ''].filter(Boolean).join(' ') || undefined"
|
|
280
438
|
class="lp-autofill min-w-0 flex-1 self-stretch bg-transparent px-3 outline-none placeholder:text-muted"
|
|
281
|
-
@beforeinput="
|
|
439
|
+
@beforeinput="onBeforeEdit"
|
|
282
440
|
@paste="onPaste"
|
|
283
441
|
@compositionend="onCompositionEnd"
|
|
284
442
|
@input="onInput"
|
|
285
443
|
@focus="onFocus"
|
|
286
444
|
@blur="onBlur"
|
|
287
445
|
/>
|
|
446
|
+
<LpFieldNotice v-if="ownNotice" :notice="notice" :id="noticeId" />
|
|
288
447
|
</div>
|
|
289
448
|
</template>
|
package/src/components/LpPie.vue
CHANGED
|
@@ -697,16 +697,19 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
697
697
|
</span>
|
|
698
698
|
</LpShift>
|
|
699
699
|
</template>
|
|
700
|
+
<!-- The canvas keeps its natural size where there is room and scales
|
|
701
|
+
down as a whole where there is not (a phone, a narrow column):
|
|
702
|
+
the callout labels widen the box well past the circle, and at a
|
|
703
|
+
fixed width they ran off the screen. -->
|
|
700
704
|
<div
|
|
701
|
-
class="relative shrink-0"
|
|
702
|
-
:style="{ width: `${canvasWidth}px`,
|
|
705
|
+
class="relative max-w-full shrink-0 max-sm:mx-auto"
|
|
706
|
+
:style="{ width: `${canvasWidth}px`, aspectRatio: `${canvasWidth} / ${canvasHeight}` }"
|
|
703
707
|
>
|
|
704
708
|
<!-- Clicking the empty middle (or the gaps) clears, so a selection can
|
|
705
709
|
be dropped without hunting for somewhere outside the chart. -->
|
|
706
710
|
<svg
|
|
707
|
-
:
|
|
708
|
-
|
|
709
|
-
class="touch-none select-none"
|
|
711
|
+
:viewBox="`0 0 ${canvasWidth} ${canvasHeight}`"
|
|
712
|
+
class="block size-full touch-none select-none"
|
|
710
713
|
role="img"
|
|
711
714
|
:aria-label="summary"
|
|
712
715
|
@pointerleave="leaveChart"
|