@leavepulse/ui 0.44.0 → 0.45.1
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-HzAA4gri.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-CJOYGZlI.js} +7 -7
- package/dist/chunks/{LpAlert-DkOIjsbq.js → LpAlert-B6ilrM7T.js} +1 -1
- package/dist/chunks/{LpAppShell-BUKPcHco.js → LpAppShell-B07Lf48e.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-BTVAR4tF.js} +3 -3
- package/dist/chunks/{LpConfirmHost-CDm0Wtpv.js → LpConfirmHost-Ci_uCHgQ.js} +1 -1
- package/dist/chunks/{LpContextMenu-DQw5dGfG.js → LpContextMenu-BqkAYa9X.js} +2 -1
- package/dist/chunks/{LpDatePicker-Drirx1AK.js → LpDatePicker-BrdFtSTy.js} +16 -10
- package/dist/chunks/LpDateRangePicker-Drd7EbIz.js +210 -0
- package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
- package/dist/chunks/{LpDrawer-bScx-KMO.js → LpDrawer-CfRhHwmE.js} +3 -3
- 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-BzTVeNqe.js} +3 -3
- package/dist/chunks/{LpModalNav-DPg5H9f3.js → LpModalNav-CjABSGCu.js} +14 -8
- 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-Bwws_X29.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-DTtWuKvq.js} +76 -9
- 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 +62 -11
- 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.d.ts +2 -0
- 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/useLocale.d.ts +2 -0
- package/dist/composables/useLocale.js +9 -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 +82 -8
- package/src/components/LpContextMenu.vue +1 -1
- package/src/components/LpDatePicker.vue +11 -4
- package/src/components/LpDateRangePicker.vue +226 -0
- package/src/components/LpDrawer.vue +1 -1
- 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 +2 -2
- package/src/components/LpModalNav.vue +13 -2
- 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 +91 -5
- 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/useLocale.ts +14 -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 +53 -4
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { type MaybeRefOrGetter } from "vue";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/** One key of a sequence: a bare key ("g") or a full chord. */
|
|
3
|
+
export interface HotkeyStep {
|
|
4
4
|
key: string;
|
|
5
|
+
code?: string;
|
|
6
|
+
ctrl?: boolean;
|
|
7
|
+
shift?: boolean;
|
|
8
|
+
alt?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface HotkeyBinding {
|
|
11
|
+
/**
|
|
12
|
+
* Layout-produced key, compared case-insensitively ("l", "f2", "escape").
|
|
13
|
+
* Required unless the binding is a `sequence`.
|
|
14
|
+
*/
|
|
15
|
+
key?: string;
|
|
5
16
|
/**
|
|
6
17
|
* Physical key position ("KeyL", "F2", "Digit1"). Matches on its own, which
|
|
7
18
|
* is what makes non-Latin layouts work.
|
|
@@ -11,6 +22,16 @@ export interface HotkeyBinding {
|
|
|
11
22
|
ctrl?: boolean;
|
|
12
23
|
shift?: boolean;
|
|
13
24
|
alt?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Keys pressed one after another: `"g i"`, or steps for chords and physical
|
|
27
|
+
* positions (`[{ key: "g", code: "KeyG" }, { key: "i", code: "KeyI" }]`).
|
|
28
|
+
* The string form splits on spaces, so a Space step needs the array form.
|
|
29
|
+
*/
|
|
30
|
+
sequence?: string | (string | HotkeyStep)[];
|
|
31
|
+
/** How long a sequence waits for its next key, in ms. Default 1000. */
|
|
32
|
+
timeout?: number;
|
|
33
|
+
/** Extra condition checked on every press; `false` leaves the key alone. */
|
|
34
|
+
when?: (event: KeyboardEvent) => boolean;
|
|
14
35
|
/**
|
|
15
36
|
* Runs even while a text field has focus. Only for shortcuts that mean
|
|
16
37
|
* something there — Escape, and the palette the user reaches from anywhere.
|
|
@@ -48,14 +69,19 @@ export interface HotkeyHelp {
|
|
|
48
69
|
/**
|
|
49
70
|
* Written the way it reads, "+"-separated. Defaults to one built from the
|
|
50
71
|
* binding; set it where the binding is not the whole story, such as the
|
|
51
|
-
*
|
|
72
|
+
* key that needs Shift to type ("?"). Sequences read with spaces: "G I".
|
|
52
73
|
*/
|
|
53
74
|
combo?: string;
|
|
54
75
|
}
|
|
55
76
|
export interface ShortcutEntry {
|
|
56
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Rendered one <kbd> per "+"-separated key, so write it the way it reads.
|
|
79
|
+
* Spaces separate the steps of a sequence ("G I", "Ctrl+K Ctrl+S").
|
|
80
|
+
*/
|
|
57
81
|
combo: string;
|
|
58
82
|
label: string;
|
|
83
|
+
/** Another mounted binding answers the same keys at the same priority. */
|
|
84
|
+
conflict?: boolean;
|
|
59
85
|
}
|
|
60
86
|
export interface ShortcutGroup {
|
|
61
87
|
title: string;
|
|
@@ -77,7 +103,15 @@ export declare function useAppleKeyboard(): import("vue").Ref<boolean, boolean>;
|
|
|
77
103
|
* Meta as one modifier, so only the label may differ — never the binding.
|
|
78
104
|
*/
|
|
79
105
|
export declare function appleModifierLabel(key: string, apple?: boolean): string;
|
|
80
|
-
|
|
106
|
+
type KeyLike = Pick<KeyboardEvent, "key" | "code" | "ctrlKey" | "metaKey" | "shiftKey" | "altKey">;
|
|
107
|
+
export declare function matchesHotkey(event: KeyLike, binding: HotkeyBinding | HotkeyStep): boolean;
|
|
108
|
+
/** The keys a binding waits for, in order: one step for a plain binding. */
|
|
109
|
+
export declare function hotkeySteps(binding: HotkeyBinding): HotkeyStep[];
|
|
110
|
+
/**
|
|
111
|
+
* Where a run of presses stands against a binding: "full" when they spell it
|
|
112
|
+
* out, "prefix" when more keys would, otherwise null.
|
|
113
|
+
*/
|
|
114
|
+
export declare function sequenceProgress(presses: KeyLike[], binding: HotkeyBinding): "full" | "prefix" | null;
|
|
81
115
|
/**
|
|
82
116
|
* Bind a list of hotkeys for as long as the calling component is mounted.
|
|
83
117
|
*
|
|
@@ -102,7 +136,32 @@ export interface UseHotkeysOptions {
|
|
|
102
136
|
*/
|
|
103
137
|
scope?: MaybeRefOrGetter<HTMLElement | null | undefined>;
|
|
104
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* The keys of a sequence typed so far ("G" after the first key of "g i"),
|
|
141
|
+
* empty when nothing is pending. For a "G…" hint while the next key is awaited.
|
|
142
|
+
*/
|
|
143
|
+
export declare const pendingHotkey: import("vue").ComputedRef<string[]>;
|
|
105
144
|
export declare function useHotkeys(bindings: MaybeRefOrGetter<HotkeyBinding[]>, options?: UseHotkeysOptions): void;
|
|
145
|
+
export interface HotkeyConflict {
|
|
146
|
+
/** The keys both answer, written as in the shortcut reference. */
|
|
147
|
+
combo: string;
|
|
148
|
+
/** `help` labels of the clashing bindings; unlabelled ones are omitted. */
|
|
149
|
+
labels: string[];
|
|
150
|
+
priority: number;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Pure part of `hotkeyConflicts`: bindings from different `useHotkeys` calls
|
|
154
|
+
* answering the same keys at the same priority within the same scope. Within
|
|
155
|
+
* one call a repeat is deliberate (the first may decline), and a binding with
|
|
156
|
+
* `when` is conditional by design, so neither counts.
|
|
157
|
+
*/
|
|
158
|
+
export declare function findHotkeyConflicts(groups: {
|
|
159
|
+
bindings: HotkeyBinding[];
|
|
160
|
+
priority: number;
|
|
161
|
+
scope: unknown;
|
|
162
|
+
}[]): HotkeyConflict[];
|
|
163
|
+
/** Bindings among the mounted ones that answer the same keys. */
|
|
164
|
+
export declare const hotkeyConflicts: import("vue").ComputedRef<HotkeyConflict[]>;
|
|
106
165
|
/**
|
|
107
166
|
* The shortcuts that work right now, grouped for `LpShortcutsDialog`: every
|
|
108
167
|
* binding with `help` from every mounted `useHotkeys`. A screen that binds its
|
|
@@ -115,3 +174,4 @@ export declare const activeShortcuts: import("vue").ComputedRef<ShortcutGroup[]>
|
|
|
115
174
|
* when it lists both its own entries and the live ones.
|
|
116
175
|
*/
|
|
117
176
|
export declare function mergeShortcutGroups(...lists: ShortcutGroup[][]): ShortcutGroup[];
|
|
177
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { shallowReactive, computed, toValue, getCurrentInstance, onMounted, onBeforeUnmount
|
|
1
|
+
import { shallowReactive, ref, computed, toValue, getCurrentInstance, onMounted, onBeforeUnmount } from "vue";
|
|
2
2
|
import { topModalElement } from "../components/modalDepth.js";
|
|
3
3
|
const isAppleKeyboard = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform || "");
|
|
4
4
|
function useAppleKeyboard() {
|
|
@@ -32,6 +32,7 @@ function isTextEntry(target) {
|
|
|
32
32
|
}
|
|
33
33
|
const MODIFIER_KEYS = /* @__PURE__ */ new Set(["control", "meta", "shift", "alt"]);
|
|
34
34
|
function matchesHotkey(event, binding) {
|
|
35
|
+
if (binding.key === void 0) return false;
|
|
35
36
|
const key = binding.key.toLowerCase();
|
|
36
37
|
const ctrlHeld = event.ctrlKey || event.metaKey;
|
|
37
38
|
const wantsCtrl = Boolean(binding.ctrl) || key === "control" || key === "meta";
|
|
@@ -43,6 +44,24 @@ function matchesHotkey(event, binding) {
|
|
|
43
44
|
if (MODIFIER_KEYS.has(key)) return event.key.toLowerCase() === key;
|
|
44
45
|
return event.key.toLowerCase() === key || binding.code !== void 0 && event.code === binding.code;
|
|
45
46
|
}
|
|
47
|
+
function hotkeySteps(binding) {
|
|
48
|
+
const raw = binding.sequence;
|
|
49
|
+
if (raw === void 0) {
|
|
50
|
+
if (binding.key === void 0) return [];
|
|
51
|
+
const { key, code, ctrl, shift, alt } = binding;
|
|
52
|
+
return [{ key, code, ctrl, shift, alt }];
|
|
53
|
+
}
|
|
54
|
+
const list = typeof raw === "string" ? raw.trim().split(/\s+/) : raw;
|
|
55
|
+
return list.map((step) => typeof step === "string" ? { key: step } : step);
|
|
56
|
+
}
|
|
57
|
+
function sequenceProgress(presses, binding) {
|
|
58
|
+
const steps = hotkeySteps(binding);
|
|
59
|
+
if (!steps.length || presses.length > steps.length) return null;
|
|
60
|
+
for (let i = 0; i < presses.length; i++) {
|
|
61
|
+
if (!matchesHotkey(presses[i], steps[i])) return null;
|
|
62
|
+
}
|
|
63
|
+
return presses.length === steps.length ? "full" : "prefix";
|
|
64
|
+
}
|
|
46
65
|
const STATE_KEY = /* @__PURE__ */ Symbol.for("@leavepulse/ui:hotkeys");
|
|
47
66
|
const g = globalThis;
|
|
48
67
|
const state = g[STATE_KEY] ??= {
|
|
@@ -66,13 +85,23 @@ function insideOf(entry, layer) {
|
|
|
66
85
|
const node = scope ?? entry.host();
|
|
67
86
|
return !!node && layer.contains(node);
|
|
68
87
|
}
|
|
69
|
-
|
|
70
|
-
|
|
88
|
+
const DEFAULT_TIMEOUT = 1e3;
|
|
89
|
+
const pressed = [];
|
|
90
|
+
let held = [];
|
|
91
|
+
let holdTimer;
|
|
92
|
+
const pendingKeys = ref([]);
|
|
93
|
+
const pendingHotkey = computed(() => pendingKeys.value);
|
|
94
|
+
function snapshot(event) {
|
|
95
|
+
const { key, code, ctrlKey, metaKey, shiftKey, altKey } = event;
|
|
96
|
+
return { key, code, ctrlKey, metaKey, shiftKey, altKey };
|
|
97
|
+
}
|
|
98
|
+
function eligible(event) {
|
|
71
99
|
const inTextEntry = isTextEntry(event.target);
|
|
72
100
|
const blocker = event.key === "Escape" ? topLayer() : topModalElement();
|
|
73
101
|
const entries = [...mounted].sort(
|
|
74
102
|
(a, b) => Number(toValue(b.priority) || 0) - Number(toValue(a.priority) || 0) || b.order - a.order
|
|
75
103
|
);
|
|
104
|
+
const out = [];
|
|
76
105
|
for (const entry of entries) {
|
|
77
106
|
if (toValue(entry.enabled) === false || !inScope(event.target, entry.scope)) continue;
|
|
78
107
|
const covered = !!blocker && !insideOf(entry, blocker);
|
|
@@ -80,12 +109,73 @@ function onKeydown(event) {
|
|
|
80
109
|
if (inTextEntry && !binding.allowInInput) continue;
|
|
81
110
|
if (event.repeat && binding.repeat === false) continue;
|
|
82
111
|
if (covered && !binding.overLayers) continue;
|
|
83
|
-
if (!
|
|
84
|
-
|
|
112
|
+
if (binding.when && !binding.when(event)) continue;
|
|
113
|
+
out.push(binding);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
function run(candidates) {
|
|
119
|
+
for (const { binding, event } of candidates) {
|
|
120
|
+
if (binding.handler(event) !== false) return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
function resetSequence() {
|
|
125
|
+
clearTimeout(holdTimer);
|
|
126
|
+
holdTimer = void 0;
|
|
127
|
+
pressed.length = 0;
|
|
128
|
+
held = [];
|
|
129
|
+
pendingKeys.value = [];
|
|
130
|
+
}
|
|
131
|
+
function flushHeld() {
|
|
132
|
+
const waiting = held;
|
|
133
|
+
resetSequence();
|
|
134
|
+
run(waiting);
|
|
135
|
+
}
|
|
136
|
+
function onKeydown(event) {
|
|
137
|
+
if (event.isComposing || event.defaultPrevented) return;
|
|
138
|
+
if (pressed.length && MODIFIER_KEYS.has(event.key.toLowerCase())) return;
|
|
139
|
+
if (pressed.length && event.key === "Escape") {
|
|
140
|
+
resetSequence();
|
|
141
|
+
}
|
|
142
|
+
const presses = [...pressed, snapshot(event)];
|
|
143
|
+
const bindings = eligible(event);
|
|
144
|
+
const full = [];
|
|
145
|
+
let waitFor = 0;
|
|
146
|
+
for (const binding of bindings) {
|
|
147
|
+
const progress = sequenceProgress(presses, binding);
|
|
148
|
+
if (progress === "full") full.push({ binding, event });
|
|
149
|
+
else if (progress === "prefix") waitFor = Math.max(waitFor, binding.timeout ?? DEFAULT_TIMEOUT);
|
|
150
|
+
}
|
|
151
|
+
if (waitFor) {
|
|
152
|
+
clearTimeout(holdTimer);
|
|
153
|
+
pressed.push(snapshot(event));
|
|
154
|
+
held = full;
|
|
155
|
+
pendingKeys.value = pressed.map(pressLabel);
|
|
156
|
+
holdTimer = setTimeout(flushHeld, waitFor);
|
|
157
|
+
event.preventDefault();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (full.length) {
|
|
161
|
+
resetSequence();
|
|
162
|
+
if (run(full)) {
|
|
85
163
|
event.preventDefault();
|
|
86
164
|
return;
|
|
87
165
|
}
|
|
88
166
|
}
|
|
167
|
+
if (pressed.length) {
|
|
168
|
+
flushHeld();
|
|
169
|
+
onKeydown(event);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function pressLabel(press) {
|
|
173
|
+
return chordOf({
|
|
174
|
+
key: press.key,
|
|
175
|
+
ctrl: press.ctrlKey || press.metaKey,
|
|
176
|
+
alt: press.altKey,
|
|
177
|
+
shift: press.shiftKey && press.key.length !== 1
|
|
178
|
+
});
|
|
89
179
|
}
|
|
90
180
|
function useHotkeys(bindings, options = {}) {
|
|
91
181
|
const instance = getCurrentInstance();
|
|
@@ -102,8 +192,10 @@ function useHotkeys(bindings, options = {}) {
|
|
|
102
192
|
};
|
|
103
193
|
onMounted(() => {
|
|
104
194
|
mounted.add(entry);
|
|
195
|
+
warnConflicts();
|
|
105
196
|
if (!state.listening) {
|
|
106
197
|
window.addEventListener("keydown", onKeydown);
|
|
198
|
+
window.addEventListener("blur", resetSequence);
|
|
107
199
|
state.listening = true;
|
|
108
200
|
}
|
|
109
201
|
});
|
|
@@ -111,7 +203,9 @@ function useHotkeys(bindings, options = {}) {
|
|
|
111
203
|
mounted.delete(entry);
|
|
112
204
|
if (!mounted.size && state.listening) {
|
|
113
205
|
window.removeEventListener("keydown", onKeydown);
|
|
206
|
+
window.removeEventListener("blur", resetSequence);
|
|
114
207
|
state.listening = false;
|
|
208
|
+
resetSequence();
|
|
115
209
|
}
|
|
116
210
|
});
|
|
117
211
|
}
|
|
@@ -127,20 +221,82 @@ const KEY_NAMES = {
|
|
|
127
221
|
arrowleft: "←",
|
|
128
222
|
arrowright: "→"
|
|
129
223
|
};
|
|
224
|
+
function chordOf(step) {
|
|
225
|
+
const key = step.key.toLowerCase();
|
|
226
|
+
const name = KEY_NAMES[key] ?? (key.length === 1 || /^f\d+$/.test(key) ? key.toUpperCase() : step.key);
|
|
227
|
+
return [step.ctrl && "Ctrl", step.alt && "Alt", step.shift && "Shift", name].filter(Boolean).join("+");
|
|
228
|
+
}
|
|
130
229
|
function comboOf(binding) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
230
|
+
return hotkeySteps(binding).map(chordOf).join(" ");
|
|
231
|
+
}
|
|
232
|
+
function signatureOf(binding) {
|
|
233
|
+
return hotkeySteps(binding).map((step) => {
|
|
234
|
+
const key = step.key.toLowerCase();
|
|
235
|
+
const symbol = key.length === 1 && key === key.toUpperCase();
|
|
236
|
+
const shift = step.shift === void 0 && symbol ? "*" : String(Boolean(step.shift));
|
|
237
|
+
return `${Boolean(step.ctrl)}:${Boolean(step.alt)}:${shift}:${key}`;
|
|
238
|
+
}).join(" ");
|
|
239
|
+
}
|
|
240
|
+
function findHotkeyConflicts(groups) {
|
|
241
|
+
const scopeIds = /* @__PURE__ */ new Map();
|
|
242
|
+
const seen = /* @__PURE__ */ new Map();
|
|
243
|
+
groups.forEach((group, owner) => {
|
|
244
|
+
if (!scopeIds.has(group.scope)) scopeIds.set(group.scope, scopeIds.size);
|
|
245
|
+
for (const binding of group.bindings) {
|
|
246
|
+
if (binding.when || !hotkeySteps(binding).length) continue;
|
|
247
|
+
const id = `${group.priority}|${scopeIds.get(group.scope)}|${signatureOf(binding)}`;
|
|
248
|
+
const slot = seen.get(id) ?? {
|
|
249
|
+
combo: binding.help?.combo ?? comboOf(binding),
|
|
250
|
+
labels: [],
|
|
251
|
+
owners: /* @__PURE__ */ new Set(),
|
|
252
|
+
priority: group.priority
|
|
253
|
+
};
|
|
254
|
+
slot.owners.add(owner);
|
|
255
|
+
if (binding.help && !slot.labels.includes(binding.help.label)) slot.labels.push(binding.help.label);
|
|
256
|
+
seen.set(id, slot);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return [...seen.values()].filter((slot) => slot.owners.size > 1).map(({ combo, labels, priority }) => ({ combo, labels, priority }));
|
|
260
|
+
}
|
|
261
|
+
const hotkeyConflicts = computed(
|
|
262
|
+
() => findHotkeyConflicts(
|
|
263
|
+
[...mounted].filter((entry) => toValue(entry.enabled) !== false).map((entry) => ({
|
|
264
|
+
bindings: entry.read(),
|
|
265
|
+
priority: Number(toValue(entry.priority) || 0),
|
|
266
|
+
scope: toValue(entry.scope) ?? null
|
|
267
|
+
}))
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
const warned = /* @__PURE__ */ new Set();
|
|
271
|
+
function isDev() {
|
|
272
|
+
try {
|
|
273
|
+
return process.env.NODE_ENV !== "production";
|
|
274
|
+
} catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function warnConflicts() {
|
|
279
|
+
if (!isDev()) return;
|
|
280
|
+
for (const conflict of hotkeyConflicts.value) {
|
|
281
|
+
const id = `${conflict.priority}|${conflict.combo}`;
|
|
282
|
+
if (warned.has(id)) continue;
|
|
283
|
+
warned.add(id);
|
|
284
|
+
console.warn(
|
|
285
|
+
`[@leavepulse/ui] ${conflict.combo} is bound more than once at priority ${conflict.priority}` + (conflict.labels.length ? ` (${conflict.labels.join(", ")})` : "") + "; only the most recently mounted one runs unless it declines."
|
|
286
|
+
);
|
|
287
|
+
}
|
|
134
288
|
}
|
|
135
289
|
const activeShortcuts = computed(() => {
|
|
136
290
|
const groups = /* @__PURE__ */ new Map();
|
|
291
|
+
const clashing = new Set(hotkeyConflicts.value.map((c) => c.combo));
|
|
137
292
|
for (const entry of mounted) {
|
|
138
293
|
if (toValue(entry.enabled) === false) continue;
|
|
139
294
|
for (const binding of entry.read()) {
|
|
140
295
|
if (!binding.help) continue;
|
|
141
296
|
const combo = binding.help.combo ?? comboOf(binding);
|
|
142
297
|
const entries = groups.get(binding.help.group) ?? [];
|
|
143
|
-
|
|
298
|
+
const conflict = clashing.has(combo) || void 0;
|
|
299
|
+
if (!entries.some((e) => e.combo === combo)) entries.push({ combo, label: binding.help.label, conflict });
|
|
144
300
|
groups.set(binding.help.group, entries);
|
|
145
301
|
}
|
|
146
302
|
}
|
|
@@ -162,9 +318,14 @@ function mergeShortcutGroups(...lists) {
|
|
|
162
318
|
export {
|
|
163
319
|
activeShortcuts,
|
|
164
320
|
appleModifierLabel,
|
|
321
|
+
findHotkeyConflicts,
|
|
322
|
+
hotkeyConflicts,
|
|
323
|
+
hotkeySteps,
|
|
165
324
|
isAppleKeyboard,
|
|
166
325
|
matchesHotkey,
|
|
167
326
|
mergeShortcutGroups,
|
|
327
|
+
pendingHotkey,
|
|
328
|
+
sequenceProgress,
|
|
168
329
|
useAppleKeyboard,
|
|
169
330
|
useHotkeys
|
|
170
331
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from "vue";
|
|
2
|
+
export type MaskToken = RegExp | {
|
|
3
|
+
pattern: RegExp;
|
|
4
|
+
transform?: (ch: string) => string;
|
|
5
|
+
};
|
|
6
|
+
export type MaskTokens = Record<string, MaskToken>;
|
|
7
|
+
export type MaskSpec = string | readonly string[] | ((raw: string) => string);
|
|
8
|
+
export declare const DEFAULT_MASK_TOKENS: MaskTokens;
|
|
9
|
+
type Slot = {
|
|
10
|
+
literal: string;
|
|
11
|
+
} | {
|
|
12
|
+
test: (ch: string) => boolean;
|
|
13
|
+
transform: (ch: string) => string;
|
|
14
|
+
};
|
|
15
|
+
export interface MaskResult {
|
|
16
|
+
masked: string;
|
|
17
|
+
raw: string;
|
|
18
|
+
/** Caret offset in `masked`. */
|
|
19
|
+
caret: number;
|
|
20
|
+
/** Every token of the mask is filled. */
|
|
21
|
+
complete: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface MaskOptions {
|
|
24
|
+
mask: MaskSpec;
|
|
25
|
+
tokens?: MaskTokens;
|
|
26
|
+
/**
|
|
27
|
+
* Caret position: an offset into `input`, or with `fromRaw` the number of raw
|
|
28
|
+
* characters before it. Defaults to the end.
|
|
29
|
+
*/
|
|
30
|
+
caret?: number;
|
|
31
|
+
/** `input` holds raw characters only; literals in it are never consumed as mask literals. */
|
|
32
|
+
fromRaw?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function parseMask(mask: string, tokens?: MaskTokens): Slot[];
|
|
35
|
+
/** Format `input` through a mask and map the caret into the result. */
|
|
36
|
+
export declare function applyMask(input: string, options: MaskOptions): MaskResult;
|
|
37
|
+
/**
|
|
38
|
+
* The value after deleting one accepted character next to a collapsed caret,
|
|
39
|
+
* skipping over literals, or null when there is nothing to delete that way.
|
|
40
|
+
*/
|
|
41
|
+
export declare function maskDelete(value: string, caret: number, direction: "backward" | "forward", options: Omit<MaskOptions, "caret" | "fromRaw">): MaskResult | null;
|
|
42
|
+
export interface UseInputMaskOptions {
|
|
43
|
+
mask: MaybeRefOrGetter<MaskSpec | undefined>;
|
|
44
|
+
tokens?: MaybeRefOrGetter<MaskTokens | undefined>;
|
|
45
|
+
/** Called with every value the user produces. */
|
|
46
|
+
onChange?: (masked: string, raw: string, complete: boolean) => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Bind `onBeforeInput`, `onInput` and `onCompositionEnd` to the input, and show
|
|
50
|
+
* `format(model).masked` as its value.
|
|
51
|
+
*/
|
|
52
|
+
export declare function useInputMask(options: UseInputMaskOptions): {
|
|
53
|
+
active: import("vue").ComputedRef<boolean>;
|
|
54
|
+
format: (value: string | null | undefined) => MaskResult;
|
|
55
|
+
onBeforeInput: (event: InputEvent) => void;
|
|
56
|
+
onInput: (event: Event) => void;
|
|
57
|
+
onCompositionEnd: (event: CompositionEvent) => void;
|
|
58
|
+
};
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
|
+
const DEFAULT_MASK_TOKENS = {
|
|
3
|
+
"0": /\d/,
|
|
4
|
+
a: new RegExp("\\p{L}", "u"),
|
|
5
|
+
A: { pattern: new RegExp("\\p{L}", "u"), transform: (ch) => ch.toLocaleUpperCase() },
|
|
6
|
+
"*": /[\p{L}\d]/u
|
|
7
|
+
};
|
|
8
|
+
function tester(token) {
|
|
9
|
+
const pattern = token instanceof RegExp ? token : token.pattern;
|
|
10
|
+
const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
|
|
11
|
+
const transform = token instanceof RegExp ? void 0 : token.transform;
|
|
12
|
+
return { test: (ch) => re.test(ch), transform: transform ?? ((ch) => ch) };
|
|
13
|
+
}
|
|
14
|
+
function parseMask(mask, tokens = DEFAULT_MASK_TOKENS) {
|
|
15
|
+
const slots = [];
|
|
16
|
+
const chars = Array.from(mask);
|
|
17
|
+
for (let i = 0; i < chars.length; i++) {
|
|
18
|
+
const ch = chars[i];
|
|
19
|
+
if (ch === "\\" && i + 1 < chars.length) {
|
|
20
|
+
slots.push({ literal: chars[++i] });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const token = tokens[ch];
|
|
24
|
+
slots.push(token ? tester(token) : { literal: ch });
|
|
25
|
+
}
|
|
26
|
+
return slots;
|
|
27
|
+
}
|
|
28
|
+
function run(slots, input, caret, fromRaw) {
|
|
29
|
+
const tokenStarts = [];
|
|
30
|
+
const tokenEnds = [];
|
|
31
|
+
const tokenOffsets = [];
|
|
32
|
+
let out = "";
|
|
33
|
+
let raw = "";
|
|
34
|
+
let pending = "";
|
|
35
|
+
let si = 0;
|
|
36
|
+
let offset = 0;
|
|
37
|
+
let before = 0;
|
|
38
|
+
let overflow = false;
|
|
39
|
+
for (const ch of input) {
|
|
40
|
+
const at2 = offset;
|
|
41
|
+
offset += ch.length;
|
|
42
|
+
if (si >= slots.length) {
|
|
43
|
+
if (slots.some((s) => "test" in s && s.test(ch))) overflow = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
while (si < slots.length) {
|
|
47
|
+
const slot = slots[si];
|
|
48
|
+
if ("literal" in slot) {
|
|
49
|
+
pending += slot.literal;
|
|
50
|
+
si++;
|
|
51
|
+
if (!fromRaw && slot.literal === ch) break;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (slot.test(ch)) {
|
|
55
|
+
const value = slot.transform(ch);
|
|
56
|
+
out += pending;
|
|
57
|
+
pending = "";
|
|
58
|
+
tokenStarts.push(out.length);
|
|
59
|
+
out += value;
|
|
60
|
+
tokenEnds.push(out.length);
|
|
61
|
+
raw += value;
|
|
62
|
+
tokenOffsets.push(at2);
|
|
63
|
+
si++;
|
|
64
|
+
if (fromRaw ? raw.length <= caret : at2 < caret) before++;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const rest = slots.slice(si);
|
|
70
|
+
const complete = !rest.some((s) => "test" in s);
|
|
71
|
+
if (complete && out) out += pending + rest.map((s) => "literal" in s ? s.literal : "").join("");
|
|
72
|
+
const at = before === 0 ? tokenStarts[0] ?? 0 : tokenEnds[before - 1];
|
|
73
|
+
return {
|
|
74
|
+
masked: out,
|
|
75
|
+
raw,
|
|
76
|
+
caret: complete && before === tokenEnds.length ? out.length : at,
|
|
77
|
+
complete,
|
|
78
|
+
overflow,
|
|
79
|
+
tokenOffsets
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function resolve(options, input, caret) {
|
|
83
|
+
const tokens = options.tokens ?? DEFAULT_MASK_TOKENS;
|
|
84
|
+
const fromRaw = !!options.fromRaw;
|
|
85
|
+
const { mask } = options;
|
|
86
|
+
if (typeof mask === "string") return run(parseMask(mask, tokens), input, caret, fromRaw);
|
|
87
|
+
if (typeof mask === "function") {
|
|
88
|
+
let current = mask("");
|
|
89
|
+
let result = run(parseMask(current, tokens), input, caret, fromRaw);
|
|
90
|
+
for (let i = 0; i < 4; i++) {
|
|
91
|
+
const next = mask(result.raw);
|
|
92
|
+
if (next === current) break;
|
|
93
|
+
current = next;
|
|
94
|
+
result = run(parseMask(current, tokens), input, caret, fromRaw);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
let last;
|
|
99
|
+
for (const m of mask) {
|
|
100
|
+
last = run(parseMask(m, tokens), input, caret, fromRaw);
|
|
101
|
+
if (!last.overflow) return last;
|
|
102
|
+
}
|
|
103
|
+
return last ?? run([], input, caret, fromRaw);
|
|
104
|
+
}
|
|
105
|
+
function applyMask(input, options) {
|
|
106
|
+
const caret = options.caret ?? (options.fromRaw ? Number.POSITIVE_INFINITY : input.length);
|
|
107
|
+
const { masked, raw, caret: at, complete } = resolve(options, input, caret);
|
|
108
|
+
return { masked, raw, caret: at, complete };
|
|
109
|
+
}
|
|
110
|
+
function maskDelete(value, caret, direction, options) {
|
|
111
|
+
const { tokenOffsets, raw } = resolve(options, value, caret);
|
|
112
|
+
const index = direction === "backward" ? tokenOffsets.filter((o) => o < caret).length - 1 : tokenOffsets.findIndex((o) => o >= caret);
|
|
113
|
+
if (index < 0) return null;
|
|
114
|
+
const chars = Array.from(raw);
|
|
115
|
+
chars.splice(index, 1);
|
|
116
|
+
return applyMask(chars.join(""), { ...options, caret: index, fromRaw: true });
|
|
117
|
+
}
|
|
118
|
+
function useInputMask(options) {
|
|
119
|
+
const spec = computed(() => {
|
|
120
|
+
const mask = toValue(options.mask);
|
|
121
|
+
return mask === void 0 || mask === "" ? void 0 : { mask, tokens: toValue(options.tokens) };
|
|
122
|
+
});
|
|
123
|
+
function format(value) {
|
|
124
|
+
const s = spec.value;
|
|
125
|
+
const text = value ?? "";
|
|
126
|
+
return s ? applyMask(text, s) : { masked: text, raw: text, caret: text.length, complete: true };
|
|
127
|
+
}
|
|
128
|
+
function commit(el, result) {
|
|
129
|
+
if (el.value !== result.masked) el.value = result.masked;
|
|
130
|
+
if (el.ownerDocument.activeElement === el) {
|
|
131
|
+
try {
|
|
132
|
+
el.setSelectionRange(result.caret, result.caret);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
options.onChange?.(result.masked, result.raw, result.complete);
|
|
137
|
+
}
|
|
138
|
+
function onBeforeInput(event) {
|
|
139
|
+
const s = spec.value;
|
|
140
|
+
const el = event.target;
|
|
141
|
+
if (!s || event.isComposing || !(el instanceof HTMLInputElement)) return;
|
|
142
|
+
const backward = event.inputType === "deleteContentBackward";
|
|
143
|
+
if (!backward && event.inputType !== "deleteContentForward") return;
|
|
144
|
+
const start = el.selectionStart;
|
|
145
|
+
if (start === null || start !== el.selectionEnd) return;
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
const result = maskDelete(el.value, start, backward ? "backward" : "forward", s);
|
|
148
|
+
if (result) commit(el, result);
|
|
149
|
+
}
|
|
150
|
+
function reformat(el) {
|
|
151
|
+
const s = spec.value;
|
|
152
|
+
if (!s) return;
|
|
153
|
+
commit(el, applyMask(el.value, { ...s, caret: el.selectionStart ?? el.value.length }));
|
|
154
|
+
}
|
|
155
|
+
function onInput(event) {
|
|
156
|
+
if (event.isComposing || !(event.target instanceof HTMLInputElement)) return;
|
|
157
|
+
reformat(event.target);
|
|
158
|
+
}
|
|
159
|
+
function onCompositionEnd(event) {
|
|
160
|
+
if (event.target instanceof HTMLInputElement) reformat(event.target);
|
|
161
|
+
}
|
|
162
|
+
return { active: computed(() => !!spec.value), format, onBeforeInput, onInput, onCompositionEnd };
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
DEFAULT_MASK_TOKENS,
|
|
166
|
+
applyMask,
|
|
167
|
+
maskDelete,
|
|
168
|
+
parseMask,
|
|
169
|
+
useInputMask
|
|
170
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
|
+
import { injectConfigProviderContext } from "reka-ui";
|
|
3
|
+
function useLocale(own) {
|
|
4
|
+
const context = injectConfigProviderContext({});
|
|
5
|
+
return computed(() => toValue(own) || context.locale?.value || void 0);
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
useLocale
|
|
9
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter, type Ref } from "vue";
|
|
2
|
+
import { type HotkeyHelp } from "./useHotkeys";
|
|
3
|
+
export interface UsePaintSelectOptions<K> {
|
|
4
|
+
/** The element that holds the items. */
|
|
5
|
+
root: Ref<HTMLElement | null>;
|
|
6
|
+
selection: () => readonly K[];
|
|
7
|
+
set: (keys: K[]) => void;
|
|
8
|
+
/** Turn painting off without unmounting (e.g. selection mode is off). */
|
|
9
|
+
enabled?: MaybeRefOrGetter<boolean>;
|
|
10
|
+
/** Map an item's `data-paint-key` back to its key. Defaults to the string itself. */
|
|
11
|
+
resolve?: (raw: string) => K | undefined;
|
|
12
|
+
/** Every key, for Ctrl+A while the focus is inside `root`. Omit to leave Ctrl+A alone. */
|
|
13
|
+
selectAll?: () => K[];
|
|
14
|
+
/** How Ctrl+A is listed in the shortcuts reference. */
|
|
15
|
+
help?: MaybeRefOrGetter<HotkeyHelp | undefined>;
|
|
16
|
+
}
|
|
17
|
+
export declare function usePaintSelect<K = string>(options: UsePaintSelectOptions<K>): {
|
|
18
|
+
onPointerDown: (event: PointerEvent, key: K, behaviour?: {
|
|
19
|
+
hold?: number;
|
|
20
|
+
}) => void;
|
|
21
|
+
painting: Readonly<Ref<boolean, boolean>>;
|
|
22
|
+
};
|