@witchcraft/ui 0.0.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 +236 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +34 -0
- package/dist/module.d.ts +34 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +124 -0
- package/dist/runtime/assets/base.css +1 -0
- package/dist/runtime/assets/locales/en.json +33 -0
- package/dist/runtime/assets/style.css +1 -0
- package/dist/runtime/assets/tailwind.css +1 -0
- package/dist/runtime/assets/theme.css +1 -0
- package/dist/runtime/build/WitchcraftUiResolver.d.ts +5 -0
- package/dist/runtime/build/WitchcraftUiResolver.js +17 -0
- package/dist/runtime/build/generateTheme.d.ts +1 -0
- package/dist/runtime/build/generateTheme.js +14 -0
- package/dist/runtime/build/unpluginIconViteOptions.d.ts +2 -0
- package/dist/runtime/build/unpluginIconViteOptions.js +10 -0
- package/dist/runtime/components/Aria/Aria.vue +18 -0
- package/dist/runtime/components/Focus.stories.d.ts +11 -0
- package/dist/runtime/components/Focus.stories.js +53 -0
- package/dist/runtime/components/Icon/Icon.vue +39 -0
- package/dist/runtime/components/LibButton/LibButton.stories.d.ts +12 -0
- package/dist/runtime/components/LibButton/LibButton.stories.js +94 -0
- package/dist/runtime/components/LibButton/LibButton.vue +247 -0
- package/dist/runtime/components/LibCheckbox/LibCheckbox.stories.d.ts +14 -0
- package/dist/runtime/components/LibCheckbox/LibCheckbox.stories.js +29 -0
- package/dist/runtime/components/LibCheckbox/LibCheckbox.vue +132 -0
- package/dist/runtime/components/LibColorInput/LibColorInput.stories.d.ts +7 -0
- package/dist/runtime/components/LibColorInput/LibColorInput.stories.js +58 -0
- package/dist/runtime/components/LibColorInput/LibColorInput.vue +125 -0
- package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.d.ts +7 -0
- package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.js +51 -0
- package/dist/runtime/components/LibColorPicker/LibColorPicker.vue +448 -0
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.d.ts +7 -0
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.js +36 -0
- package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue +95 -0
- package/dist/runtime/components/LibDatePicker/LibDatePicker.stories.d.ts +11 -0
- package/dist/runtime/components/LibDatePicker/LibDatePicker.stories.js +98 -0
- package/dist/runtime/components/LibDatePicker/LibDatePicker.vue +67 -0
- package/dist/runtime/components/LibDatePicker/LibRangeDatePicker.vue +370 -0
- package/dist/runtime/components/LibDatePicker/LibSingleDatePicker.vue +314 -0
- package/dist/runtime/components/LibDatePicker/LibTimeZonePicker.vue +27 -0
- package/dist/runtime/components/LibDatePicker/helpers.d.ts +25 -0
- package/dist/runtime/components/LibDatePicker/helpers.js +28 -0
- package/dist/runtime/components/LibDebug/LibDebug.stories.d.ts +9 -0
- package/dist/runtime/components/LibDebug/LibDebug.stories.js +46 -0
- package/dist/runtime/components/LibDebug/LibDebug.vue +91 -0
- package/dist/runtime/components/LibDevOnly/LibDevOnly.vue +53 -0
- package/dist/runtime/components/LibFileInput/LibFileInput.stories.d.ts +10 -0
- package/dist/runtime/components/LibFileInput/LibFileInput.stories.js +63 -0
- package/dist/runtime/components/LibFileInput/LibFileInput.vue +273 -0
- package/dist/runtime/components/LibInput/LibInput.stories.d.ts +33 -0
- package/dist/runtime/components/LibInput/LibInput.stories.js +339 -0
- package/dist/runtime/components/LibInput/LibInput.vue +372 -0
- package/dist/runtime/components/LibLabel/LibLabel.stories.d.ts +6 -0
- package/dist/runtime/components/LibLabel/LibLabel.stories.js +25 -0
- package/dist/runtime/components/LibLabel/LibLabel.vue +66 -0
- package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.d.ts +23 -0
- package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.js +60 -0
- package/dist/runtime/components/LibMultiValues/LibMultiValues.vue +127 -0
- package/dist/runtime/components/LibNotifications/LibNotification.stories.d.ts +15 -0
- package/dist/runtime/components/LibNotifications/LibNotification.stories.js +126 -0
- package/dist/runtime/components/LibNotifications/LibNotification.vue +121 -0
- package/dist/runtime/components/LibNotifications/LibNotifications.stories.d.ts +6 -0
- package/dist/runtime/components/LibNotifications/LibNotifications.stories.js +109 -0
- package/dist/runtime/components/LibNotifications/LibNotifications.vue +133 -0
- package/dist/runtime/components/LibPagination/LibPagination.stories.d.ts +6 -0
- package/dist/runtime/components/LibPagination/LibPagination.stories.js +40 -0
- package/dist/runtime/components/LibPagination/LibPagination.vue +261 -0
- package/dist/runtime/components/LibPalette/LibPalette.stories.d.ts +6 -0
- package/dist/runtime/components/LibPalette/LibPalette.stories.js +20 -0
- package/dist/runtime/components/LibPalette/LibPalette.vue +49 -0
- package/dist/runtime/components/LibPopup/LibPopup.stories.d.ts +14 -0
- package/dist/runtime/components/LibPopup/LibPopup.stories.js +147 -0
- package/dist/runtime/components/LibPopup/LibPopup.vue +441 -0
- package/dist/runtime/components/LibProgressBar/LibProgressBar.stories.d.ts +10 -0
- package/dist/runtime/components/LibProgressBar/LibProgressBar.stories.js +81 -0
- package/dist/runtime/components/LibProgressBar/LibProgressBar.vue +192 -0
- package/dist/runtime/components/LibRecorder/LibRecorder.stories.d.ts +19 -0
- package/dist/runtime/components/LibRecorder/LibRecorder.stories.js +63 -0
- package/dist/runtime/components/LibRecorder/LibRecorder.vue +243 -0
- package/dist/runtime/components/LibRoot/LibRoot.vue +126 -0
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.stories.d.ts +26 -0
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.stories.js +78 -0
- package/dist/runtime/components/LibSimpleInput/LibSimpleInput.vue +148 -0
- package/dist/runtime/components/LibSuggestions/LibSuggestions.stories.d.ts +27 -0
- package/dist/runtime/components/LibSuggestions/LibSuggestions.stories.js +112 -0
- package/dist/runtime/components/LibSuggestions/LibSuggestions.vue +198 -0
- package/dist/runtime/components/LibTable/LibTable.stories.d.ts +16 -0
- package/dist/runtime/components/LibTable/LibTable.stories.js +156 -0
- package/dist/runtime/components/LibTable/LibTable.vue +177 -0
- package/dist/runtime/components/Template/NAME.vue +49 -0
- package/dist/runtime/components/Template/TemplateStory.d.ts +7 -0
- package/dist/runtime/components/Template/TemplateStory.js +22 -0
- package/dist/runtime/components/TestControls/TestControls.vue +19 -0
- package/dist/runtime/components/index.d.ts +19 -0
- package/dist/runtime/components/index.js +19 -0
- package/dist/runtime/components/reset.stories.d.ts +5 -0
- package/dist/runtime/components/reset.stories.js +19 -0
- package/dist/runtime/components/shared/props.d.ts +135 -0
- package/dist/runtime/components/shared/props.js +14 -0
- package/dist/runtime/components/shared/storyHelpers/playInput.d.ts +8 -0
- package/dist/runtime/components/shared/storyHelpers/playInput.js +26 -0
- package/dist/runtime/components/shared/storyHelpers/playSuggestions.d.ts +12 -0
- package/dist/runtime/components/shared/storyHelpers/playSuggestions.js +83 -0
- package/dist/runtime/composables/index.d.ts +11 -0
- package/dist/runtime/composables/index.js +11 -0
- package/dist/runtime/composables/useAccesibilityOutline.d.ts +41 -0
- package/dist/runtime/composables/useAccesibilityOutline.js +58 -0
- package/dist/runtime/composables/useAriaLabel.d.ts +6 -0
- package/dist/runtime/composables/useAriaLabel.js +15 -0
- package/dist/runtime/composables/useDarkMode.d.ts +38 -0
- package/dist/runtime/composables/useDarkMode.js +79 -0
- package/dist/runtime/composables/useDivideAttrs.d.ts +27 -0
- package/dist/runtime/composables/useDivideAttrs.js +26 -0
- package/dist/runtime/composables/useGlobalResizeObserver.d.ts +3 -0
- package/dist/runtime/composables/useGlobalResizeObserver.js +28 -0
- package/dist/runtime/composables/useInjectedDarkMode.d.ts +2 -0
- package/dist/runtime/composables/useInjectedDarkMode.js +13 -0
- package/dist/runtime/composables/useInjectedI18n.d.ts +2 -0
- package/dist/runtime/composables/useInjectedI18n.js +7 -0
- package/dist/runtime/composables/useInjectedLocale.d.ts +2 -0
- package/dist/runtime/composables/useInjectedLocale.js +21 -0
- package/dist/runtime/composables/useNotificationHandler.d.ts +4 -0
- package/dist/runtime/composables/useNotificationHandler.js +21 -0
- package/dist/runtime/composables/useScrollNearContainerEdges.d.ts +68 -0
- package/dist/runtime/composables/useScrollNearContainerEdges.js +116 -0
- package/dist/runtime/composables/useScrollNearContainerEdges.stories.d.ts +7 -0
- package/dist/runtime/composables/useScrollNearContainerEdges.stories.js +85 -0
- package/dist/runtime/composables/useSetupDarkMode.d.ts +12 -0
- package/dist/runtime/composables/useSetupDarkMode.js +4 -0
- package/dist/runtime/composables/useSetupI18n.d.ts +20 -0
- package/dist/runtime/composables/useSetupI18n.js +50 -0
- package/dist/runtime/composables/useSetupLocale.d.ts +9 -0
- package/dist/runtime/composables/useSetupLocale.js +21 -0
- package/dist/runtime/composables/useShowDevOnlyKey.d.ts +7 -0
- package/dist/runtime/composables/useShowDevOnlyKey.js +20 -0
- package/dist/runtime/composables/useSuggestions.d.ts +38 -0
- package/dist/runtime/composables/useSuggestions.js +226 -0
- package/dist/runtime/directives/index.d.ts +4 -0
- package/dist/runtime/directives/index.js +4 -0
- package/dist/runtime/directives/vDetectFlex.d.ts +2 -0
- package/dist/runtime/directives/vDetectFlex.js +109 -0
- package/dist/runtime/directives/vExtractRootEl.d.ts +22 -0
- package/dist/runtime/directives/vExtractRootEl.js +13 -0
- package/dist/runtime/directives/vResizableCols.d.ts +60 -0
- package/dist/runtime/directives/vResizableCols.js +252 -0
- package/dist/runtime/directives/vResizeObserver.d.ts +2 -0
- package/dist/runtime/directives/vResizeObserver.js +34 -0
- package/dist/runtime/globalResizeObserver.d.ts +5 -0
- package/dist/runtime/globalResizeObserver.js +5 -0
- package/dist/runtime/helpers/NotificationHandler.d.ts +48 -0
- package/dist/runtime/helpers/NotificationHandler.js +162 -0
- package/dist/runtime/helpers/addValue.d.ts +1 -0
- package/dist/runtime/helpers/addValue.js +8 -0
- package/dist/runtime/helpers/base64ToImg.d.ts +1 -0
- package/dist/runtime/helpers/base64ToImg.js +11 -0
- package/dist/runtime/helpers/copy.d.ts +1 -0
- package/dist/runtime/helpers/copy.js +10 -0
- package/dist/runtime/helpers/createNoonUtcDate.d.ts +7 -0
- package/dist/runtime/helpers/createNoonUtcDate.js +14 -0
- package/dist/runtime/helpers/defaultTranslationFunction.d.ts +16 -0
- package/dist/runtime/helpers/defaultTranslationFunction.js +14 -0
- package/dist/runtime/helpers/getTimeZoneList.d.ts +1 -0
- package/dist/runtime/helpers/getTimeZoneList.js +3 -0
- package/dist/runtime/helpers/hasModifiers.d.ts +1 -0
- package/dist/runtime/helpers/hasModifiers.js +1 -0
- package/dist/runtime/helpers/index.d.ts +8 -0
- package/dist/runtime/helpers/index.js +8 -0
- package/dist/runtime/helpers/readFile.d.ts +1 -0
- package/dist/runtime/helpers/readFile.js +13 -0
- package/dist/runtime/helpers/resizeObserverWrapper.d.ts +8 -0
- package/dist/runtime/helpers/resizeObserverWrapper.js +37 -0
- package/dist/runtime/helpers/storybook.d.ts +7 -0
- package/dist/runtime/helpers/storybook.js +42 -0
- package/dist/runtime/main.lib.d.ts +26 -0
- package/dist/runtime/main.lib.js +8 -0
- package/dist/runtime/nuxt/plugins/vue-plugin.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/vue-plugin.js +12 -0
- package/dist/runtime/tailwind/index.d.ts +1 -0
- package/dist/runtime/tailwind/index.js +1 -0
- package/dist/runtime/tailwind/themeConvertionOpts.d.ts +2 -0
- package/dist/runtime/tailwind/themeConvertionOpts.js +12 -0
- package/dist/runtime/theme.d.ts +2 -0
- package/dist/runtime/theme.js +2 -0
- package/dist/runtime/types/index.d.ts +119 -0
- package/dist/runtime/types/index.js +0 -0
- package/dist/runtime/utils/twMerge.d.ts +10 -0
- package/dist/runtime/utils/twMerge.js +10 -0
- package/dist/runtime/vue/VueComponentsPlugin.d.ts +2 -0
- package/dist/runtime/vue/VueComponentsPlugin.js +10 -0
- package/dist/runtime/vue/registerComponents.d.ts +19 -0
- package/dist/runtime/vue/registerComponents.js +10 -0
- package/dist/runtime/vue/registerDirectives.d.ts +3 -0
- package/dist/runtime/vue/registerDirectives.js +9 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +207 -0
- package/src/module.ts +176 -0
- package/src/runtime/assets/base.css +67 -0
- package/src/runtime/assets/locales/en.json +33 -0
- package/src/runtime/assets/style.css +144 -0
- package/src/runtime/assets/tailwind.css +5 -0
- package/src/runtime/assets/theme.css +65 -0
- package/src/runtime/build/WitchcraftUiResolver.ts +27 -0
- package/src/runtime/build/generateTheme.ts +16 -0
- package/src/runtime/build/unpluginIconViteOptions.ts +11 -0
- package/src/runtime/components/Aria/Aria.vue +27 -0
- package/src/runtime/components/Focus.stories.ts +67 -0
- package/src/runtime/components/Icon/Icon.vue +39 -0
- package/src/runtime/components/LibButton/LibButton.stories.ts +107 -0
- package/src/runtime/components/LibButton/LibButton.vue +247 -0
- package/src/runtime/components/LibCheckbox/LibCheckbox.stories.ts +41 -0
- package/src/runtime/components/LibCheckbox/LibCheckbox.vue +132 -0
- package/src/runtime/components/LibColorInput/LibColorInput.stories.ts +69 -0
- package/src/runtime/components/LibColorInput/LibColorInput.vue +125 -0
- package/src/runtime/components/LibColorPicker/LibColorPicker.stories.ts +60 -0
- package/src/runtime/components/LibColorPicker/LibColorPicker.vue +448 -0
- package/src/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.ts +51 -0
- package/src/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue +95 -0
- package/src/runtime/components/LibDatePicker/LibDatePicker.stories.ts +114 -0
- package/src/runtime/components/LibDatePicker/LibDatePicker.vue +67 -0
- package/src/runtime/components/LibDatePicker/LibRangeDatePicker.vue +370 -0
- package/src/runtime/components/LibDatePicker/LibSingleDatePicker.vue +314 -0
- package/src/runtime/components/LibDatePicker/LibTimeZonePicker.vue +27 -0
- package/src/runtime/components/LibDatePicker/helpers.ts +55 -0
- package/src/runtime/components/LibDebug/LibDebug.stories.ts +58 -0
- package/src/runtime/components/LibDebug/LibDebug.vue +91 -0
- package/src/runtime/components/LibDevOnly/LibDevOnly.vue +53 -0
- package/src/runtime/components/LibFileInput/LibFileInput.stories.ts +79 -0
- package/src/runtime/components/LibFileInput/LibFileInput.vue +273 -0
- package/src/runtime/components/LibInput/LibInput.stories.ts +367 -0
- package/src/runtime/components/LibInput/LibInput.vue +372 -0
- package/src/runtime/components/LibLabel/LibLabel.stories.ts +37 -0
- package/src/runtime/components/LibLabel/LibLabel.vue +66 -0
- package/src/runtime/components/LibMultiValues/LibMultiValues.stories.ts +83 -0
- package/src/runtime/components/LibMultiValues/LibMultiValues.vue +127 -0
- package/src/runtime/components/LibNotifications/LibNotification.stories.ts +142 -0
- package/src/runtime/components/LibNotifications/LibNotification.vue +121 -0
- package/src/runtime/components/LibNotifications/LibNotifications.stories.ts +124 -0
- package/src/runtime/components/LibNotifications/LibNotifications.vue +133 -0
- package/src/runtime/components/LibPagination/LibPagination.stories.ts +53 -0
- package/src/runtime/components/LibPagination/LibPagination.vue +261 -0
- package/src/runtime/components/LibPalette/LibPalette.stories.ts +32 -0
- package/src/runtime/components/LibPalette/LibPalette.vue +49 -0
- package/src/runtime/components/LibPopup/LibPopup.stories.ts +157 -0
- package/src/runtime/components/LibPopup/LibPopup.vue +441 -0
- package/src/runtime/components/LibProgressBar/LibProgressBar.stories.ts +94 -0
- package/src/runtime/components/LibProgressBar/LibProgressBar.vue +192 -0
- package/src/runtime/components/LibRecorder/LibRecorder.stories.ts +81 -0
- package/src/runtime/components/LibRecorder/LibRecorder.vue +243 -0
- package/src/runtime/components/LibRoot/LibRoot.vue +126 -0
- package/src/runtime/components/LibSimpleInput/LibSimpleInput.stories.ts +98 -0
- package/src/runtime/components/LibSimpleInput/LibSimpleInput.vue +148 -0
- package/src/runtime/components/LibSuggestions/LibSuggestions.stories.ts +137 -0
- package/src/runtime/components/LibSuggestions/LibSuggestions.vue +198 -0
- package/src/runtime/components/LibTable/LibTable.stories.ts +170 -0
- package/src/runtime/components/LibTable/LibTable.vue +177 -0
- package/src/runtime/components/Template/NAME.vue +49 -0
- package/src/runtime/components/Template/TemplateStory.ts +38 -0
- package/src/runtime/components/TestControls/TestControls.vue +19 -0
- package/src/runtime/components/index.ts +22 -0
- package/src/runtime/components/reset.stories.ts +32 -0
- package/src/runtime/components/shared/props.ts +142 -0
- package/src/runtime/components/shared/storyHelpers/playInput.ts +35 -0
- package/src/runtime/components/shared/storyHelpers/playSuggestions.ts +105 -0
- package/src/runtime/composables/index.ts +13 -0
- package/src/runtime/composables/useAccesibilityOutline.ts +104 -0
- package/src/runtime/composables/useAriaLabel.ts +23 -0
- package/src/runtime/composables/useDarkMode.ts +146 -0
- package/src/runtime/composables/useDivideAttrs.ts +52 -0
- package/src/runtime/composables/useGlobalResizeObserver.ts +33 -0
- package/src/runtime/composables/useInjectedDarkMode.ts +15 -0
- package/src/runtime/composables/useInjectedI18n.ts +10 -0
- package/src/runtime/composables/useInjectedLocale.ts +24 -0
- package/src/runtime/composables/useNotificationHandler.ts +32 -0
- package/src/runtime/composables/useScrollNearContainerEdges.stories.ts +93 -0
- package/src/runtime/composables/useScrollNearContainerEdges.ts +205 -0
- package/src/runtime/composables/useSetupDarkMode.ts +14 -0
- package/src/runtime/composables/useSetupI18n.ts +77 -0
- package/src/runtime/composables/useSetupLocale.ts +32 -0
- package/src/runtime/composables/useShowDevOnlyKey.ts +28 -0
- package/src/runtime/composables/useSuggestions.ts +297 -0
- package/src/runtime/directives/index.ts +6 -0
- package/src/runtime/directives/vDetectFlex.ts +159 -0
- package/src/runtime/directives/vExtractRootEl.ts +38 -0
- package/src/runtime/directives/vResizableCols.ts +378 -0
- package/src/runtime/directives/vResizeObserver.ts +45 -0
- package/src/runtime/globalResizeObserver.ts +12 -0
- package/src/runtime/helpers/NotificationHandler.ts +227 -0
- package/src/runtime/helpers/addValue.ts +10 -0
- package/src/runtime/helpers/base64ToImg.ts +14 -0
- package/src/runtime/helpers/copy.ts +11 -0
- package/src/runtime/helpers/createNoonUtcDate.ts +21 -0
- package/src/runtime/helpers/defaultTranslationFunction.ts +33 -0
- package/src/runtime/helpers/getTimeZoneList.ts +4 -0
- package/src/runtime/helpers/hasModifiers.ts +1 -0
- package/src/runtime/helpers/index.ts +10 -0
- package/src/runtime/helpers/readFile.ts +22 -0
- package/src/runtime/helpers/resizeObserverWrapper.ts +45 -0
- package/src/runtime/helpers/storybook.ts +52 -0
- package/src/runtime/main.lib.ts +31 -0
- package/src/runtime/nuxt/plugins/vue-plugin.ts +19 -0
- package/src/runtime/tailwind/index.ts +3 -0
- package/src/runtime/tailwind/themeConvertionOpts.ts +15 -0
- package/src/runtime/theme.ts +5 -0
- package/src/runtime/types/index.ts +116 -0
- package/src/runtime/utils/twMerge.ts +13 -0
- package/src/runtime/vue/VueComponentsPlugin.ts +16 -0
- package/src/runtime/vue/registerComponents.ts +31 -0
- package/src/runtime/vue/registerDirectives.ts +12 -0
- package/types/components.d.ts +27 -0
- package/types/global.d.ts +16 -0
- package/types/index.d.ts +5 -0
- package/types/vite.d.ts +2 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { reactive, type Ref, ref } from "vue"
|
|
2
|
+
|
|
3
|
+
import type { ScrollNearContainerEdgesOptions } from "../types/index.js"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a function `scrollContainer` that allows scrolling a container manually when the coordinates are near it's edges.
|
|
8
|
+
* Supports scrolling faster the closer one is to the edge, and configuing an inner and outer margin.
|
|
9
|
+
*
|
|
10
|
+
* It can be used in any *move event.
|
|
11
|
+
*
|
|
12
|
+
* By default it sets a setInterval timer to continue scrolling even when the user does not move.
|
|
13
|
+
* An `endScroll` function is provided which should be called on the *up event to cleanup the timer and variables properly (there is also the individual `clearScrollInterval` and `resetCanScroll` functions.
|
|
14
|
+
*
|
|
15
|
+
* It also provides an `isScrolling` ref and a `scrollIndicator` reactive object for knowing which direction the user is scrolling in to be able to style the element. They can be force cleared with the `resetCanScroll` function.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* const {
|
|
19
|
+
* scrollEdges,
|
|
20
|
+
* isScrolling,
|
|
21
|
+
* scrollIndicator,
|
|
22
|
+
* endScroll,
|
|
23
|
+
* } = useScrollNearContainerEdges({
|
|
24
|
+
* containerEl,
|
|
25
|
+
* scrollMargin,
|
|
26
|
+
* outerScrollMargin,
|
|
27
|
+
* })
|
|
28
|
+
*
|
|
29
|
+
* const onPointerMove = (e: PointerEvent): void => {
|
|
30
|
+
* scrollEdges(e.clientX, e.clientY)
|
|
31
|
+
* if (isScrolling.value) {
|
|
32
|
+
* e.preventDefault()
|
|
33
|
+
* return
|
|
34
|
+
* }
|
|
35
|
+
* //...
|
|
36
|
+
* }
|
|
37
|
+
* const onPointerUp = (_e: PointerEvent): void => {
|
|
38
|
+
* endScroll()
|
|
39
|
+
* //...
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
* Styling example with tailwind and tailwind-merge:
|
|
43
|
+
*
|
|
44
|
+
* ```vue
|
|
45
|
+
* <div
|
|
46
|
+
* :class="twMerge(
|
|
47
|
+
* isScrolling && `relative after:content-[''] after:absolute after:inset after:border-2 after:border-transparent`,
|
|
48
|
+
* scrollIndicator.right && `after:border-r-accent-500/60`,
|
|
49
|
+
* scrollIndicator.down && `after:border-b-accent-500/60`,
|
|
50
|
+
* scrollIndicator.left && `after:border-l-accent-500/60`,
|
|
51
|
+
* scrollIndicator.up && `after:border-t-accent-500/60`,
|
|
52
|
+
* )"
|
|
53
|
+
* >
|
|
54
|
+
* <div class="overflow-auto" ref="containerEl" >
|
|
55
|
+
* </div>
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
export const useScrollNearContainerEdges = ({
|
|
60
|
+
containerEl,
|
|
61
|
+
scrollMargin = 10,
|
|
62
|
+
outerScrollMargin,
|
|
63
|
+
fastPixelMultiplier = 4,
|
|
64
|
+
fastPixelAmount,
|
|
65
|
+
useTimer = true,
|
|
66
|
+
timerInterval = 1,
|
|
67
|
+
}: ScrollNearContainerEdgesOptions): {
|
|
68
|
+
scrollEdges: (clientX: number, clientY: number, overrideUseTimer?: boolean) => void
|
|
69
|
+
/** Reactive. */
|
|
70
|
+
scrollIndicator: { left: boolean, right: boolean, down: boolean, up: boolean }
|
|
71
|
+
resetScrollIndicator: () => void
|
|
72
|
+
clearScrollInterval: () => void
|
|
73
|
+
isScrolling: Ref<boolean>
|
|
74
|
+
endScroll: () => void
|
|
75
|
+
} => {
|
|
76
|
+
fastPixelMultiplier = fastPixelAmount !== undefined ? fastPixelAmount * 2 : fastPixelMultiplier
|
|
77
|
+
const scrollIndicator = reactive({ left: false, right: false, down: false, up: false })
|
|
78
|
+
const isScrolling = ref(false)
|
|
79
|
+
const resetScrollIndicator = (): void => {
|
|
80
|
+
scrollIndicator.right = false
|
|
81
|
+
scrollIndicator.left = false
|
|
82
|
+
scrollIndicator.up = false
|
|
83
|
+
scrollIndicator.down = false
|
|
84
|
+
isScrolling.value = false
|
|
85
|
+
}
|
|
86
|
+
let timer: ReturnType<typeof setTimeout>
|
|
87
|
+
|
|
88
|
+
const scrollContainerRelative = (x: number, y: number): void => {
|
|
89
|
+
const el = containerEl.value
|
|
90
|
+
if (!el) return
|
|
91
|
+
const leftSpace = el.scrollLeft
|
|
92
|
+
const topSpace = el.scrollTop
|
|
93
|
+
el.scroll(leftSpace + x, topSpace + y)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const move = { x: 0, y: 0 }
|
|
97
|
+
const resetMove = (): void => {
|
|
98
|
+
move.x = 0; move.y = 0
|
|
99
|
+
}
|
|
100
|
+
const m = scrollMargin
|
|
101
|
+
const M = outerScrollMargin ?? 0
|
|
102
|
+
|
|
103
|
+
const tryScrollContainer = (x: number, y: number): void => {
|
|
104
|
+
const el = containerEl.value
|
|
105
|
+
if (!el) return
|
|
106
|
+
const box = el.getBoundingClientRect()
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
rightRightLimit│
|
|
110
|
+
│
|
|
111
|
+
rightLeftLimit│ │
|
|
112
|
+
│ ▼
|
|
113
|
+
┌───────────────┼───┐
|
|
114
|
+
│OuterLimit │ │
|
|
115
|
+
│ ┌─────────────┴─┐ │
|
|
116
|
+
│ │Container ▼ │ │
|
|
117
|
+
│ │ ┌───────────┐ │ │
|
|
118
|
+
│ │ │InnerLimit │ │ │
|
|
119
|
+
│ │ └───────────┘ │ │
|
|
120
|
+
│ └───────────────┘ │
|
|
121
|
+
└───────────────────┘
|
|
122
|
+
*/
|
|
123
|
+
const leftLimit = box.x
|
|
124
|
+
const rightLimit = box.x + box.width
|
|
125
|
+
const topLimit = box.y
|
|
126
|
+
const bottomLimit = box.y + box.height
|
|
127
|
+
|
|
128
|
+
const leftLeftLimit = leftLimit - M
|
|
129
|
+
const leftRightLimit = leftLimit + m
|
|
130
|
+
const rightLeftLimit = rightLimit - M
|
|
131
|
+
const rightRightLimit = rightLimit + m
|
|
132
|
+
|
|
133
|
+
const topTopLimit = topLimit - M
|
|
134
|
+
const topBottomLimit = topLimit + m
|
|
135
|
+
const bottomTopLimit = bottomLimit - m
|
|
136
|
+
const bottomBottomLimit = bottomLimit + M
|
|
137
|
+
const t = m + M
|
|
138
|
+
|
|
139
|
+
resetScrollIndicator()
|
|
140
|
+
resetMove()
|
|
141
|
+
|
|
142
|
+
if (x > leftLeftLimit && x < leftRightLimit) {
|
|
143
|
+
const leftSpace = el.scrollLeft
|
|
144
|
+
if (leftSpace > 0) {
|
|
145
|
+
const edgeOffset = ((leftLimit + m) - x) / t
|
|
146
|
+
move.x = -edgeOffset * fastPixelMultiplier
|
|
147
|
+
}
|
|
148
|
+
} else if (x > rightLeftLimit && x < rightRightLimit) {
|
|
149
|
+
const rightSpace = (el.scrollWidth - el.scrollLeft) - Math.round(box.width)
|
|
150
|
+
if (rightSpace > 0) {
|
|
151
|
+
const edgeOffset = (x - (rightLimit - m)) / t
|
|
152
|
+
move.x = edgeOffset * fastPixelMultiplier
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (y > topTopLimit && y < topBottomLimit) {
|
|
156
|
+
const topSpace = el.scrollTop
|
|
157
|
+
if (topSpace > 0) {
|
|
158
|
+
const edgeOffset = ((topLimit + m) - y) / t
|
|
159
|
+
move.y = -edgeOffset * fastPixelMultiplier
|
|
160
|
+
}
|
|
161
|
+
} else if (y > bottomTopLimit && y < bottomBottomLimit) {
|
|
162
|
+
const bottomSpace = (el.scrollHeight - el.scrollTop) - Math.round(box.height)
|
|
163
|
+
if (bottomSpace > 0) {
|
|
164
|
+
const edgeOffset = (y - (bottomLimit - m)) / t
|
|
165
|
+
move.y = edgeOffset * fastPixelMultiplier
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (move.x !== 0 || move.y !== 0) {
|
|
169
|
+
isScrolling.value = true
|
|
170
|
+
scrollIndicator.right = move.x > 0
|
|
171
|
+
scrollIndicator.left = move.x < 0
|
|
172
|
+
scrollIndicator.up = move.y < 0
|
|
173
|
+
scrollIndicator.down = move.y > 0
|
|
174
|
+
scrollContainerRelative(move.x, move.y)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const clearScrollInterval = (): void => {
|
|
178
|
+
clearInterval(timer)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const scrollEdges = (clientX: number, clientY: number, overrideUseTimer?: boolean): void => {
|
|
182
|
+
clearInterval(timer)
|
|
183
|
+
tryScrollContainer(clientX, clientY)
|
|
184
|
+
|
|
185
|
+
if (overrideUseTimer ? overrideUseTimer : useTimer) {
|
|
186
|
+
timer = setInterval(() => {
|
|
187
|
+
tryScrollContainer(clientX, clientY)
|
|
188
|
+
}, timerInterval)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const endScroll = (): void => {
|
|
192
|
+
clearScrollInterval()
|
|
193
|
+
resetScrollIndicator()
|
|
194
|
+
resetMove()
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
scrollEdges,
|
|
198
|
+
scrollIndicator,
|
|
199
|
+
resetScrollIndicator,
|
|
200
|
+
clearScrollInterval,
|
|
201
|
+
isScrolling,
|
|
202
|
+
endScroll,
|
|
203
|
+
/* resetMove does not need to be returned since the user cannot call the timer interval manually and the scrollContainer function resets it before starting. */
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useDarkMode } from "./useDarkMode.js"
|
|
2
|
+
/**
|
|
3
|
+
* A composable for setting up dark mode that automatically takes care of saving the user's preference.
|
|
4
|
+
*
|
|
5
|
+
* See the returned utilities for more details.
|
|
6
|
+
*
|
|
7
|
+
* Use it's twin, `useInjectedDarkMode` for accessing the injected state and commands in components
|
|
8
|
+
*
|
|
9
|
+
* Note that this should only be called once at the root of the app.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
export function useSetupDarkMode(...args: Parameters<typeof useDarkMode>): ReturnType<typeof useDarkMode> {
|
|
13
|
+
return useDarkMode(...args)
|
|
14
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type InjectionKey,
|
|
3
|
+
provide,
|
|
4
|
+
type Ref,
|
|
5
|
+
ref,
|
|
6
|
+
watch,
|
|
7
|
+
} from "vue"
|
|
8
|
+
|
|
9
|
+
import { defaultTranslationFunction } from "../helpers/defaultTranslationFunction.js"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export type TranslationFunction = (key: string, replacements?: Record<string, any>) => string
|
|
13
|
+
|
|
14
|
+
const _i18nInjectionKey = Symbol("witchcraftUiI18n") as InjectionKey<TranslationFunction>
|
|
15
|
+
const _translationMessagesInjectionKey = Symbol("witchcraftUiI18nMessages") as InjectionKey<Ref<Record<string, any>>>
|
|
16
|
+
|
|
17
|
+
const messagesGlob = import.meta.glob("../assets/locales/*.json")
|
|
18
|
+
const loaded: Record<string, any> = {}
|
|
19
|
+
const dummyLibraryMessages = new Proxy({}, {
|
|
20
|
+
get() {
|
|
21
|
+
return "..."
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
const dummyMessageSet = new Proxy({}, {
|
|
25
|
+
get(_, key: string) {
|
|
26
|
+
if (key === "witchcraft-ui") {
|
|
27
|
+
return dummyLibraryMessages
|
|
28
|
+
} else return undefined
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A composable for providing a translation function + messages for the library. Note the Root component already takes care of setting this up with defaults, disable it via it's `useBuiltinTranslations` option.
|
|
35
|
+
*
|
|
36
|
+
* Should be called only once. You can choose to await it or not (see the `useDummyMessageSetWhileLoading` option).
|
|
37
|
+
*
|
|
38
|
+
* A default function is available, see {@link defaultTranslationFunction}.
|
|
39
|
+
*/
|
|
40
|
+
export async function useSetupI18n({
|
|
41
|
+
locale,
|
|
42
|
+
useBuiltinTranslations = false,
|
|
43
|
+
useDummyMessageSetWhileLoading = true,
|
|
44
|
+
translationFunction = defaultTranslationFunction,
|
|
45
|
+
}: {
|
|
46
|
+
locale: Ref<string>
|
|
47
|
+
/** Will asynchronously load the built-in translations for the current locale (watching and updating if needed). If you don't use this, you'll need to provide a custom translation function. */
|
|
48
|
+
useBuiltinTranslations?: boolean
|
|
49
|
+
/** To avoid having to wrap the component in a Suspense component because of the await on `useSetupI18n`, we can provide a dummy message proxy that just returns empty text until the messages are loaded. */
|
|
50
|
+
useDummyMessageSetWhileLoading?: boolean
|
|
51
|
+
/** A custom translation function. The default requires the `useBuiltinTranslations` option to be true. */
|
|
52
|
+
translationFunction?: TranslationFunction
|
|
53
|
+
}): Promise<void> {
|
|
54
|
+
provide(_i18nInjectionKey, translationFunction)
|
|
55
|
+
if (useBuiltinTranslations) {
|
|
56
|
+
const messages = ref(useDummyMessageSetWhileLoading ? dummyMessageSet : {})
|
|
57
|
+
provide(_translationMessagesInjectionKey, messages)
|
|
58
|
+
async function loadMessageSet(l: string): Promise<void> {
|
|
59
|
+
const isLoaded = loaded[l]
|
|
60
|
+
if (isLoaded) {
|
|
61
|
+
messages.value = loaded[l]
|
|
62
|
+
} else {
|
|
63
|
+
const newMessages = ((await messagesGlob[`../assets/locales/${l}.json`]()) as any).default as any
|
|
64
|
+
loaded[l] = newMessages
|
|
65
|
+
messages.value = newMessages
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
void loadMessageSet(locale.value)
|
|
69
|
+
watch(locale, async () => {
|
|
70
|
+
void loadMessageSet(locale.value)
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const i18nInjectionKey = _i18nInjectionKey
|
|
76
|
+
export const translationMessagesInjectionKey = _translationMessagesInjectionKey
|
|
77
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type InjectionKey, provide,type Ref,ref } from "vue"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const languageLocaleInjectionKey = Symbol("witchcraftUiLanguageLocale") as InjectionKey<Ref<string>>
|
|
5
|
+
export const timeLocaleInjectionKey = Symbol("witchcraftUiTimeLocale") as InjectionKey<Ref<string>>
|
|
6
|
+
|
|
7
|
+
export function useSetupLocale(): {
|
|
8
|
+
languageLocale: Ref<string>
|
|
9
|
+
timeLocale: Ref<string>
|
|
10
|
+
setLanguageLocale: (value: string) => void
|
|
11
|
+
setTimeLocale: (value: string) => void
|
|
12
|
+
} {
|
|
13
|
+
const languageLocale = ref("en")
|
|
14
|
+
// GB instead of US so we can get sane dd/mm/yy.
|
|
15
|
+
const timeLocale = ref("en-GB")
|
|
16
|
+
function setLanguageLocale(value: string): void {
|
|
17
|
+
languageLocale.value = value
|
|
18
|
+
}
|
|
19
|
+
function setTimeLocale(value: string): void {
|
|
20
|
+
timeLocale.value = value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
provide(languageLocaleInjectionKey, languageLocale)
|
|
24
|
+
provide(timeLocaleInjectionKey, timeLocale)
|
|
25
|
+
return {
|
|
26
|
+
languageLocale,
|
|
27
|
+
timeLocale,
|
|
28
|
+
setLanguageLocale,
|
|
29
|
+
setTimeLocale,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, provide, ref } from "vue"
|
|
2
|
+
|
|
3
|
+
const injectionKey = Symbol("showDevOnly")
|
|
4
|
+
/**
|
|
5
|
+
* For nuxt only.
|
|
6
|
+
*
|
|
7
|
+
* Communicates with the dev only component to show/hide it depending on a keypress (single key, no modifiers, `F1` by default). *
|
|
8
|
+
*/
|
|
9
|
+
export function useShowDevOnlyKey(key: string = "F1"): void {
|
|
10
|
+
if (!(import.meta as any).dev) return
|
|
11
|
+
const showDevOnly = ref(false)
|
|
12
|
+
provide(injectionKey, showDevOnly)
|
|
13
|
+
|
|
14
|
+
const listener = (e: KeyboardEvent): void => {
|
|
15
|
+
if (e.key === key && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
16
|
+
showDevOnly.value = !showDevOnly.value
|
|
17
|
+
e.preventDefault()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
document.addEventListener("keydown", listener)
|
|
22
|
+
})
|
|
23
|
+
onBeforeUnmount(() => {
|
|
24
|
+
document.removeEventListener("keydown", listener)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const showDevOnlyInjectionKey = injectionKey
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { isBlank } from "@alanscodelog/utils/isBlank.js"
|
|
3
|
+
import { isObject } from "@alanscodelog/utils/isObject.js"
|
|
4
|
+
import { computed, type Ref, ref, toRaw, watch } from "vue"
|
|
5
|
+
|
|
6
|
+
import { type SuggestionsEmits,type SuggestionsOptions } from "../components/shared/props.js"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The logic for the suggestions component.
|
|
11
|
+
*
|
|
12
|
+
* Note that while object suggestions are supported, the `suggestionLabel` prop is required and $inputModel and $modelValue will still be string values (as returned by the suggestionLabel function).
|
|
13
|
+
*/
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
15
|
+
export function useSuggestions<TSuggestion>(
|
|
16
|
+
$inputValue: Ref<string>,
|
|
17
|
+
$modelValue: Ref<string>,
|
|
18
|
+
emit: SuggestionsEmits,
|
|
19
|
+
opts: SuggestionsOptions<TSuggestion>,
|
|
20
|
+
debug: boolean = false
|
|
21
|
+
) {
|
|
22
|
+
if (typeof opts.suggestions?.[0] === "object" && !opts.suggestionLabel && !opts.suggestionsFilter) {
|
|
23
|
+
throw new Error("`suggestionLabel` or `suggestionsFilter` must be passed if suggestions are objects.")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isOpen = ref(false)
|
|
27
|
+
const activeSuggestion = ref(-1)
|
|
28
|
+
watch(isOpen, val => { emit("update:isOpen", val) })
|
|
29
|
+
watch(activeSuggestion, val => { emit("update:activeSuggestion", val) })
|
|
30
|
+
const suggestionKey = (item: any): string => {
|
|
31
|
+
if (isObject<any>(item)) {
|
|
32
|
+
if (opts.suggestionLabel) {
|
|
33
|
+
return opts.suggestionLabel(item)
|
|
34
|
+
}
|
|
35
|
+
throw new Error("`suggestionLabel` must be passed if suggestions are objects.")
|
|
36
|
+
}
|
|
37
|
+
return item
|
|
38
|
+
}
|
|
39
|
+
const getSuggestionLabel = (item: any): string => opts.suggestionLabel?.(item) ?? suggestionKey(item) ?? ""
|
|
40
|
+
|
|
41
|
+
const defaultSuggestionsFilter = (input: string, items: TSuggestion[]): TSuggestion[] => input === ""
|
|
42
|
+
? [...items]
|
|
43
|
+
: items.filter(item => getSuggestionLabel(item).toLowerCase().includes(input.toLowerCase()))
|
|
44
|
+
const suggestionsFilter = computed(() => opts.suggestionsFilter ?? defaultSuggestionsFilter)
|
|
45
|
+
|
|
46
|
+
const suggestionsList = computed(() => {
|
|
47
|
+
if (opts.suggestions) {
|
|
48
|
+
const res = suggestionsFilter.value($inputValue.value, opts.suggestions)
|
|
49
|
+
return res
|
|
50
|
+
}
|
|
51
|
+
return undefined
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const suggestionAvailable = computed<boolean>(() =>
|
|
55
|
+
(suggestionsList.value?.length ?? 0) > 0)
|
|
56
|
+
|
|
57
|
+
const moreThanOneSuggestionAvailable = computed<boolean>(() =>
|
|
58
|
+
(suggestionsList.value?.length ?? 0) > 1)
|
|
59
|
+
|
|
60
|
+
const exactlyMatchingSuggestion = computed(() =>
|
|
61
|
+
opts.suggestions?.find(suggestion =>
|
|
62
|
+
$inputValue.value === getSuggestionLabel(suggestion)))
|
|
63
|
+
|
|
64
|
+
const isValidSuggestion = computed(() =>
|
|
65
|
+
((!opts.restrictToSuggestions && opts.isValid) || suggestionAvailable.value))
|
|
66
|
+
|
|
67
|
+
const openable = computed(() =>
|
|
68
|
+
opts.canOpen && (
|
|
69
|
+
(isBlank($inputValue.value) && opts.allowOpenEmpty) ||
|
|
70
|
+
suggestionAvailable.value
|
|
71
|
+
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const filteredSuggestions = computed(() => {
|
|
76
|
+
if (opts.suggestions) {
|
|
77
|
+
const res = (suggestionAvailable.value
|
|
78
|
+
? suggestionsList.value!
|
|
79
|
+
: opts.suggestions!)
|
|
80
|
+
|
|
81
|
+
if (opts.restrictToSuggestions && !isValidSuggestion.value) return res
|
|
82
|
+
if (opts.preventDuplicateValues && opts.values) {
|
|
83
|
+
return res.filter(suggestion => !opts.values!.includes(suggestionKey(suggestion)))
|
|
84
|
+
}
|
|
85
|
+
return res
|
|
86
|
+
}
|
|
87
|
+
return undefined
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
// methods
|
|
92
|
+
|
|
93
|
+
const closeSuggestions = (): void => {
|
|
94
|
+
if (debug) console.log("closeSuggestions")
|
|
95
|
+
isOpen.value = false
|
|
96
|
+
activeSuggestion.value = -1
|
|
97
|
+
}
|
|
98
|
+
const openSuggestions = (): void => {
|
|
99
|
+
if (debug) console.log("openSuggestions", { openable: openable.value })
|
|
100
|
+
if (!openable.value) return
|
|
101
|
+
if (activeSuggestion.value === -1) {
|
|
102
|
+
if (exactlyMatchingSuggestion.value) {
|
|
103
|
+
activeSuggestion.value = suggestionsList.value?.indexOf(exactlyMatchingSuggestion.value) ?? -1
|
|
104
|
+
} else {
|
|
105
|
+
activeSuggestion.value = 0
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
isOpen.value = true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function enterSuggestion(num: number): void {
|
|
112
|
+
if (num < -1 || num > (filteredSuggestions.value?.length ?? 0)) return
|
|
113
|
+
if (debug) console.log("enterSuggestion", num)
|
|
114
|
+
if (filteredSuggestions.value === undefined) return
|
|
115
|
+
|
|
116
|
+
const suggestion = filteredSuggestions.value[num]
|
|
117
|
+
const val = suggestionKey(suggestion)
|
|
118
|
+
$modelValue.value = val
|
|
119
|
+
$inputValue.value = getSuggestionLabel(suggestion)
|
|
120
|
+
closeSuggestions()
|
|
121
|
+
emit("submit", val, toRaw(suggestion))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const enterSelected = (): void => {
|
|
125
|
+
if (activeSuggestion.value === -1) {
|
|
126
|
+
if (!opts.restrictToSuggestions) {
|
|
127
|
+
if (debug) console.log("enterSelected, unrestricted, emitting submit")
|
|
128
|
+
emit("submit", $inputValue.value)
|
|
129
|
+
} else {
|
|
130
|
+
if (debug) console.log("enterSelected, no active suggestion, ignoring")
|
|
131
|
+
}
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
if (debug) console.log("enterSelected")
|
|
135
|
+
enterSuggestion(activeSuggestion.value)
|
|
136
|
+
}
|
|
137
|
+
const selectSuggestion = (num: number): void => {
|
|
138
|
+
if (debug) console.log("selectSuggestion", num)
|
|
139
|
+
if (num >= -1) {
|
|
140
|
+
activeSuggestion.value = num
|
|
141
|
+
}
|
|
142
|
+
if (num === Infinity && (filteredSuggestions.value?.length ?? 0) > 0) {
|
|
143
|
+
activeSuggestion.value = filteredSuggestions.value!.length - 1
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const toggleSuggestions = (): void => {
|
|
148
|
+
isOpen.value ? closeSuggestions() : openSuggestions()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const prevSuggestion = (): void => {
|
|
152
|
+
if (!filteredSuggestions.value) return
|
|
153
|
+
if (activeSuggestion.value > 0) {
|
|
154
|
+
activeSuggestion.value--
|
|
155
|
+
} else if (filteredSuggestions.value) {
|
|
156
|
+
activeSuggestion.value = filteredSuggestions.value.length - 1
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const nextSuggestion = (): void => {
|
|
161
|
+
if (!filteredSuggestions.value) return
|
|
162
|
+
if (activeSuggestion.value >= filteredSuggestions.value.length - 1) {
|
|
163
|
+
activeSuggestion.value = 0
|
|
164
|
+
} else {
|
|
165
|
+
activeSuggestion.value++
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const firstSuggestion = (): void => {
|
|
169
|
+
selectSuggestion(0)
|
|
170
|
+
}
|
|
171
|
+
const lastSuggestion = (): void => {
|
|
172
|
+
selectSuggestion(Infinity)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const cancel = (): void => {
|
|
176
|
+
if (debug) console.log("cancel")
|
|
177
|
+
$inputValue.value = getSuggestionLabel($modelValue.value)
|
|
178
|
+
closeSuggestions()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
watch(() => opts.canOpen, val => {
|
|
183
|
+
if (!val) {
|
|
184
|
+
if (debug) console.log("canOpen changed to false, closing suggestions")
|
|
185
|
+
closeSuggestions()
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
watch(openable, val => {
|
|
190
|
+
if (!val) {
|
|
191
|
+
if (debug) console.log("openable changed to false, closing suggestions")
|
|
192
|
+
closeSuggestions()
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
watch(isValidSuggestion, () => {
|
|
197
|
+
if (!isValidSuggestion.value) {
|
|
198
|
+
if (debug) console.log("isValidSuggestion changed to false, opening suggestions")
|
|
199
|
+
openSuggestions()
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// sync vmodels and vmodel effects
|
|
204
|
+
|
|
205
|
+
watch($modelValue, () => {
|
|
206
|
+
$inputValue.value = getSuggestionLabel($modelValue.value)
|
|
207
|
+
if (debug) console.log("modelValue changed")
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const defaultSuggestionSelector = (suggestions: TSuggestion[], input: string): number => {
|
|
211
|
+
if (input.length === 0) return 0
|
|
212
|
+
let longestMatch
|
|
213
|
+
let ii = -1
|
|
214
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
215
|
+
const suggestion = suggestions[i]
|
|
216
|
+
const label = getSuggestionLabel(suggestion)
|
|
217
|
+
const labelPart = label.slice(0, input.length)
|
|
218
|
+
if (labelPart === input) {
|
|
219
|
+
if (label.length > (longestMatch?.[0]?.length ?? 0)) {
|
|
220
|
+
longestMatch = label
|
|
221
|
+
ii = i
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return ii
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
watch($inputValue, () => {
|
|
229
|
+
if (debug) console.log("input changed:", $inputValue.value, "modelValue:", $modelValue.value)
|
|
230
|
+
if (getSuggestionLabel($modelValue.value) === $inputValue.value) return
|
|
231
|
+
|
|
232
|
+
if (suggestionAvailable.value) {
|
|
233
|
+
if (debug) console.log("input changed, suggestion available, opening suggestions")
|
|
234
|
+
openSuggestions()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!opts.restrictToSuggestions) {
|
|
238
|
+
if (debug) console.log("input changed, unrestricted, setting modelValue")
|
|
239
|
+
$modelValue.value = $inputValue.value
|
|
240
|
+
}
|
|
241
|
+
if (exactlyMatchingSuggestion.value && suggestionsList.value) {
|
|
242
|
+
if (debug) console.log("input changed, exactly matching, setting activeSuggestion")
|
|
243
|
+
selectSuggestion(suggestionsList.value.indexOf(exactlyMatchingSuggestion.value))
|
|
244
|
+
} else {
|
|
245
|
+
if (debug) console.log("input changed, not exactly matching, finding longest match")
|
|
246
|
+
|
|
247
|
+
const i =
|
|
248
|
+
opts.suggestionSelector?.(filteredSuggestions.value ?? [], $inputValue.value)
|
|
249
|
+
?? defaultSuggestionSelector(filteredSuggestions.value ?? [], $inputValue.value)
|
|
250
|
+
|
|
251
|
+
selectSuggestion(i)
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
list: suggestionsList,
|
|
258
|
+
filtered: filteredSuggestions,
|
|
259
|
+
active: activeSuggestion,
|
|
260
|
+
available: suggestionAvailable,
|
|
261
|
+
moreThanOneAvailable: moreThanOneSuggestionAvailable,
|
|
262
|
+
hasExactlyMatching: exactlyMatchingSuggestion,
|
|
263
|
+
/** Whether there is a valid suggestion that can be submitted. If `restrictToSuggestions` is true, this will be true if isValid is true, otherwise this is considered to be true if suggestions are available. */
|
|
264
|
+
hasValidSuggestion: isValidSuggestion,
|
|
265
|
+
openable,
|
|
266
|
+
getLabel: getSuggestionLabel,
|
|
267
|
+
isOpen,
|
|
268
|
+
open: openSuggestions,
|
|
269
|
+
close: closeSuggestions,
|
|
270
|
+
enterSelected,
|
|
271
|
+
enterSuggestion,
|
|
272
|
+
toggle: toggleSuggestions,
|
|
273
|
+
cancel,
|
|
274
|
+
select: selectSuggestion,
|
|
275
|
+
prev: prevSuggestion,
|
|
276
|
+
next: nextSuggestion,
|
|
277
|
+
first: firstSuggestion,
|
|
278
|
+
last: lastSuggestion,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
283
|
+
export function useSuggestionsInputAria(
|
|
284
|
+
id: Ref<string>,
|
|
285
|
+
isOpen: Ref<boolean>,
|
|
286
|
+
activeSuggestion: Ref<number>,
|
|
287
|
+
suggestions: Ref<any | undefined>
|
|
288
|
+
) {
|
|
289
|
+
const ariaInputProps = computed(() => ({
|
|
290
|
+
"aria-autocomplete": suggestions !== undefined ? "both" as const : undefined,
|
|
291
|
+
"aria-controls": suggestions !== undefined ? `suggestions-${id.value}` : undefined,
|
|
292
|
+
role: suggestions ? "combobox" : undefined,
|
|
293
|
+
"aria-expanded": suggestions !== undefined ? isOpen.value : undefined,
|
|
294
|
+
"aria-activedescendant": isOpen.value ? `suggestion-${id.value}-${activeSuggestion.value}` : undefined,
|
|
295
|
+
}))
|
|
296
|
+
return ariaInputProps
|
|
297
|
+
}
|