daisy-ui-kit 5.0.0-pre.6 → 5.0.0-pre.8
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/app/components/Button.vue +17 -12
- package/app/components/Calendar.vue +1 -1
- package/app/components/CalendarInput.vue +1 -1
- package/app/components/Checkbox.vue +2 -0
- package/app/components/Dropdown.vue +4 -4
- package/app/components/Input.vue +26 -9
- package/app/components/Label.vue +40 -4
- package/app/components/MockupPhone.vue +8 -2
- package/app/components/Radio.vue +3 -1
- package/app/components/Select.vue +1 -1
- package/app/components/ThemeController.vue +46 -0
- package/app/components/ThemeProvider.vue +287 -0
- package/app/components/ThemeTile.vue +50 -0
- package/app/components/Toggle.vue +3 -1
- package/nuxt.js +7 -1
- package/package.json +16 -14
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
3
|
|
|
4
4
|
const props = defineProps<{
|
|
5
5
|
is?: string
|
|
@@ -42,25 +42,30 @@ const props = defineProps<{
|
|
|
42
42
|
type?: 'button' | 'submit' | 'reset'
|
|
43
43
|
}>()
|
|
44
44
|
|
|
45
|
-
// Accessibility
|
|
46
|
-
const
|
|
45
|
+
// Accessibility: Determine if rendering a native button
|
|
46
|
+
const isButton = computed(() => (props.is || 'button') === 'button')
|
|
47
|
+
|
|
48
|
+
// Accessibility: Keyboard event handler for custom elements
|
|
49
|
+
function onKeydown(e: KeyboardEvent) {
|
|
47
50
|
if (props.disabled) {
|
|
48
|
-
return
|
|
49
|
-
'tabindex': -1,
|
|
50
|
-
'role': 'button',
|
|
51
|
-
'aria-disabled': true,
|
|
52
|
-
}
|
|
51
|
+
return
|
|
53
52
|
}
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (e.code === 'Space' || e.code === 'Enter' || e.key === ' ' || e.key === 'Enter') {
|
|
54
|
+
e.preventDefault()
|
|
55
|
+
// @ts-expect-error: $el may not exist yet
|
|
56
|
+
e.currentTarget?.click?.()
|
|
56
57
|
}
|
|
57
|
-
}
|
|
58
|
+
}
|
|
58
59
|
</script>
|
|
59
60
|
|
|
60
61
|
<template>
|
|
61
62
|
<component
|
|
62
63
|
:is="is || 'button'"
|
|
63
64
|
:type="type"
|
|
65
|
+
:disabled="isButton && disabled ? true : undefined"
|
|
66
|
+
:aria-disabled="!isButton && disabled ? true : undefined"
|
|
67
|
+
:tabindex="!isButton ? (disabled ? -1 : 0) : undefined"
|
|
68
|
+
:role="!isButton ? 'button' : undefined"
|
|
64
69
|
class="btn"
|
|
65
70
|
:class="{
|
|
66
71
|
'join-item': join,
|
|
@@ -106,7 +111,7 @@ const disabledAttrs = computed(() => {
|
|
|
106
111
|
'no-animation': noAnimation,
|
|
107
112
|
'btn-active': !disabled && active,
|
|
108
113
|
}"
|
|
109
|
-
|
|
114
|
+
@keydown="!isButton ? onKeydown : undefined"
|
|
110
115
|
>
|
|
111
116
|
<slot />
|
|
112
117
|
</component>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { PikadayOptions } from 'pikaday'
|
|
3
3
|
import { onMounted, ref } from 'vue'
|
|
4
|
-
import { usePikaday } from '~/composables/
|
|
4
|
+
import { usePikaday } from '~/composables/use-pikaday'
|
|
5
5
|
import CalendarSkeleton from './CalendarSkeleton.vue'
|
|
6
6
|
|
|
7
7
|
const props = defineProps<{
|
|
@@ -3,7 +3,7 @@ import type { PikadayOptions } from 'pikaday'
|
|
|
3
3
|
import type { ComponentPublicInstance } from 'vue'
|
|
4
4
|
import { onMounted, ref, watch } from 'vue'
|
|
5
5
|
|
|
6
|
-
import { usePikaday } from '~/composables/
|
|
6
|
+
import { usePikaday } from '~/composables/use-pikaday'
|
|
7
7
|
|
|
8
8
|
const props = defineProps<{
|
|
9
9
|
/** Bound value: Date object or ISO string or null */
|
|
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|
|
3
3
|
|
|
4
4
|
const props = defineProps<{
|
|
5
5
|
modelValue?: any
|
|
6
|
+
themeController?: boolean
|
|
6
7
|
|
|
7
8
|
color?: string
|
|
8
9
|
primary?: boolean
|
|
@@ -44,6 +45,7 @@ const currentValue = computed({
|
|
|
44
45
|
'checkbox-sm': sm || size === 'sm',
|
|
45
46
|
'checkbox-md': md || size === 'md',
|
|
46
47
|
'checkbox-lg': lg || size === 'lg',
|
|
48
|
+
'theme-controller': themeController,
|
|
47
49
|
}"
|
|
48
50
|
>
|
|
49
51
|
</template>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { autoUpdate, useFloating } from '@floating-ui/vue'
|
|
3
|
-
import { onClickOutside, syncRefs, useElementHover } from '@vueuse/core'
|
|
4
|
-
import { onMounted, provide, reactive, ref } from 'vue'
|
|
5
|
-
import { randomString } from '../utils/random-string'
|
|
2
|
+
import { autoUpdate, useFloating } from '@floating-ui/vue'
|
|
3
|
+
import { onClickOutside, syncRefs, useElementHover } from '@vueuse/core'
|
|
4
|
+
import { onMounted, provide, reactive, ref } from 'vue'
|
|
5
|
+
import { randomString } from '../utils/random-string'
|
|
6
6
|
|
|
7
7
|
const props = withDefaults(defineProps<{
|
|
8
8
|
// allows passing a custom id to the dropdown. Required for SSR hydration.
|
package/app/components/Input.vue
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { syncRef } from '@vueuse/core'
|
|
3
|
+
import { computed, inject, ref } from 'vue'
|
|
3
4
|
|
|
4
5
|
// Map input type to value type
|
|
5
6
|
type InputType =
|
|
@@ -37,7 +38,29 @@ function setValue(val: string | number | null) {
|
|
|
37
38
|
emit('update:modelValue', val)
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
const labelCtx = inject('labelCtx'
|
|
41
|
+
const labelCtx = inject('labelCtx')
|
|
42
|
+
|
|
43
|
+
function handleInput(event: Event) {
|
|
44
|
+
let val: string | number | null
|
|
45
|
+
if (props.type === 'number') {
|
|
46
|
+
const inputVal = (event.target as HTMLInputElement).value
|
|
47
|
+
val = inputVal === '' ? null : Number(inputVal)
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
val = (event.target as HTMLInputElement).value
|
|
51
|
+
}
|
|
52
|
+
emit('update:modelValue', val)
|
|
53
|
+
|
|
54
|
+
// Sync checked only on input if it's a checkbox
|
|
55
|
+
if (
|
|
56
|
+
props.type === 'checkbox'
|
|
57
|
+
&& labelCtx
|
|
58
|
+
&& Object.prototype.hasOwnProperty.call(labelCtx, 'checked')
|
|
59
|
+
&& typeof labelCtx.checked.value === 'boolean'
|
|
60
|
+
) {
|
|
61
|
+
labelCtx.checked.value = Boolean(event.target.checked)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
41
64
|
|
|
42
65
|
const inputAttrs = computed(() => {
|
|
43
66
|
if (labelCtx) {
|
|
@@ -91,12 +114,6 @@ const inputModel = computed({
|
|
|
91
114
|
v-else
|
|
92
115
|
v-bind="inputAttrs"
|
|
93
116
|
:value="inputModel"
|
|
94
|
-
@input="
|
|
95
|
-
$emit('update:modelValue',
|
|
96
|
-
props.type === 'number'
|
|
97
|
-
? ($event.target as HTMLInputElement).value === '' ? null : Number(($event.target as HTMLInputElement).value)
|
|
98
|
-
: ($event.target as HTMLInputElement).value,
|
|
99
|
-
)
|
|
100
|
-
"
|
|
117
|
+
@input="handleInput"
|
|
101
118
|
>
|
|
102
119
|
</template>
|
package/app/components/Label.vue
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { provide } from 'vue'
|
|
2
|
+
import { provide, ref } from 'vue'
|
|
3
3
|
import Text from './Text.vue'
|
|
4
4
|
|
|
5
5
|
withDefaults(defineProps<{
|
|
6
6
|
is?: string
|
|
7
7
|
floating?: boolean
|
|
8
|
+
|
|
8
9
|
input?: boolean
|
|
10
|
+
select?: boolean
|
|
11
|
+
toggle?: boolean
|
|
9
12
|
|
|
10
13
|
join?: boolean
|
|
11
14
|
color?: string
|
|
15
|
+
neutral?: boolean
|
|
12
16
|
primary?: boolean
|
|
13
17
|
secondary?: boolean
|
|
14
18
|
accent?: boolean
|
|
@@ -28,7 +32,8 @@ withDefaults(defineProps<{
|
|
|
28
32
|
})
|
|
29
33
|
|
|
30
34
|
// Provide label context for child inputs
|
|
31
|
-
|
|
35
|
+
const checked = ref(false)
|
|
36
|
+
provide('labelCtx', { checked })
|
|
32
37
|
</script>
|
|
33
38
|
|
|
34
39
|
<template>
|
|
@@ -37,7 +42,10 @@ provide('labelCtx', true)
|
|
|
37
42
|
:class="[
|
|
38
43
|
{
|
|
39
44
|
'input': input,
|
|
40
|
-
'
|
|
45
|
+
'select': select,
|
|
46
|
+
'toggle': toggle,
|
|
47
|
+
'join-item': (input || select || toggle) && join,
|
|
48
|
+
'input-neutral': input && (neutral || color === 'neutral'),
|
|
41
49
|
'input-primary': input && (primary || color === 'primary'),
|
|
42
50
|
'input-secondary': input && (secondary || color === 'secondary'),
|
|
43
51
|
'input-accent': input && (accent || color === 'accent'),
|
|
@@ -51,8 +59,36 @@ provide('labelCtx', true)
|
|
|
51
59
|
'input-md': input && (size === 'md' || md),
|
|
52
60
|
'input-sm': input && (size === 'sm' || sm),
|
|
53
61
|
'input-xs': input && (size === 'xs' || xs),
|
|
62
|
+
'select-neutral': select && (neutral || color === 'neutral'),
|
|
63
|
+
'select-primary': select && (primary || color === 'primary'),
|
|
64
|
+
'select-secondary': select && (secondary || color === 'secondary'),
|
|
65
|
+
'select-accent': select && (accent || color === 'accent'),
|
|
66
|
+
'select-info': select && (info || color === 'info'),
|
|
67
|
+
'select-success': select && (success || color === 'success'),
|
|
68
|
+
'select-warning': select && (warning || color === 'warning'),
|
|
69
|
+
'select-error': select && (error || color === 'error'),
|
|
70
|
+
'select-ghost': select && ghost,
|
|
71
|
+
'select-xl': select && (size === 'xl' || xl),
|
|
72
|
+
'select-lg': select && (size === 'lg' || lg),
|
|
73
|
+
'select-md': select && (size === 'md' || md),
|
|
74
|
+
'select-sm': select && (size === 'sm' || sm),
|
|
75
|
+
'select-xs': select && (size === 'xs' || xs),
|
|
76
|
+
'text-neutral': toggle && checked && (neutral || color === 'neutral'),
|
|
77
|
+
'text-primary': toggle && checked && (primary || color === 'primary'),
|
|
78
|
+
'text-secondary': toggle && checked && (secondary || color === 'secondary'),
|
|
79
|
+
'text-accent': toggle && checked && (accent || color === 'accent'),
|
|
80
|
+
'text-info': toggle && checked && (info || color === 'info'),
|
|
81
|
+
'text-success': toggle && checked && (success || color === 'success'),
|
|
82
|
+
'text-warning': toggle && checked && (warning || color === 'warning'),
|
|
83
|
+
'text-error': toggle && checked && (error || color === 'error'),
|
|
84
|
+
'text-ghost': toggle && ghost,
|
|
85
|
+
'toggle-xl': toggle && (size === 'xl' || xl),
|
|
86
|
+
'toggle-lg': toggle && (size === 'lg' || lg),
|
|
87
|
+
'toggle-md': toggle && (size === 'md' || md),
|
|
88
|
+
'toggle-sm': toggle && (size === 'sm' || sm),
|
|
89
|
+
'toggle-xs': toggle && (size === 'xs' || xs),
|
|
54
90
|
},
|
|
55
|
-
{ label: !floating && !input },
|
|
91
|
+
{ label: !floating && !(input || toggle) },
|
|
56
92
|
{ 'floating-label': floating },
|
|
57
93
|
]"
|
|
58
94
|
>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
defineProps<{
|
|
3
|
+
displayClasses?: string
|
|
4
|
+
}>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
1
7
|
<template>
|
|
2
8
|
<div class="mockup-phone">
|
|
3
|
-
<div class="camera" />
|
|
4
|
-
<div class="display">
|
|
9
|
+
<div class="mockup-phone-camera" />
|
|
10
|
+
<div class="mockup-phone-display" :class="displayClasses">
|
|
5
11
|
<slot />
|
|
6
12
|
</div>
|
|
7
13
|
</div>
|
package/app/components/Radio.vue
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, inject } from 'vue'
|
|
2
|
+
import { computed, inject } from 'vue'
|
|
3
3
|
|
|
4
4
|
const props = defineProps<{
|
|
5
5
|
modelValue?: any
|
|
6
6
|
value: any
|
|
7
7
|
disabled?: boolean
|
|
8
|
+
themeController?: boolean
|
|
8
9
|
|
|
9
10
|
color?: string
|
|
10
11
|
neutral?: boolean
|
|
@@ -60,6 +61,7 @@ const currentValue = computed({
|
|
|
60
61
|
'radio-md': props.md || props.size === 'md',
|
|
61
62
|
'radio-lg': props.lg || props.size === 'lg',
|
|
62
63
|
'radio-xl': props.xl || props.size === 'xl',
|
|
64
|
+
'theme-controller': props.themeController,
|
|
63
65
|
}"
|
|
64
66
|
:disabled="props.disabled"
|
|
65
67
|
:value="props.value"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DaisyThemeInput } from '@/composables/use-daisy-theme'
|
|
3
|
+
import { useDaisyTheme } from '@/composables/use-daisy-theme'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
/**
|
|
8
|
+
* Optional: List of themes to provide (string or DaisyThemeMeta)
|
|
9
|
+
*/
|
|
10
|
+
themes?: DaisyThemeInput[]
|
|
11
|
+
/**
|
|
12
|
+
* Optional: Default theme name (string or 'default')
|
|
13
|
+
*/
|
|
14
|
+
defaultTheme?: string
|
|
15
|
+
/**
|
|
16
|
+
* Optional: Storage function for persistence (e.g., useCookie, useLocalStorage, or ref)
|
|
17
|
+
*/
|
|
18
|
+
storage?: <T>(key: string, initial: T) => import('vue').Ref<T>
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const themeOptions = props.themes || props.defaultTheme
|
|
22
|
+
? { themes: props.themes || [], defaultTheme: props.defaultTheme }
|
|
23
|
+
: undefined
|
|
24
|
+
|
|
25
|
+
const { theme, effectiveTheme, themes, setTheme, cycleTheme, registerTheme, removeTheme } = useDaisyTheme(
|
|
26
|
+
props.storage,
|
|
27
|
+
themeOptions,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const availableThemes = computed(() => themes.value.map(t => typeof t === 'string' ? t : t.theme))
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="theme-controller">
|
|
35
|
+
<slot
|
|
36
|
+
:theme="theme"
|
|
37
|
+
:effective-theme="effectiveTheme"
|
|
38
|
+
:themes="themes"
|
|
39
|
+
:available-themes="availableThemes"
|
|
40
|
+
:set-theme="setTheme"
|
|
41
|
+
:cycle-theme="cycleTheme"
|
|
42
|
+
:register-theme="registerTheme"
|
|
43
|
+
:remove-theme="removeTheme"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComputedThemeVarsSlot } from './ThemeProvider.inject.js'
|
|
3
|
+
import { computed, nextTick, onUnmounted, provide, ref, watch } from 'vue'
|
|
4
|
+
import { daisyUiThemeKey } from './ThemeProvider.inject.js'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
/**
|
|
8
|
+
* DaisyUI theme string (recommended prop name)
|
|
9
|
+
*/
|
|
10
|
+
dataTheme?: string
|
|
11
|
+
|
|
12
|
+
cssVars?: string
|
|
13
|
+
snoop?: boolean
|
|
14
|
+
}>()
|
|
15
|
+
|
|
16
|
+
function cssVarToCamel(str: string): string {
|
|
17
|
+
return str.replace(/^--/, '').replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase())
|
|
18
|
+
}
|
|
19
|
+
function dataAttrToCamel(str: string): string {
|
|
20
|
+
return str.replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase())
|
|
21
|
+
}
|
|
22
|
+
function parseThemeString(themeString?: string): { style: Record<string, string>, dataAttrs: Record<string, string>, dataTheme: string | undefined, allAttrs: Record<string, string> } {
|
|
23
|
+
if (!themeString) {
|
|
24
|
+
return { style: {}, dataAttrs: {}, dataTheme: undefined, allAttrs: {} }
|
|
25
|
+
}
|
|
26
|
+
const varNamePattern = /^--[\w-]+$/
|
|
27
|
+
const attrNamePattern = /^[a-z_][\w-]*$/i
|
|
28
|
+
const forbiddenChars = /[<>{};]/g
|
|
29
|
+
|
|
30
|
+
const style: Record<string, string> = {}
|
|
31
|
+
const dataAttrs: Record<string, string> = {}
|
|
32
|
+
let dataTheme: string | undefined
|
|
33
|
+
const allAttrs: Record<string, string> = {}
|
|
34
|
+
|
|
35
|
+
// Remove @plugin ... { and closing } if present
|
|
36
|
+
const str = themeString.trim().replace(/^@plugin[^{}]*\{/, '').replace(/\}$/, '').trim()
|
|
37
|
+
|
|
38
|
+
// works with or without newline characters
|
|
39
|
+
str.split(/[\n;]/).forEach((line) => {
|
|
40
|
+
const trimmed = line.trim()
|
|
41
|
+
if (!trimmed || trimmed.startsWith('//')) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
const colonIdx = trimmed.indexOf(':')
|
|
45
|
+
if (colonIdx === -1) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
const key = trimmed.slice(0, colonIdx).trim()
|
|
49
|
+
let value = trimmed.slice(colonIdx + 1).trim()
|
|
50
|
+
if (value.endsWith(';')) {
|
|
51
|
+
value = value.slice(0, -1).trim()
|
|
52
|
+
}
|
|
53
|
+
value = value.replace(forbiddenChars, '')
|
|
54
|
+
if (!value) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
if (varNamePattern.test(key)) {
|
|
58
|
+
style[key] = value
|
|
59
|
+
allAttrs[cssVarToCamel(key)] = value
|
|
60
|
+
}
|
|
61
|
+
else if (key === 'name') {
|
|
62
|
+
dataTheme = value.replace(/"/g, '')
|
|
63
|
+
allAttrs.dataTheme = dataTheme
|
|
64
|
+
}
|
|
65
|
+
else if (attrNamePattern.test(key)) {
|
|
66
|
+
const attrKey = `data-${key.replace(/[^\w-]/g, '')}`
|
|
67
|
+
const attrVal = value.replace(/"/g, '')
|
|
68
|
+
dataAttrs[attrKey] = attrVal
|
|
69
|
+
if (attrKey.startsWith('data-')) {
|
|
70
|
+
allAttrs[dataAttrToCamel(attrKey)] = attrVal
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
return { style, dataAttrs, dataTheme, allAttrs }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const parsed = computed(() => parseThemeString(props.dataTheme ?? props.cssVars))
|
|
78
|
+
|
|
79
|
+
const themeVars = ref<Record<string, string>>({})
|
|
80
|
+
let observers: MutationObserver[] = []
|
|
81
|
+
let themeControllerInputs: Array<{ el: HTMLInputElement, listener: () => void }> = []
|
|
82
|
+
let themeControllerDomObserver: MutationObserver | null = null
|
|
83
|
+
|
|
84
|
+
// Converts a themeAttrs object to a DaisyUI theme string
|
|
85
|
+
function toThemeString(attrs: Record<string, string>, opts?: { asPlugin?: boolean }) {
|
|
86
|
+
const lines: string[] = []
|
|
87
|
+
// Map camelCase back to DaisyUI keys
|
|
88
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
89
|
+
if (key === 'dataTheme') {
|
|
90
|
+
lines.push(`name: "${value}";`)
|
|
91
|
+
}
|
|
92
|
+
else if (key.startsWith('data')) {
|
|
93
|
+
// Convert dataFooBar -> foo-bar
|
|
94
|
+
const attr = key.slice(4).replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
95
|
+
if (attr !== 'theme') {
|
|
96
|
+
lines.push(`${attr}: ${value};`)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// Convert camelCase to --kebab-case
|
|
101
|
+
const cssVar = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`
|
|
102
|
+
lines.push(`${cssVar}: ${value};`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const inner = lines.join('\n')
|
|
106
|
+
if (opts?.asPlugin) {
|
|
107
|
+
return `@plugin \"daisyui/theme\" {\n${inner}\n}`
|
|
108
|
+
}
|
|
109
|
+
return inner
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Returns the slot data object depending on snoop mode
|
|
113
|
+
const slotData: ComputedThemeVarsSlot = computed(() => {
|
|
114
|
+
// Access both dependencies so Vue always tracks them for reactivity
|
|
115
|
+
void themeVars.value
|
|
116
|
+
void parsed.value.allAttrs
|
|
117
|
+
if (props.snoop) {
|
|
118
|
+
return {
|
|
119
|
+
vars: themeVars.value,
|
|
120
|
+
toThemeString: (attrs = themeVars.value, opts) => toThemeString(attrs, opts),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
vars: parsed.value.allAttrs,
|
|
125
|
+
toThemeString: (attrs = parsed.value.allAttrs, opts) => toThemeString(attrs, opts),
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Provide the slotData as 'daisyUiTheme' for child consumers
|
|
130
|
+
provide(daisyUiThemeKey, slotData)
|
|
131
|
+
|
|
132
|
+
// DaisyUI variable names
|
|
133
|
+
const daisyVars = [
|
|
134
|
+
'--color-primary',
|
|
135
|
+
'--color-primary-content',
|
|
136
|
+
'--color-secondary',
|
|
137
|
+
'--color-secondary-content',
|
|
138
|
+
'--color-accent',
|
|
139
|
+
'--color-accent-content',
|
|
140
|
+
'--color-neutral',
|
|
141
|
+
'--color-neutral-content',
|
|
142
|
+
'--color-base-100',
|
|
143
|
+
'--color-base-200',
|
|
144
|
+
'--color-base-300',
|
|
145
|
+
'--color-base-content',
|
|
146
|
+
'--color-info',
|
|
147
|
+
'--color-info-content',
|
|
148
|
+
'--color-success',
|
|
149
|
+
'--color-success-content',
|
|
150
|
+
'--color-warning',
|
|
151
|
+
'--color-warning-content',
|
|
152
|
+
'--color-error',
|
|
153
|
+
'--color-error-content',
|
|
154
|
+
'--radius-selector',
|
|
155
|
+
'--radius-field',
|
|
156
|
+
'--radius-box',
|
|
157
|
+
'--size-selector',
|
|
158
|
+
'--size-field',
|
|
159
|
+
'--border',
|
|
160
|
+
'--depth',
|
|
161
|
+
'--noise',
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
function getDaisyVarsFromEl(el: HTMLElement): Record<string, string> {
|
|
165
|
+
const vars: Record<string, string> = {}
|
|
166
|
+
const style = getComputedStyle(el)
|
|
167
|
+
for (const varName of daisyVars) {
|
|
168
|
+
const val = style.getPropertyValue(varName)
|
|
169
|
+
if (val) {
|
|
170
|
+
vars[cssVarToCamel(varName)] = val.trim()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return vars
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let prefersColorSchemeMql: MediaQueryList | null = null
|
|
177
|
+
let prefersColorSchemeCleanup: (() => void) | null = null
|
|
178
|
+
|
|
179
|
+
function setupSnoop(rootEl: HTMLElement): void {
|
|
180
|
+
function updateVars(): void {
|
|
181
|
+
themeVars.value = getDaisyVarsFromEl(rootEl)
|
|
182
|
+
}
|
|
183
|
+
// Observe the current element for data-theme changes
|
|
184
|
+
const obs = new MutationObserver(() => updateVars())
|
|
185
|
+
obs.observe(rootEl, { attributes: true, attributeFilter: ['data-theme'] })
|
|
186
|
+
|
|
187
|
+
// Also observe the <html> element for data-theme changes
|
|
188
|
+
const htmlEl = document.documentElement
|
|
189
|
+
const htmlObs = new MutationObserver(() => updateVars())
|
|
190
|
+
htmlObs.observe(htmlEl, { attributes: true, attributeFilter: ['data-theme'] })
|
|
191
|
+
|
|
192
|
+
observers = [obs, htmlObs]
|
|
193
|
+
|
|
194
|
+
// Listen for prefers-color-scheme changes
|
|
195
|
+
prefersColorSchemeMql = window.matchMedia('(prefers-color-scheme: dark)')
|
|
196
|
+
const prefersColorSchemeListener = () => updateVars()
|
|
197
|
+
prefersColorSchemeMql.addEventListener('change', prefersColorSchemeListener)
|
|
198
|
+
prefersColorSchemeCleanup = () => {
|
|
199
|
+
prefersColorSchemeMql?.removeEventListener('change', prefersColorSchemeListener)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Observe theme-controller checkboxes/radios, including dynamic changes ---
|
|
203
|
+
function bindThemeControllerInputs() {
|
|
204
|
+
// Remove any previous listeners
|
|
205
|
+
themeControllerInputs.forEach(({ el, listener }) => {
|
|
206
|
+
el.removeEventListener('change', listener)
|
|
207
|
+
})
|
|
208
|
+
themeControllerInputs = []
|
|
209
|
+
// Find all matching inputs in the document
|
|
210
|
+
const inputs = Array.from(document.querySelectorAll('input[type="checkbox"].theme-controller, input[type="radio"].theme-controller')) as HTMLInputElement[]
|
|
211
|
+
inputs.forEach((el) => {
|
|
212
|
+
const listener = () => updateVars()
|
|
213
|
+
el.addEventListener('change', listener)
|
|
214
|
+
themeControllerInputs.push({ el, listener })
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
bindThemeControllerInputs()
|
|
218
|
+
// Observe DOM for dynamic addition/removal of theme-controller inputs
|
|
219
|
+
if (themeControllerDomObserver) {
|
|
220
|
+
themeControllerDomObserver.disconnect()
|
|
221
|
+
themeControllerDomObserver = null
|
|
222
|
+
}
|
|
223
|
+
themeControllerDomObserver = new MutationObserver(() => {
|
|
224
|
+
bindThemeControllerInputs()
|
|
225
|
+
})
|
|
226
|
+
themeControllerDomObserver.observe(document.body, { childList: true, subtree: true })
|
|
227
|
+
|
|
228
|
+
// Watch for applied style changes (parsed.style)
|
|
229
|
+
watch(() => parsed.value.style, () => {
|
|
230
|
+
nextTick(() => updateVars())
|
|
231
|
+
}, { immediate: false, deep: true })
|
|
232
|
+
updateVars()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
236
|
+
|
|
237
|
+
const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'
|
|
238
|
+
|
|
239
|
+
watch([
|
|
240
|
+
() => props.snoop,
|
|
241
|
+
() => rootEl.value,
|
|
242
|
+
], ([snoop, el]) => {
|
|
243
|
+
if (isClient) {
|
|
244
|
+
// Clean up previous observers if any
|
|
245
|
+
observers.forEach((o: MutationObserver) => o.disconnect())
|
|
246
|
+
observers = []
|
|
247
|
+
if (snoop && el) {
|
|
248
|
+
nextTick(() => {
|
|
249
|
+
setupSnoop(el)
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}, { immediate: true })
|
|
254
|
+
|
|
255
|
+
onUnmounted(() => {
|
|
256
|
+
observers.forEach((o: MutationObserver) => o.disconnect())
|
|
257
|
+
observers = []
|
|
258
|
+
// Remove prefers-color-scheme listener if present
|
|
259
|
+
if (prefersColorSchemeCleanup) {
|
|
260
|
+
prefersColorSchemeCleanup()
|
|
261
|
+
prefersColorSchemeCleanup = null
|
|
262
|
+
prefersColorSchemeMql = null
|
|
263
|
+
}
|
|
264
|
+
// Remove theme-controller listeners
|
|
265
|
+
themeControllerInputs.forEach(({ el, listener }) => {
|
|
266
|
+
el.removeEventListener('change', listener)
|
|
267
|
+
})
|
|
268
|
+
themeControllerInputs = []
|
|
269
|
+
// Disconnect theme-controller DOM observer
|
|
270
|
+
if (themeControllerDomObserver) {
|
|
271
|
+
themeControllerDomObserver.disconnect()
|
|
272
|
+
themeControllerDomObserver = null
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
</script>
|
|
276
|
+
|
|
277
|
+
<template>
|
|
278
|
+
<div
|
|
279
|
+
ref="rootEl"
|
|
280
|
+
v-bind="parsed.dataAttrs"
|
|
281
|
+
:data-theme="dataTheme"
|
|
282
|
+
:style="parsed.style"
|
|
283
|
+
class="[background-color:unset] theme-provider"
|
|
284
|
+
>
|
|
285
|
+
<slot v-bind="slotData" />
|
|
286
|
+
</div>
|
|
287
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
theme?: string
|
|
6
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
7
|
+
xs?: boolean
|
|
8
|
+
sm?: boolean
|
|
9
|
+
md?: boolean
|
|
10
|
+
lg?: boolean
|
|
11
|
+
xl?: boolean
|
|
12
|
+
}>()
|
|
13
|
+
const size = computed(() => props.size ?? 'md')
|
|
14
|
+
|
|
15
|
+
const squareClass = computed(() => {
|
|
16
|
+
switch (size.value) {
|
|
17
|
+
case 'xs':
|
|
18
|
+
return 'w-full h-full rounded-[2px]'
|
|
19
|
+
case 'sm':
|
|
20
|
+
return 'w-full h-full rounded-[2px]'
|
|
21
|
+
case 'md':
|
|
22
|
+
return 'w-full h-full rounded-xs'
|
|
23
|
+
case 'lg':
|
|
24
|
+
return 'w-full h-full rounded-sm'
|
|
25
|
+
case 'xl':
|
|
26
|
+
return 'w-full h-full rounded'
|
|
27
|
+
default:
|
|
28
|
+
return 'w-full h-full rounded-xs'
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div
|
|
35
|
+
:data-theme="theme"
|
|
36
|
+
class="grid grid-cols-2 bg-white rounded-sm"
|
|
37
|
+
:class="{
|
|
38
|
+
'!size-4 p-0.5 gap-px': xs || size === 'xs',
|
|
39
|
+
'!size-5 p-0.5 gap-px': sm || size === 'sm',
|
|
40
|
+
'size-6 p-0.5 gap-0.5': md || size === 'md' || !size,
|
|
41
|
+
'size-8 p-0.75 gap-0.5': lg || size === 'lg',
|
|
42
|
+
'size-10 h-10 p-1 gap-0.75': xl || size === 'xl',
|
|
43
|
+
}"
|
|
44
|
+
>
|
|
45
|
+
<div class="bg-neutral" :class="squareClass" />
|
|
46
|
+
<div class="bg-primary" :class="squareClass" />
|
|
47
|
+
<div class="bg-secondary" :class="squareClass" />
|
|
48
|
+
<div class="bg-accent" :class="squareClass" />
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
3
|
|
|
4
4
|
const props = defineProps<{
|
|
5
5
|
modelValue?: any
|
|
6
6
|
disabled?: boolean
|
|
7
7
|
validator?: boolean
|
|
8
|
+
themeController?: boolean
|
|
8
9
|
|
|
9
10
|
color?: string
|
|
10
11
|
neutral?: boolean
|
|
@@ -53,6 +54,7 @@ const currentValue = computed({
|
|
|
53
54
|
'toggle-md': md || size === 'md',
|
|
54
55
|
'toggle-lg': lg || size === 'lg',
|
|
55
56
|
'toggle-xl': xl || size === 'xl',
|
|
57
|
+
'theme-controller': themeController,
|
|
56
58
|
}"
|
|
57
59
|
>
|
|
58
60
|
</template>
|
package/nuxt.js
CHANGED
|
@@ -14,9 +14,15 @@ export default defineNuxtModule({
|
|
|
14
14
|
|
|
15
15
|
nuxt.hook('components:dirs', (dirs) => {
|
|
16
16
|
dirs.push({
|
|
17
|
-
path: fileURLToPath(new URL('./components', import.meta.url)),
|
|
17
|
+
path: fileURLToPath(new URL('./app/components', import.meta.url)),
|
|
18
18
|
prefix,
|
|
19
19
|
})
|
|
20
20
|
})
|
|
21
|
+
|
|
22
|
+
nuxt.hook('composables:dirs', (dirs) => {
|
|
23
|
+
dirs.push({
|
|
24
|
+
path: fileURLToPath(new URL('./app/composables-docs', import.meta.url)),
|
|
25
|
+
})
|
|
26
|
+
})
|
|
21
27
|
},
|
|
22
28
|
})
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daisy-ui-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.0.0-pre.
|
|
5
|
-
"packageManager": "pnpm@10.
|
|
4
|
+
"version": "5.0.0-pre.8",
|
|
5
|
+
"packageManager": "pnpm@10.10.0",
|
|
6
6
|
"files": [
|
|
7
7
|
"app/components/*.vue",
|
|
8
8
|
"app/utils/*",
|
|
9
9
|
"nuxt.js"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
+
"import-d1-dumps": "./import-d1-dumps.sh",
|
|
12
13
|
"build": "nuxt build",
|
|
13
14
|
"dev": "nuxt dev",
|
|
14
15
|
"lint": "eslint",
|
|
@@ -20,7 +21,8 @@
|
|
|
20
21
|
"release:pre": "npm version prerelease && npm publish --tag pre",
|
|
21
22
|
"release:patch": "npm version patch && npm publish",
|
|
22
23
|
"release:minor": "npm version minor && npm publish",
|
|
23
|
-
"release:major": "npm version major && npm publish"
|
|
24
|
+
"release:major": "npm version major && npm publish",
|
|
25
|
+
"deploy": "pnpm run build && npx wrangler pages deploy dist"
|
|
24
26
|
},
|
|
25
27
|
"peerDependencies": {
|
|
26
28
|
"@vueuse/core": "^13.1.0",
|
|
@@ -28,34 +30,34 @@
|
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"@floating-ui/vue": "^1.1.6",
|
|
31
|
-
"@nuxt/content": "3.
|
|
33
|
+
"@nuxt/content": "3.5.1",
|
|
32
34
|
"@nuxt/eslint": "1.3.0",
|
|
33
|
-
"@nuxt/fonts": "0.11.
|
|
35
|
+
"@nuxt/fonts": "0.11.2",
|
|
34
36
|
"@nuxt/icon": "1.12.0",
|
|
35
37
|
"@nuxt/image": "1.10.0",
|
|
36
|
-
"@nuxt/kit": "^3.17.
|
|
38
|
+
"@nuxt/kit": "^3.17.2",
|
|
37
39
|
"@nuxt/scripts": "0.11.6",
|
|
38
|
-
"@nuxt/test-utils": "3.
|
|
40
|
+
"@nuxt/test-utils": "3.18.0",
|
|
39
41
|
"@unhead/vue": "^2.0.8",
|
|
40
42
|
"@vueuse/core": "^13.1.0",
|
|
41
43
|
"@vueuse/integrations": "^13.1.0",
|
|
42
44
|
"focus-trap": "^7.6.4",
|
|
43
|
-
"nuxt": "^3.
|
|
45
|
+
"nuxt": "^3.17.2",
|
|
44
46
|
"pikaday": "^1.8.2",
|
|
45
47
|
"shiki": "^3.3.0",
|
|
46
48
|
"typescript": "^5.8.3",
|
|
47
49
|
"vue": "^3.5.13",
|
|
48
|
-
"vue-router": "^4.5.
|
|
49
|
-
"zod": "^3.24.
|
|
50
|
+
"vue-router": "^4.5.1",
|
|
51
|
+
"zod": "^3.24.4"
|
|
50
52
|
},
|
|
51
53
|
"devDependencies": {
|
|
52
54
|
"@antfu/eslint-config": "^4.12.0",
|
|
53
55
|
"@stylistic/eslint-plugin": "^4.2.0",
|
|
54
56
|
"@tailwindcss/typography": "^0.5.16",
|
|
55
|
-
"@tailwindcss/vite": "^4.1.
|
|
57
|
+
"@tailwindcss/vite": "^4.1.5",
|
|
56
58
|
"@types/pikaday": "^1.7.9",
|
|
57
|
-
"daisyui": "^5.0.
|
|
58
|
-
"eslint": "^9.
|
|
59
|
-
"tailwindcss": "^4.1.
|
|
59
|
+
"daisyui": "^5.0.35",
|
|
60
|
+
"eslint": "^9.26.0",
|
|
61
|
+
"tailwindcss": "^4.1.5"
|
|
60
62
|
}
|
|
61
63
|
}
|