@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,105 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
2
|
+
import { expect, userEvent, within } from "@storybook/test"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const playBasicSelect = async ({ canvasElement, args }: { canvasElement: HTMLElement, args: any }) => {
|
|
6
|
+
const canvas = within(canvasElement)
|
|
7
|
+
const input = canvas.getByLabelText(args.label ?? "", { selector: "input" })
|
|
8
|
+
await userEvent.type(input, "A")
|
|
9
|
+
await expect(canvas.queryByRole("option", { name: "A", selected: true })).toBeInTheDocument()
|
|
10
|
+
await userEvent.type(input, "BCDE")
|
|
11
|
+
// partial match
|
|
12
|
+
await expect(canvas.queryByRole("option", { name: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", selected: true })).toBeInTheDocument()
|
|
13
|
+
await userEvent.keyboard("{Enter}")
|
|
14
|
+
|
|
15
|
+
if (args.values !== undefined) {
|
|
16
|
+
await expect(canvas.getByTestId("model-value").textContent).toBe("")
|
|
17
|
+
// await expect(canvas.getByTestId("model-value")).toHaveTextContent("")
|
|
18
|
+
await expect(canvas.queryByRole("listbox")).not.toBeNull()
|
|
19
|
+
} else {
|
|
20
|
+
await expect(canvas.getByTestId("model-value")).toHaveTextContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
21
|
+
// await expect(canvas.getByTestId("model-value").textContent).toBe("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
22
|
+
// should be closed after enter
|
|
23
|
+
await expect(canvas.queryByRole("listbox")).toBeNull()
|
|
24
|
+
}
|
|
25
|
+
await userEvent.keyboard("{Backspace}")
|
|
26
|
+
await expect(canvas.queryByRole("option",{ selected: true })).toBeInTheDocument()
|
|
27
|
+
await userEvent.clear(input)
|
|
28
|
+
await userEvent.type(input, "unmatched")
|
|
29
|
+
if (!args.suggestionsFilter) {
|
|
30
|
+
await expect(canvas.queryAllByRole("option", { selected: true })).toEqual([])
|
|
31
|
+
}
|
|
32
|
+
await userEvent.clear(input)
|
|
33
|
+
|
|
34
|
+
// first match should be selected if input is empty
|
|
35
|
+
await expect(await canvas.findByRole("option", { name: "A", selected: true })).toBeInTheDocument()
|
|
36
|
+
if (args.restrictToSuggestions && args.values === undefined) {
|
|
37
|
+
// should still equal last selected
|
|
38
|
+
await expect(canvas.getByTestId("model-value").textContent).toBe("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await userEvent.keyboard("{Enter}")
|
|
42
|
+
if (args.values === undefined) {
|
|
43
|
+
await expect(canvas.getByTestId("model-value").textContent).toBe("A")
|
|
44
|
+
}
|
|
45
|
+
await userEvent.clear(input)
|
|
46
|
+
await userEvent.keyboard("AB{Escape}")
|
|
47
|
+
await expect(canvas.queryByRole("listbox")).toBeNull()
|
|
48
|
+
if (args.values === undefined) {
|
|
49
|
+
if (args.restrictToSuggestions) {
|
|
50
|
+
await expect(canvas.getByTestId("model-value").textContent).toBe("A")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const playBasicKeyboardSelect = async ({ canvasElement, args }: { canvasElement: HTMLElement , args: any }) => {
|
|
56
|
+
const canvas = within(canvasElement)
|
|
57
|
+
const input = canvas.getByLabelText(args.label ?? "", { selector: "input" })
|
|
58
|
+
await userEvent.clear(input)
|
|
59
|
+
await userEvent.type(input, "A")
|
|
60
|
+
await expect(canvas.queryByRole("option", { name: "A", selected: true })).toBeInTheDocument()
|
|
61
|
+
await userEvent.keyboard("{ArrowDown}")
|
|
62
|
+
await expect(canvas.queryByRole("option", { name: "AB", selected: true })).toBeInTheDocument()
|
|
63
|
+
await userEvent.keyboard("{ArrowUp}")
|
|
64
|
+
await expect(canvas.queryByRole("option", { name: "A", selected: true })).toBeInTheDocument()
|
|
65
|
+
|
|
66
|
+
// loops to last item
|
|
67
|
+
await userEvent.keyboard("{ArrowUp}")
|
|
68
|
+
await expect(canvas.queryByRole("option", { name: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", selected: true })).toBeInTheDocument()
|
|
69
|
+
|
|
70
|
+
// loops back to the first item
|
|
71
|
+
await userEvent.keyboard("{ArrowDown}")
|
|
72
|
+
await expect(canvas.queryByRole("option", { name: "A", selected: true })).toBeInTheDocument()
|
|
73
|
+
|
|
74
|
+
// goes to last
|
|
75
|
+
await userEvent.keyboard("{PageDown}")
|
|
76
|
+
await expect(canvas.queryByRole("option", { name: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", selected: true })).toBeInTheDocument()
|
|
77
|
+
// goes to first
|
|
78
|
+
await userEvent.keyboard("{PageUp}")
|
|
79
|
+
await expect(canvas.queryByRole("option", { name: "A", selected: true })).toBeInTheDocument()
|
|
80
|
+
|
|
81
|
+
const testOpen = async (key: string) => {
|
|
82
|
+
await userEvent.keyboard("{Escape}")
|
|
83
|
+
await expect(canvas.queryByRole("listbox")).toBeNull()
|
|
84
|
+
await userEvent.keyboard(`{${key}}`)
|
|
85
|
+
await expect(canvas.queryByRole("listbox")).toBeInTheDocument()
|
|
86
|
+
}
|
|
87
|
+
await testOpen("ArrowDown")
|
|
88
|
+
await testOpen("ArrowUp")
|
|
89
|
+
await testOpen("PageDown")
|
|
90
|
+
await testOpen("PageUp")
|
|
91
|
+
}
|
|
92
|
+
export const playBasicClickSelect = async ({ canvasElement, args }: { canvasElement: HTMLElement, args: any }) => {
|
|
93
|
+
const canvas = within(canvasElement)
|
|
94
|
+
const input = canvas.getByLabelText(args.label ?? "", { selector: "input" })
|
|
95
|
+
await userEvent.clear(input)
|
|
96
|
+
await userEvent.type(input, "A")
|
|
97
|
+
await userEvent.click(canvas.getByRole("option", { name: "AB" }))
|
|
98
|
+
if (args.values === undefined) {
|
|
99
|
+
await expect(canvas.getByTestId("model-value").textContent).toBe("AB")
|
|
100
|
+
await expect(canvas.queryByRole("listbox")).toBeNull()
|
|
101
|
+
} else {
|
|
102
|
+
await expect(canvas.getByTestId("values")).toHaveTextContent(/AB$/)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* Autogenerated Index */
|
|
2
|
+
|
|
3
|
+
export { useAccesibilityOutline } from "./useAccesibilityOutline.js"
|
|
4
|
+
export { useAriaLabel } from "./useAriaLabel.js"
|
|
5
|
+
export { useDarkMode } from "./useDarkMode.js"
|
|
6
|
+
export { useDivideAttrs } from "./useDivideAttrs.js"
|
|
7
|
+
export { useGlobalResizeObserver } from "./useGlobalResizeObserver.js"
|
|
8
|
+
export { useInjectedDarkMode } from "./useInjectedDarkMode.js"
|
|
9
|
+
export { useNotificationHandler } from "./useNotificationHandler.js"
|
|
10
|
+
export { useScrollNearContainerEdges } from "./useScrollNearContainerEdges.js"
|
|
11
|
+
export { useSetupDarkMode } from "./useSetupDarkMode.js"
|
|
12
|
+
export { useShowDevOnlyKey } from "./useShowDevOnlyKey.js"
|
|
13
|
+
export { useSuggestions } from "./useSuggestions.js"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { castType } from "@alanscodelog/utils/castType.js"
|
|
2
|
+
import { onBeforeUnmount, onMounted, type Ref, ref, watch } from "vue"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns a reactive ref that indicates whether the user presses a key *and* produced a change in focus. Used for only providing a `:focus` outline when the user is navigating via the keyboard.
|
|
6
|
+
*
|
|
7
|
+
* Can be optionally passed a reactive ref to enable/disabled the calculation (e.g. based on a user setting). This is returned back by the function in case you want to just use the control variable it auto creates. Event listeners are automatically attached/detached when this is changed.
|
|
8
|
+
*
|
|
9
|
+
* In setup:
|
|
10
|
+
* ```ts
|
|
11
|
+
* const el = ref(null)
|
|
12
|
+
*
|
|
13
|
+
* const {outline, control: outlineControl} = useAccesibilityOutline(el)
|
|
14
|
+
* outlineControl.value = userSettings.outlineOnlyOnNavigation
|
|
15
|
+
* // or
|
|
16
|
+
* useAccesibilityOutline(el, userSettings.outlineOnlyOnNavigation)
|
|
17
|
+
*
|
|
18
|
+
* const classes = computed(() =>( {
|
|
19
|
+
* // if setting is false => always outline
|
|
20
|
+
* outline: !userSettings.outlineOnlyOnNavigation || outline.value
|
|
21
|
+
* }))
|
|
22
|
+
*
|
|
23
|
+
* return {
|
|
24
|
+
* classes,
|
|
25
|
+
* el
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* In the root app component. Note it requires the element have a tabindex.
|
|
30
|
+
* ```html
|
|
31
|
+
* <div id="app" :class="classes" ref="el" tabindex="-1"></div>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* In style of any subcomponents we can now target `:focus` only when the user is navigating with the keyboard.
|
|
35
|
+
*
|
|
36
|
+
* ```css
|
|
37
|
+
* .outline button:focus {
|
|
38
|
+
* ....
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
43
|
+
// TODO tailwind example/plugin?
|
|
44
|
+
export function useAccesibilityOutline(
|
|
45
|
+
target: Ref<HTMLElement | null>,
|
|
46
|
+
enable: Ref<boolean> = ref(true),
|
|
47
|
+
): Record<"outline" | "control", Ref<boolean>> {
|
|
48
|
+
const outline = ref(false)
|
|
49
|
+
const awaitingFocus = ref(false)
|
|
50
|
+
const keydown = (_e: KeyboardEvent): void => {
|
|
51
|
+
awaitingFocus.value = true
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
awaitingFocus.value = false
|
|
54
|
+
}, 0)
|
|
55
|
+
}
|
|
56
|
+
const mousedown = (_e: MouseEvent): void => {
|
|
57
|
+
awaitingFocus.value = false
|
|
58
|
+
outline.value = false
|
|
59
|
+
}
|
|
60
|
+
const focusin = (_e: FocusEvent): void => {
|
|
61
|
+
if (awaitingFocus.value && enable.value) {
|
|
62
|
+
outline.value = true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let canAttach = false
|
|
66
|
+
const attach = (): void => {
|
|
67
|
+
if (!canAttach) return
|
|
68
|
+
castType<Ref<HTMLElement>>(target.value)
|
|
69
|
+
if (!target.value) return
|
|
70
|
+
target.value.addEventListener("focusin", focusin)
|
|
71
|
+
target.value.addEventListener("keydown", keydown)
|
|
72
|
+
target.value.addEventListener("mousedown", mousedown)
|
|
73
|
+
}
|
|
74
|
+
const detach = (): void => {
|
|
75
|
+
if (target.value) {
|
|
76
|
+
castType<Ref<HTMLElement>>(target.value)
|
|
77
|
+
target.value.removeEventListener("focusin", focusin)
|
|
78
|
+
target.value.removeEventListener("keydown", keydown)
|
|
79
|
+
target.value.removeEventListener("mousedown", mousedown)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
watch(enable, (newVal, oldVal) => {
|
|
84
|
+
if (newVal === oldVal) return
|
|
85
|
+
if (newVal) {
|
|
86
|
+
attach()
|
|
87
|
+
} else {
|
|
88
|
+
outline.value = false
|
|
89
|
+
detach()
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
onMounted(() => {
|
|
94
|
+
canAttach = true
|
|
95
|
+
if (enable.value) {
|
|
96
|
+
attach()
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
onBeforeUnmount(() => {
|
|
100
|
+
canAttach = false
|
|
101
|
+
detach()
|
|
102
|
+
})
|
|
103
|
+
return { outline, control: enable }
|
|
104
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { computed, type ComputedRef, onMounted, ref } from "vue"
|
|
2
|
+
|
|
3
|
+
/** Returns a computed ref that creates aria-label and aria-labelledby with the correct id for labelledby. */
|
|
4
|
+
export const useAriaLabel = (
|
|
5
|
+
props: { id?: string, label?: string },
|
|
6
|
+
fallbackId?: string
|
|
7
|
+
|
|
8
|
+
): ComputedRef<Record<string, string | undefined>> => {
|
|
9
|
+
const id = computed(() => props.id ?? fallbackId)
|
|
10
|
+
const labelExists = ref(false)
|
|
11
|
+
|
|
12
|
+
const aria = computed(() => ({
|
|
13
|
+
"aria-label": labelExists.value ? undefined : props.label,
|
|
14
|
+
"aria-labelledby": labelExists.value ? `label-${id.value}` : undefined,
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
onMounted(() => {
|
|
18
|
+
if (id.value && document.querySelector(`#label-${id.value}`)) {
|
|
19
|
+
labelExists.value = true
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
return aria
|
|
23
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { computed, type InjectionKey, onMounted, provide, type Ref,ref, watch } from "vue"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const defaultLocalStorageKey = "prefersColorSchemeDark"
|
|
5
|
+
const defaultOrder = ["system", "dark", "light"] as const
|
|
6
|
+
/** @deprecated */
|
|
7
|
+
const injectionKey = Symbol("isDarkMode") as InjectionKey<Ref<boolean>>
|
|
8
|
+
/** @deprecated */
|
|
9
|
+
const manualInjectionKey = Symbol("manualDarkMode") as InjectionKey<Ref<boolean | undefined>>
|
|
10
|
+
const commandsInjectionKey = Symbol("darkModeCommands") as InjectionKey<DarkModeCommands>
|
|
11
|
+
const stateInjectionKey = Symbol("darkModeState") as InjectionKey<DarkModeState>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Use `useSetupDarkMode` instead.
|
|
15
|
+
*
|
|
16
|
+
* A composable for setting up dark mode that automatically takes care of saving the user's preference.
|
|
17
|
+
*
|
|
18
|
+
* See the returned utilities for more details.
|
|
19
|
+
*
|
|
20
|
+
* It also provides injection keys with a ref so it can be accessed in deep nested components if needed. Alternatively you can use `useInjectedDarkMode` for ease of use.
|
|
21
|
+
*
|
|
22
|
+
* Note that this should only be called once at the root of the app.
|
|
23
|
+
*/
|
|
24
|
+
export const useDarkMode = ({
|
|
25
|
+
useLocalStorage = true,
|
|
26
|
+
darkModeOrder = defaultOrder,
|
|
27
|
+
/** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
|
|
28
|
+
isClientSide = true
|
|
29
|
+
}: DarkModeOptions = {}): DarkModeState & DarkModeCommands => {
|
|
30
|
+
const systemDarkMode = ref(false)
|
|
31
|
+
const manualDarkMode = ref<boolean | undefined>(undefined)
|
|
32
|
+
|
|
33
|
+
if (useLocalStorage && isClientSide) {
|
|
34
|
+
watch(manualDarkMode, () => {
|
|
35
|
+
localStorage.setItem(defaultLocalStorageKey, manualDarkMode.value ? "true" : "false")
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const darkMode = computed(() => manualDarkMode.value ?? systemDarkMode.value)
|
|
40
|
+
const darkModeState = computed(() =>
|
|
41
|
+
manualDarkMode.value === undefined
|
|
42
|
+
? "system"
|
|
43
|
+
: manualDarkMode.value
|
|
44
|
+
? "dark"
|
|
45
|
+
: "light"
|
|
46
|
+
)
|
|
47
|
+
// todo move to useinjected
|
|
48
|
+
function setDarkMode(value: "dark" | "light" | "system"): void {
|
|
49
|
+
manualDarkMode.value =
|
|
50
|
+
value === "dark"
|
|
51
|
+
? true
|
|
52
|
+
: value === "light"
|
|
53
|
+
? false
|
|
54
|
+
: undefined
|
|
55
|
+
}
|
|
56
|
+
function cycleDarkMode(): void {
|
|
57
|
+
const index = darkModeOrder.indexOf(darkModeState.value)
|
|
58
|
+
|
|
59
|
+
if (index === 2) {
|
|
60
|
+
setDarkMode(darkModeOrder[0])
|
|
61
|
+
} else {
|
|
62
|
+
setDarkMode(darkModeOrder[index + 1])
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onMounted(() => {
|
|
67
|
+
window.matchMedia("(prefers-color-scheme: dark)")
|
|
68
|
+
.addEventListener("change", ({ matches }) => {
|
|
69
|
+
if (matches) {
|
|
70
|
+
systemDarkMode.value = true
|
|
71
|
+
} else {
|
|
72
|
+
systemDarkMode.value = false
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
if (useLocalStorage !== false) {
|
|
76
|
+
const key = typeof useLocalStorage === "string" ? useLocalStorage : defaultLocalStorageKey
|
|
77
|
+
const value = localStorage.getItem(key)
|
|
78
|
+
|
|
79
|
+
if (value === "true") {
|
|
80
|
+
manualDarkMode.value = true
|
|
81
|
+
} else if (value === "false") {
|
|
82
|
+
manualDarkMode.value = false
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
provide(injectionKey, darkMode)
|
|
87
|
+
provide(manualInjectionKey, manualDarkMode)
|
|
88
|
+
|
|
89
|
+
provide(stateInjectionKey, {
|
|
90
|
+
darkMode,
|
|
91
|
+
darkModeState,
|
|
92
|
+
manualDarkMode,
|
|
93
|
+
systemDarkMode,
|
|
94
|
+
})
|
|
95
|
+
provide(commandsInjectionKey, {
|
|
96
|
+
setDarkMode,
|
|
97
|
+
cycleDarkMode,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
darkMode,
|
|
102
|
+
darkModeState,
|
|
103
|
+
setDarkMode,
|
|
104
|
+
cycleDarkMode,
|
|
105
|
+
manualDarkMode,
|
|
106
|
+
systemDarkMode,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export const defaultDarkModeOrder = defaultOrder
|
|
110
|
+
|
|
111
|
+
export const isDarkModeInjectionKey = injectionKey
|
|
112
|
+
|
|
113
|
+
export const manualDarkModeInjectionKey = manualInjectionKey
|
|
114
|
+
|
|
115
|
+
export const darkModeCommandsInjectionKey = commandsInjectionKey
|
|
116
|
+
|
|
117
|
+
export const darkModeStateInjectionKey = stateInjectionKey
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
export type DarkModeOptions = {
|
|
121
|
+
/* Whether to save the manual dark mode to local storage. Uses the key "prefersColorSchemeDark" by default. You can pass a key instead of true to use that as the key instead. */
|
|
122
|
+
useLocalStorage?: boolean | string
|
|
123
|
+
/* The order of the string dark modes when using `cycleDarkMode`. Defaults to `["system", "dark", "light"]` */
|
|
124
|
+
darkModeOrder?: readonly ("system" | "dark" | "light")[]
|
|
125
|
+
/** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
|
|
126
|
+
isClientSide?: boolean
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
export interface DarkModeCommands {
|
|
131
|
+
setDarkMode: (value: "dark" | "light" | "system") => void
|
|
132
|
+
cycleDarkMode: () => void
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
export interface DarkModeState {
|
|
137
|
+
/** Whether the dark mode should be enabled or not */
|
|
138
|
+
darkMode: Ref<boolean>
|
|
139
|
+
/** The current state of the darkMode but as a string (dark, light, system) */
|
|
140
|
+
darkModeState: Ref<"dark" | "light" | "system">
|
|
141
|
+
/** The value of the manuably controllable dark mode. You can set this to true/false or undefined to allow the systemDarkMode to take priority. Alternatively use setDarkMode instead. */
|
|
142
|
+
manualDarkMode: Ref<boolean | undefined>
|
|
143
|
+
/** The value of the system dark mode. This is automatically set depending on the user's `prefer-color-scheme` and should not be set directly. */
|
|
144
|
+
systemDarkMode: Ref<boolean>
|
|
145
|
+
|
|
146
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { keys } from "@alanscodelog/utils/keys.js"
|
|
2
|
+
import { computed, type Ref, useAttrs } from "vue"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Allows users to more easily pass attributes to different parts of a component. Suppose a component has an input and a wrapper and you want most attributes to go to the input, but any that start with `wrapper` to go to the wrapper.
|
|
6
|
+
*
|
|
7
|
+
* You can do:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const extraAttrs = useDivideAttrs(["wrapper"] as const)
|
|
11
|
+
* ```
|
|
12
|
+
* This will correctly remove the start of the key. So if the user passes:
|
|
13
|
+
* ```vue
|
|
14
|
+
* <your-component regular-attr wrapper-attr wrapperAttrs/>
|
|
15
|
+
* ```
|
|
16
|
+
* You will get a ref like:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* extraAttrs.value = {
|
|
20
|
+
* attrs: { regular-attr: true },
|
|
21
|
+
* wrapperAttrs: { attr: true, Attrs: true },
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* Additionally attributes are properly reactive, tough this is probably a bit expensive given they weren't meant to be reactive.
|
|
25
|
+
*
|
|
26
|
+
* Note that if using multi-word prefixes, like `inner-wrapper`, they will appear as the key `inner-wrapperAttrs` internally, but users will be able to correctly pass `inner-wrapper-prop`. Aditionally these can be passed down to deeper components, allowing easy wrapping.
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const useDivideAttrs = <T extends readonly string[]>(divisionKeys: T): Ref<Record<`${T[number]}Attrs` | "attrs", Record<string, any>>> => computed(() => {
|
|
30
|
+
const attrs: Record<string, any> = useAttrs()
|
|
31
|
+
const res: any = { attrs: {} }
|
|
32
|
+
const unseen = keys(attrs)
|
|
33
|
+
for (const key of divisionKeys) {
|
|
34
|
+
res[`${key}Attrs`] = {}
|
|
35
|
+
for (let i = 0; i < unseen.length; i++) {
|
|
36
|
+
const attrKey = unseen[i]
|
|
37
|
+
if (attrKey.startsWith(`${key}-`)) {
|
|
38
|
+
res[`${key}Attrs`][attrKey.slice(key.length + 1)] = attrs[attrKey]
|
|
39
|
+
unseen.splice(i, 1)
|
|
40
|
+
i--
|
|
41
|
+
} else if (attrKey.startsWith(key)) {
|
|
42
|
+
res[`${key}Attrs`][attrKey.slice(key.length)] = attrs[attrKey]
|
|
43
|
+
unseen.splice(i, 1)
|
|
44
|
+
i--
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const attrKey of unseen) {
|
|
49
|
+
res.attrs[attrKey] = attrs[attrKey]
|
|
50
|
+
}
|
|
51
|
+
return res
|
|
52
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AnyFunction } from "@alanscodelog/utils"
|
|
2
|
+
import { onBeforeUnmount, onMounted, type Ref, watch } from "vue"
|
|
3
|
+
|
|
4
|
+
import { globalResizeObserver } from "../globalResizeObserver.js"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const useGlobalResizeObserver = (el: Ref<HTMLElement | null>, cb: AnyFunction): void => {
|
|
8
|
+
let wasMounted = false
|
|
9
|
+
// in case we accidentally use it in an onMounted hook or somewhere where the element already exists
|
|
10
|
+
if (el.value) {
|
|
11
|
+
wasMounted = true
|
|
12
|
+
globalResizeObserver.observe(el.value, cb)
|
|
13
|
+
}
|
|
14
|
+
onMounted(() => {
|
|
15
|
+
watch(el, (_newval, oldval) => {
|
|
16
|
+
if (el.value) {
|
|
17
|
+
globalResizeObserver.observe(el.value, cb)
|
|
18
|
+
} else {
|
|
19
|
+
if (oldval) {
|
|
20
|
+
globalResizeObserver.unobserve(oldval, cb)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
if (el.value && !wasMounted) {
|
|
25
|
+
globalResizeObserver.observe(el.value, cb)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
onBeforeUnmount(() => {
|
|
29
|
+
if (el.value) {
|
|
30
|
+
globalResizeObserver.unobserve(el.value, cb)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { inject } from "vue"
|
|
2
|
+
|
|
3
|
+
import { type DarkModeCommands, darkModeCommandsInjectionKey, type DarkModeState,darkModeStateInjectionKey } from "./useDarkMode.js"
|
|
4
|
+
|
|
5
|
+
export function useInjectedDarkMode(): DarkModeState & DarkModeCommands {
|
|
6
|
+
const darkModeState = inject(darkModeStateInjectionKey)
|
|
7
|
+
const darkModeCommands = inject(darkModeCommandsInjectionKey)
|
|
8
|
+
if (!darkModeState || !darkModeCommands) {
|
|
9
|
+
throw new Error("useInjectedDarkMode could not find provided state. Did you use useSetupDarkMode?")
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
...darkModeState,
|
|
13
|
+
...darkModeCommands,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { inject } from "vue"
|
|
2
|
+
|
|
3
|
+
import { i18nInjectionKey } from "./useSetupI18n.js"
|
|
4
|
+
import { type TranslationFunction } from "./useSetupI18n.js"
|
|
5
|
+
|
|
6
|
+
export function useInjectedI18n(): TranslationFunction {
|
|
7
|
+
const val = inject(i18nInjectionKey, undefined)
|
|
8
|
+
if (val === undefined) throw new Error("witchcraft/ui: i18n is not injected. See useSetupI18n.")
|
|
9
|
+
return val
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { inject } from "vue"
|
|
2
|
+
|
|
3
|
+
import { languageLocaleInjectionKey , timeLocaleInjectionKey , type useSetupLocale } from "./useSetupLocale.js"
|
|
4
|
+
|
|
5
|
+
export function useInjectedLocale(): ReturnType<typeof useSetupLocale> {
|
|
6
|
+
const timeLocale = inject(timeLocaleInjectionKey)
|
|
7
|
+
const languageLocale = inject(languageLocaleInjectionKey)
|
|
8
|
+
if (timeLocale === undefined || languageLocale === undefined) {
|
|
9
|
+
throw new Error("useInjectedLocale could not find provided state. Did you use useSetupLocale?")
|
|
10
|
+
}
|
|
11
|
+
function setLanguageLocale(value: string): void {
|
|
12
|
+
languageLocale!.value = value
|
|
13
|
+
}
|
|
14
|
+
function setTimeLocale(value: string): void {
|
|
15
|
+
timeLocale!.value = value
|
|
16
|
+
}
|
|
17
|
+
return{
|
|
18
|
+
timeLocale,
|
|
19
|
+
languageLocale,
|
|
20
|
+
setLanguageLocale,
|
|
21
|
+
setTimeLocale,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { NotificationHandler } from "../helpers/NotificationHandler.js"
|
|
2
|
+
|
|
3
|
+
let first = true
|
|
4
|
+
const configuration: {
|
|
5
|
+
notificationHandler: NotificationHandler
|
|
6
|
+
isClientSide: boolean
|
|
7
|
+
} = {
|
|
8
|
+
notificationHandler: undefined as any,
|
|
9
|
+
isClientSide: undefined as any
|
|
10
|
+
}
|
|
11
|
+
export const useNotificationHandler = (
|
|
12
|
+
handler?: NotificationHandler,
|
|
13
|
+
/** True by default, should be passed import.meta.client if using nuxt, or false when running server side. */
|
|
14
|
+
isClientSide?: boolean
|
|
15
|
+
): NotificationHandler => {
|
|
16
|
+
const clientSide = isClientSide ?? configuration.isClientSide
|
|
17
|
+
|
|
18
|
+
if (!clientSide) return undefined as any
|
|
19
|
+
if (first) {
|
|
20
|
+
first = false
|
|
21
|
+
configuration.isClientSide = clientSide ?? true
|
|
22
|
+
if (handler) {
|
|
23
|
+
configuration.notificationHandler = handler
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error("You must set the notification handler to use at least once before using it.")
|
|
26
|
+
}
|
|
27
|
+
} else if (handler || isClientSide) {
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.warn("You can only configure useNotificationHandler once. (Note that there might be false positive during HMR).")
|
|
30
|
+
}
|
|
31
|
+
return configuration.notificationHandler
|
|
32
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { StoryObj } from "@storybook/vue3"
|
|
2
|
+
import { onMounted, ref } from "vue"
|
|
3
|
+
|
|
4
|
+
import { useScrollNearContainerEdges } from "./useScrollNearContainerEdges.js"
|
|
5
|
+
|
|
6
|
+
import { twMerge } from "../utils/twMerge.js"
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "Composables/ScrollNearContainerEdges",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
type Story = StoryObj // & StoryObj<typeof extraArgs>
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
15
|
+
export const Primary: Story = {
|
|
16
|
+
render: args => ({
|
|
17
|
+
setup: () => {
|
|
18
|
+
const containerEl = ref<HTMLElement | null>(null)
|
|
19
|
+
const {
|
|
20
|
+
resetScrollIndicator,
|
|
21
|
+
scrollEdges,
|
|
22
|
+
endScroll,
|
|
23
|
+
scrollIndicator,
|
|
24
|
+
isScrolling
|
|
25
|
+
} = useScrollNearContainerEdges({
|
|
26
|
+
containerEl,
|
|
27
|
+
scrollMargin: 20,
|
|
28
|
+
outerScrollMargin: 20,
|
|
29
|
+
})
|
|
30
|
+
const pos = ref({ x: 0, y: 0 })
|
|
31
|
+
onMounted(() => {
|
|
32
|
+
pos.value.x = containerEl.value!.getBoundingClientRect().left
|
|
33
|
+
pos.value.y = containerEl.value!.getBoundingClientRect().top
|
|
34
|
+
})
|
|
35
|
+
const moveDrag = (e: MouseEvent): void => {
|
|
36
|
+
pos.value.x = e.clientX
|
|
37
|
+
pos.value.y = e.clientY
|
|
38
|
+
scrollEdges(e.clientX, e.clientY)
|
|
39
|
+
}
|
|
40
|
+
const endDrag = (_e: MouseEvent): void => {
|
|
41
|
+
endScroll()
|
|
42
|
+
document.removeEventListener("mousemove", moveDrag)
|
|
43
|
+
document.removeEventListener("mouseup", endDrag)
|
|
44
|
+
}
|
|
45
|
+
const startDrag = (_e: MouseEvent): void => {
|
|
46
|
+
document.addEventListener("mousemove", moveDrag)
|
|
47
|
+
document.addEventListener("mouseup", endDrag)
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
args,
|
|
51
|
+
containerEl,
|
|
52
|
+
resetScrollIndicator,
|
|
53
|
+
scrollEdges,
|
|
54
|
+
endScroll,
|
|
55
|
+
startDrag,
|
|
56
|
+
scrollIndicator,
|
|
57
|
+
isScrolling,
|
|
58
|
+
twMerge,
|
|
59
|
+
pos
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
template: `
|
|
64
|
+
<div>
|
|
65
|
+
<p>Scroll the container by dragging the red box (which represents the dragging mouse position).</p>
|
|
66
|
+
<p>It should drag slow, then faster as the mouse nears the edges and scroll indicators should appear on the scrolling edges.</p>
|
|
67
|
+
</div>
|
|
68
|
+
<div
|
|
69
|
+
:class="twMerge(
|
|
70
|
+
'relative flex flex-col max-h-[300px] max-w-[300px] border-2 border-red-500',
|
|
71
|
+
isScrolling && 'after:content-[\\'\\'] after:absolute after:inset-0 after:border-transparent after:border-[15px]',
|
|
72
|
+
scrollIndicator.right && 'after:border-r-accent-500/60',
|
|
73
|
+
scrollIndicator.down && 'after:border-b-accent-500/60',
|
|
74
|
+
scrollIndicator.left && 'after:border-l-accent-500/60',
|
|
75
|
+
scrollIndicator.up && 'after:border-t-accent-500/60',
|
|
76
|
+
)"
|
|
77
|
+
|
|
78
|
+
>
|
|
79
|
+
<div
|
|
80
|
+
class="overflow-auto"
|
|
81
|
+
ref="containerEl"
|
|
82
|
+
>
|
|
83
|
+
<div class="h-[1000px] w-[1000px]"/>
|
|
84
|
+
<div
|
|
85
|
+
@mousedown="startDrag"
|
|
86
|
+
:style="\`top:\${pos.y}px; left:\${pos.x}px;\`"
|
|
87
|
+
class="h-[20px] w-[20px] -ml-[10px] -mt-[10px] bg-red-500 cursor-move fixed"
|
|
88
|
+
></div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
`,
|
|
92
|
+
}),
|
|
93
|
+
}
|