daisy-ui-kit 2.1.17 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Dropdown.vue +62 -65
- package/components/DropdownButton.vue +16 -0
- package/components/DropdownContent.vue +53 -2
- package/components/DropdownTarget.vue +11 -2
- package/components/Menu.vue +2 -0
- package/components/MenuExpand.vue +78 -0
- package/components/MenuExpandToggle.vue +13 -0
- package/components/MenuItem.vue +16 -0
- package/index.ts +3 -0
- package/package.json +16 -12
- package/utils/random-string.ts +19 -0
package/components/Dropdown.vue
CHANGED
|
@@ -1,96 +1,93 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { provide, ref } from 'vue'
|
|
3
|
+
import { autoUpdate, useFloating } from '@floating-ui/vue'
|
|
3
4
|
import { onClickOutside, syncRefs, useElementHover } from '@vueuse/core'
|
|
5
|
+
import { randomString } from '../utils/random-string'
|
|
4
6
|
|
|
5
7
|
const props = withDefaults(defineProps<{
|
|
6
|
-
|
|
8
|
+
autoFocus?: boolean
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
bottom?: boolean
|
|
11
|
-
right?: boolean
|
|
12
|
-
left?: boolean
|
|
10
|
+
placement?: 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'
|
|
11
|
+
strategy?: 'fixed' | 'absolute'
|
|
13
12
|
|
|
14
|
-
end?: boolean
|
|
15
13
|
hover?: boolean
|
|
16
14
|
delayEnter?: number
|
|
17
15
|
delayLeave?: number
|
|
18
16
|
closeOnClickOutside?: boolean
|
|
19
17
|
}>(), {
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
autoFocus: false,
|
|
19
|
+
placement: 'bottom-start',
|
|
22
20
|
hover: false,
|
|
23
21
|
delayEnter: 0,
|
|
24
22
|
delayLeave: 300,
|
|
25
|
-
closeOnClickOutside:
|
|
23
|
+
closeOnClickOutside: true,
|
|
26
24
|
})
|
|
27
|
-
|
|
25
|
+
// Dropdown Visibility
|
|
26
|
+
const isOpen = defineModel('open', { local: true })
|
|
27
|
+
provide('isDropdownOpen', isOpen)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
watch(() => props.open, (val) => {
|
|
32
|
-
_isOpen.value = val
|
|
33
|
-
})
|
|
34
|
-
const isOpen = computed({
|
|
35
|
-
get: () => _isOpen.value,
|
|
36
|
-
set: (val) => {
|
|
37
|
-
_isOpen.value = val
|
|
38
|
-
if (props.open !== val)
|
|
39
|
-
emit('update:open', val)
|
|
40
|
-
},
|
|
41
|
-
})
|
|
29
|
+
const autoFocus = ref(props.autoFocus)
|
|
30
|
+
provide('dropdownAutoFocus', autoFocus)
|
|
42
31
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
const randomValue = randomString(12)
|
|
33
|
+
const wrapperId = `dropdown-wrapper-${randomValue}`
|
|
34
|
+
const id = `dropdown-${randomValue}`
|
|
35
|
+
provide('dropdownId', id)
|
|
36
|
+
|
|
37
|
+
// set up the floating ui instance
|
|
38
|
+
const buttonEl = ref(null)
|
|
39
|
+
const contentEl = ref(null)
|
|
40
|
+
const floatingConfig = reactive({
|
|
41
|
+
placement: ref(props.placement),
|
|
42
|
+
strategy: ref(props.strategy),
|
|
43
|
+
whileElementsMounted: autoUpdate,
|
|
52
44
|
})
|
|
45
|
+
const { floatingStyles } = useFloating(buttonEl, contentEl, floatingConfig)
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
provide('buttonEl', buttonEl)
|
|
48
|
+
provide('contentEl', contentEl)
|
|
49
|
+
provide('floatingStyles', floatingStyles)
|
|
50
|
+
|
|
51
|
+
// Visibility Utils
|
|
52
|
+
function toggle() {
|
|
53
|
+
isOpen.value = !isOpen.value
|
|
54
|
+
}
|
|
55
|
+
function open() {
|
|
56
|
+
isOpen.value = true
|
|
57
|
+
}
|
|
58
|
+
function close() {
|
|
59
|
+
isOpen.value = false
|
|
60
|
+
}
|
|
61
|
+
provide('toggleDropdown', toggle)
|
|
62
|
+
provide('openDropdown', open)
|
|
63
|
+
provide('closeDropdown', close)
|
|
56
64
|
|
|
57
|
-
const
|
|
58
|
-
const isHovered = ref(false)
|
|
65
|
+
const dropdownWrapper = ref(null)
|
|
59
66
|
|
|
60
67
|
onMounted(() => {
|
|
61
68
|
// Close when clicking outside the element
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
// use a slight delay to avoid conflict with the focus trap in the DropdownContent.
|
|
70
|
+
onClickOutside(contentEl, () => {
|
|
71
|
+
if (props.closeOnClickOutside) {
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
isOpen.value = false
|
|
74
|
+
}, 50)
|
|
75
|
+
}
|
|
65
76
|
})
|
|
66
77
|
|
|
67
78
|
// Sync with top-level isHovered ref. For SSR compatibility.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const shouldBeOpen = computed(() => {
|
|
76
|
-
return isOpen.value || (props.hover && isHovered.value)
|
|
79
|
+
if (props.hover) {
|
|
80
|
+
const hover = useElementHover(dropdownWrapper, {
|
|
81
|
+
delayLeave: props.delayLeave,
|
|
82
|
+
delayEnter: props.delayEnter,
|
|
83
|
+
})
|
|
84
|
+
syncRefs(hover, isOpen)
|
|
85
|
+
}
|
|
77
86
|
})
|
|
78
|
-
|
|
79
|
-
function handleClick(ev: MouseEvent) {
|
|
80
|
-
ev.preventDefault()
|
|
81
|
-
if (ev.target === dropdown.value.children[0])
|
|
82
|
-
isOpen.value = !isOpen.value
|
|
83
|
-
}
|
|
84
87
|
</script>
|
|
85
88
|
|
|
86
89
|
<template>
|
|
87
|
-
<
|
|
88
|
-
<slot />
|
|
89
|
-
</
|
|
90
|
+
<div :id="wrapperId" ref="dropdownWrapper" class="relative inline-block floating-dropdown">
|
|
91
|
+
<slot v-bind="{ toggle, open, close }" />
|
|
92
|
+
</div>
|
|
90
93
|
</template>
|
|
91
|
-
|
|
92
|
-
<style>
|
|
93
|
-
.dropdown > :first-child > * {
|
|
94
|
-
pointer-events: none;
|
|
95
|
-
}
|
|
96
|
-
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
const id = inject('dropdownId')
|
|
5
|
+
const isOpen = inject('isDropdownOpen')
|
|
6
|
+
const toggleDropdown = inject('toggleDropdown')
|
|
7
|
+
const buttonEl = inject('buttonEl')
|
|
8
|
+
|
|
9
|
+
const toggle = toggleDropdown
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<Button :id="id" ref="buttonEl" :aria-expanded="isOpen" aria-haspopup="menu" class="dropdown-button" @click="toggle">
|
|
14
|
+
<slot />
|
|
15
|
+
</Button>
|
|
16
|
+
</template>
|
|
@@ -1,5 +1,56 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
|
4
|
+
|
|
5
|
+
const autoFocus = inject('dropdownAutoFocus')
|
|
6
|
+
const id = inject('dropdownId')
|
|
7
|
+
const isOpen = inject('isDropdownOpen')
|
|
8
|
+
const isOpenDelayed = ref(isOpen.value)
|
|
9
|
+
const contentEl = inject('contentEl')
|
|
10
|
+
const floatingStyles = inject('floatingStyles')
|
|
11
|
+
|
|
12
|
+
// Dropdown Utils
|
|
13
|
+
const toggle = inject('toggleDropdown')
|
|
14
|
+
const open = inject('openDropdown')
|
|
15
|
+
const close = inject('closeDropdown')
|
|
16
|
+
|
|
17
|
+
let activate
|
|
18
|
+
let deactivate
|
|
19
|
+
|
|
20
|
+
if (autoFocus.value) {
|
|
21
|
+
const { activate: _activate, deactivate: _deactivate, hasFocus } = useFocusTrap(contentEl, { immediate: true })
|
|
22
|
+
activate = _activate
|
|
23
|
+
deactivate = _deactivate
|
|
24
|
+
|
|
25
|
+
// hide the dropdown when the focus-trap drops focus (by pressing escape, for example)
|
|
26
|
+
watchEffect(() => {
|
|
27
|
+
if (!hasFocus.value)
|
|
28
|
+
close()
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
// const { activate, deactivate, hasFocus } = useFocusTrap(contentEl, { immediate: true })
|
|
32
|
+
|
|
33
|
+
// synchronize isOpenDelayed with isOpen
|
|
34
|
+
watchEffect(async () => {
|
|
35
|
+
if (isOpen.value) {
|
|
36
|
+
isOpenDelayed.value = true
|
|
37
|
+
if (autoFocus.value) {
|
|
38
|
+
await nextTick()
|
|
39
|
+
activate()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (autoFocus.value) {
|
|
44
|
+
deactivate()
|
|
45
|
+
await nextTick()
|
|
46
|
+
}
|
|
47
|
+
isOpenDelayed.value = false
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
</script>
|
|
51
|
+
|
|
1
52
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<slot />
|
|
53
|
+
<div v-if="isOpen" ref="contentEl" :style="floatingStyles" :aria-labelledby="id" role="menu">
|
|
54
|
+
<slot v-bind="{ toggle, open, close }" />
|
|
4
55
|
</div>
|
|
5
56
|
</template>
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
const id = inject('dropdownId')
|
|
5
|
+
const isOpen = inject('isDropdownOpen')
|
|
6
|
+
const toggle = inject('toggleDropdown')
|
|
7
|
+
const buttonEl = inject('buttonEl')
|
|
8
|
+
</script>
|
|
9
|
+
|
|
1
10
|
<template>
|
|
2
|
-
<
|
|
11
|
+
<div :id="id" ref="buttonEl" :aria-expanded="isOpen" aria-haspopup="menu" class="dropdown-target" @click="toggle">
|
|
3
12
|
<slot />
|
|
4
|
-
</
|
|
13
|
+
</div>
|
|
5
14
|
</template>
|
package/components/Menu.vue
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, provide, ref } from 'vue'
|
|
3
|
+
import { onClickOutside, syncRefs, useElementHover } from '@vueuse/core'
|
|
4
|
+
import { randomString } from '../utils/random-string'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
hover?: boolean
|
|
8
|
+
delayEnter?: number
|
|
9
|
+
delayLeave?: number
|
|
10
|
+
closeOnClickOutside?: boolean
|
|
11
|
+
}>(), {
|
|
12
|
+
position: 'bottom',
|
|
13
|
+
end: false,
|
|
14
|
+
hover: false,
|
|
15
|
+
delayEnter: 0,
|
|
16
|
+
delayLeave: 300,
|
|
17
|
+
closeOnClickOutside: false,
|
|
18
|
+
})
|
|
19
|
+
// "Expand" Visibility
|
|
20
|
+
const isOpen = defineModel('open', { local: true, default: false, type: Boolean })
|
|
21
|
+
provide('isExpandOpen', isOpen)
|
|
22
|
+
|
|
23
|
+
// ids for accessibility
|
|
24
|
+
const randomValue = randomString(12)
|
|
25
|
+
const wrapperId = `expand-wrapper-${randomValue}`
|
|
26
|
+
const id = `expand-${randomValue}`
|
|
27
|
+
provide('expandId', id)
|
|
28
|
+
|
|
29
|
+
// Visibility Utils
|
|
30
|
+
function toggle() {
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
isOpen.value = !isOpen.value
|
|
33
|
+
}, 50)
|
|
34
|
+
}
|
|
35
|
+
function open() {
|
|
36
|
+
isOpen.value = true
|
|
37
|
+
}
|
|
38
|
+
function close() {
|
|
39
|
+
isOpen.value = false
|
|
40
|
+
}
|
|
41
|
+
provide('toggleExpand', toggle)
|
|
42
|
+
provide('openExpand', open)
|
|
43
|
+
provide('closeExpand', close)
|
|
44
|
+
|
|
45
|
+
const expandEl = ref()
|
|
46
|
+
|
|
47
|
+
onMounted(() => {
|
|
48
|
+
// Close when clicking outside the element
|
|
49
|
+
onClickOutside(expandEl, () => {
|
|
50
|
+
if (props.closeOnClickOutside) {
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
isOpen.value = false
|
|
53
|
+
}, 500)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (props.hover) {
|
|
58
|
+
// Sync with top-level isHovered ref. For SSR compatibility.
|
|
59
|
+
const hover = useElementHover(expandEl, {
|
|
60
|
+
delayLeave: props.delayLeave,
|
|
61
|
+
delayEnter: props.delayEnter,
|
|
62
|
+
})
|
|
63
|
+
syncRefs(hover, isOpen)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
function handleClick(ev: MouseEvent) {
|
|
68
|
+
ev.preventDefault()
|
|
69
|
+
if (ev.target === expandEl.value.children[0])
|
|
70
|
+
isOpen.value = !isOpen.value
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<details :id="wrapperId" ref="expandEl" class="dropdown menu-expand" :open="isOpen" @click="handleClick">
|
|
76
|
+
<slot v-bind="{ toggle, open, close }" />
|
|
77
|
+
</details>
|
|
78
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { inject } from 'vue'
|
|
3
|
+
|
|
4
|
+
const id = inject('expandId')
|
|
5
|
+
const isOpen = inject('isExpandOpen')
|
|
6
|
+
const toggle = inject('toggleExpand')
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<summary :id="id" :aria-expanded="isOpen" aria-haspopup="menu" class="menu-expand-toggle" @click.prevent.stop="toggle">
|
|
11
|
+
<slot />
|
|
12
|
+
</summary>
|
|
13
|
+
</template>
|
package/components/MenuItem.vue
CHANGED
|
@@ -12,6 +12,10 @@ defineProps<{
|
|
|
12
12
|
</template>
|
|
13
13
|
|
|
14
14
|
<style lang="postcss">
|
|
15
|
+
/*
|
|
16
|
+
Allow adding .active class to the MenuItem element.
|
|
17
|
+
DaisyUI only supports adding it to the `a` element.
|
|
18
|
+
*/
|
|
15
19
|
.menu-item.active > a, .menu-item.active > span {
|
|
16
20
|
background-color: hsl(var(--n) / var(--tw-bg-opacity));
|
|
17
21
|
color: hsl(var(--nc) / var(--tw-text-opacity));
|
|
@@ -19,4 +23,16 @@ defineProps<{
|
|
|
19
23
|
.menu-item.disabled > a, .menu-item.disabled > span {
|
|
20
24
|
background-color: var(--n)
|
|
21
25
|
}
|
|
26
|
+
|
|
27
|
+
/* Fix padding when putting a Dropdown inside of a menu */
|
|
28
|
+
.menu-item > .floating-dropdown {
|
|
29
|
+
padding: 0;
|
|
30
|
+
}
|
|
31
|
+
.menu-item > .floating-dropdown > .dropdown-button,
|
|
32
|
+
.menu-item > .floating-dropdown > .dropdown-target {
|
|
33
|
+
padding-left: 1rem;
|
|
34
|
+
padding-right: 1rem;
|
|
35
|
+
padding-top: 0.5rem;
|
|
36
|
+
padding-bottom: 0.5rem
|
|
37
|
+
}
|
|
22
38
|
</style>
|
package/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export { default as DrawerContent } from './components/DrawerContent.vue'
|
|
|
32
32
|
export { default as DrawerSide } from './components/DrawerSide.vue'
|
|
33
33
|
export { default as Dropdown } from './components/Dropdown.vue'
|
|
34
34
|
export { default as DropdownContent } from './components/DropdownContent.vue'
|
|
35
|
+
export { default as DropdownButton } from './components/DropdownButton.vue'
|
|
35
36
|
export { default as DropdownTarget } from './components/DropdownTarget.vue'
|
|
36
37
|
export { default as FileInput } from './components/FileInput.vue'
|
|
37
38
|
export { default as Flex } from './components/Flex.vue'
|
|
@@ -60,6 +61,8 @@ export { default as Mask } from './components/Mask.vue'
|
|
|
60
61
|
export { default as Menu } from './components/Menu.vue'
|
|
61
62
|
export { default as MenuItem } from './components/MenuItem.vue'
|
|
62
63
|
export { default as MenuTitle } from './components/MenuTitle.vue'
|
|
64
|
+
export { default as MenuExpand } from './components/MenuExpand.vue'
|
|
65
|
+
export { default as MenuExpandToggle } from './components/MenuExpandToggle.vue'
|
|
63
66
|
export { default as MockupCode } from './components/MockupCode.vue'
|
|
64
67
|
export { default as MockupBrowser } from './components/MockupBrowser.vue'
|
|
65
68
|
export { default as MockupBrowserToolbar } from './components/MockupBrowserToolbar.vue'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daisy-ui-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "nuxi build",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"utils/*",
|
|
21
21
|
"nuxt.js"
|
|
22
22
|
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@floating-ui/vue": "^1.0.2",
|
|
25
|
+
"@vueuse/integrations": "^10.4.0",
|
|
26
|
+
"focus-trap": "^7.5.2"
|
|
27
|
+
},
|
|
23
28
|
"peerDependencies": {
|
|
24
29
|
"@vueuse/core": "^10.2.1",
|
|
25
30
|
"daisyui": "^3",
|
|
@@ -29,10 +34,10 @@
|
|
|
29
34
|
"prismjs": "^1.29.0"
|
|
30
35
|
},
|
|
31
36
|
"devDependencies": {
|
|
32
|
-
"@antfu/eslint-config": "^0.
|
|
37
|
+
"@antfu/eslint-config": "^0.41.0",
|
|
33
38
|
"@headlessui/vue": "^1.7.16",
|
|
34
39
|
"@heroicons/vue": "^2.0.18",
|
|
35
|
-
"@iconify/json": "^2.2.
|
|
40
|
+
"@iconify/json": "^2.2.106",
|
|
36
41
|
"@nuxt/content": "^2.7.2",
|
|
37
42
|
"@nuxt/kit": "link:@nuxt/kit",
|
|
38
43
|
"@nuxtjs/color-mode": "^3.3.0",
|
|
@@ -41,26 +46,25 @@
|
|
|
41
46
|
"@popperjs/core": "^2.11.8",
|
|
42
47
|
"@rovit/popper": "^3.9.0",
|
|
43
48
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
|
44
|
-
"@tailwindcss/forms": "^0.5.
|
|
49
|
+
"@tailwindcss/forms": "^0.5.5",
|
|
45
50
|
"@tailwindcss/line-clamp": "^0.4.4",
|
|
46
51
|
"@tailwindcss/typography": "^0.5.9",
|
|
47
|
-
"@vueuse/core": "^10.
|
|
48
|
-
"@vueuse/
|
|
49
|
-
"@vueuse/nuxt": "^10.3.0",
|
|
52
|
+
"@vueuse/core": "^10.4.0",
|
|
53
|
+
"@vueuse/nuxt": "^10.4.0",
|
|
50
54
|
"autoprefixer": "^10.4.15",
|
|
51
55
|
"cookie": "^0.5.0",
|
|
52
|
-
"daisyui": "^3.
|
|
53
|
-
"eslint": "^8.
|
|
54
|
-
"feathers-pinia": "^
|
|
56
|
+
"daisyui": "^3.6.3",
|
|
57
|
+
"eslint": "^8.48.0",
|
|
58
|
+
"feathers-pinia": "^4.0.0",
|
|
55
59
|
"fuse.js": "^6.6.2",
|
|
56
60
|
"mobile-detect": "^1.4.5",
|
|
57
|
-
"nuxt": "^3.
|
|
61
|
+
"nuxt": "^3.7.0",
|
|
58
62
|
"nuxt-icon": "^0.5.0",
|
|
59
63
|
"ohmyfetch": "^0.4.21",
|
|
60
64
|
"pinia": "^2.1.6",
|
|
61
65
|
"postcss": "^8.4.28",
|
|
62
66
|
"tailwindcss": "^3.3.3",
|
|
63
|
-
"typescript": "^5.
|
|
67
|
+
"typescript": "^5.2.2",
|
|
64
68
|
"unplugin-icons": "^0.16.5",
|
|
65
69
|
"vue": "^3.3.4"
|
|
66
70
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a random string of `length` characters
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const myString = randomString(15); // This will give you a random string of 15 characters
|
|
6
|
+
* @param length The length of the string to generate
|
|
7
|
+
* @returns A random string of `length` characters
|
|
8
|
+
*/
|
|
9
|
+
export function randomString(length: number = 10): string {
|
|
10
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
11
|
+
let result = ''
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < length; i++) {
|
|
14
|
+
const randomIndex = Math.floor(Math.random() * characters.length)
|
|
15
|
+
result += characters[randomIndex]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return result
|
|
19
|
+
}
|