fds-vue-core 2.2.0 → 2.3.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/dist/fds-vue-core.cjs.js +1033 -523
- package/dist/fds-vue-core.cjs.js.map +1 -1
- package/dist/fds-vue-core.css +1 -1
- package/dist/fds-vue-core.es.js +1034 -524
- package/dist/fds-vue-core.es.js.map +1 -1
- package/package.json +1 -1
- package/src/components/FdsDevMode/FdsDevMode.vue +11 -7
- package/src/components/FdsDevMode/FdsDevModeStorage.vue +413 -0
- package/src/components/Form/FdsInput/FdsInput.vue +2 -0
- package/src/components/Form/FdsInput/types.ts +1 -0
package/package.json
CHANGED
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
3
|
import { useDevMode } from '../../composables/useDevMode'
|
|
4
4
|
import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
|
|
5
|
+
import FdsDevModeStorage from './FdsDevModeStorage.vue'
|
|
5
6
|
|
|
6
|
-
type EnvironmentKey =
|
|
7
|
-
| 'localhost'
|
|
8
|
-
| 'development'
|
|
9
|
-
| 'development:feature-branch'
|
|
10
|
-
| 'preprod'
|
|
11
|
-
| 'demo'
|
|
12
|
-
| 'production'
|
|
7
|
+
type EnvironmentKey = 'localhost' | 'development' | 'development:feature-branch' | 'preprod' | 'demo' | 'production'
|
|
13
8
|
|
|
14
9
|
const props = withDefaults(
|
|
15
10
|
defineProps<{
|
|
@@ -22,6 +17,7 @@ const props = withDefaults(
|
|
|
22
17
|
|
|
23
18
|
const { isDevMode, toggleDevMode } = useDevMode()
|
|
24
19
|
const hideEnvBanner = ref(false)
|
|
20
|
+
const isStorageModalOpen = ref(false)
|
|
25
21
|
|
|
26
22
|
const environmentKey = computed<EnvironmentKey | null>(() => props.environment ?? null)
|
|
27
23
|
|
|
@@ -93,6 +89,10 @@ const envBannerFillColor = computed(() => {
|
|
|
93
89
|
const handleEnvBanner = () => {
|
|
94
90
|
hideEnvBanner.value = true
|
|
95
91
|
}
|
|
92
|
+
|
|
93
|
+
const openStorageModal = () => {
|
|
94
|
+
isStorageModalOpen.value = true
|
|
95
|
+
}
|
|
96
96
|
</script>
|
|
97
97
|
|
|
98
98
|
<template>
|
|
@@ -110,6 +110,10 @@ const handleEnvBanner = () => {
|
|
|
110
110
|
{{ isDevMode ? 'Dev mode: ON' : 'Dev mode: OFF' }}
|
|
111
111
|
</button>
|
|
112
112
|
|
|
113
|
+
<FdsButtonIcon icon="settings" :size="24" class="mx-1" :class="envBannerFillColor" @click="openStorageModal" />
|
|
114
|
+
|
|
113
115
|
<FdsButtonIcon @click="handleEnvBanner" icon="cross" :size="24" :class="envBannerFillColor" />
|
|
114
116
|
</div>
|
|
117
|
+
|
|
118
|
+
<FdsDevModeStorage v-model:open="isStorageModalOpen" />
|
|
115
119
|
</template>
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, nextTick, ref, watch } from 'vue'
|
|
3
|
+
import FdsBlockInfo from '../Blocks/FdsBlockInfo/FdsBlockInfo.vue'
|
|
4
|
+
import FdsButtonPrimary from '../Buttons/FdsButtonPrimary/FdsButtonPrimary.vue'
|
|
5
|
+
import FdsButtonSecondary from '../Buttons/FdsButtonSecondary/FdsButtonSecondary.vue'
|
|
6
|
+
import FdsModal from '../FdsModal/FdsModal.vue'
|
|
7
|
+
import FdsTabs from '../Tabs/FdsTabs/FdsTabs.vue'
|
|
8
|
+
import FdsTabsItem from '../Tabs/FdsTabsItem/FdsTabsItem.vue'
|
|
9
|
+
|
|
10
|
+
type StorageEntry = {
|
|
11
|
+
key: string
|
|
12
|
+
value: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
open?: boolean
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
open: false,
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits<{
|
|
25
|
+
(e: 'update:open', value: boolean): void
|
|
26
|
+
}>()
|
|
27
|
+
|
|
28
|
+
const isOpen = computed({
|
|
29
|
+
get: () => props.open,
|
|
30
|
+
set: (value: boolean) => emit('update:open', value),
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const activeTab = ref<'local' | 'session' | 'cookies' | 'placeholder1' | 'placeholder2'>('local')
|
|
34
|
+
|
|
35
|
+
const localStorageEntries = ref<StorageEntry[]>([])
|
|
36
|
+
const sessionStorageEntries = ref<StorageEntry[]>([])
|
|
37
|
+
const cookieEntries = ref<StorageEntry[]>([])
|
|
38
|
+
|
|
39
|
+
const localEditKey = ref('')
|
|
40
|
+
const localEditValue = ref('')
|
|
41
|
+
const sessionEditKey = ref('')
|
|
42
|
+
const sessionEditValue = ref('')
|
|
43
|
+
const cookieEditKey = ref('')
|
|
44
|
+
const cookieEditValue = ref('')
|
|
45
|
+
|
|
46
|
+
const localTextareaRef = ref<HTMLTextAreaElement | null>(null)
|
|
47
|
+
const sessionTextareaRef = ref<HTMLTextAreaElement | null>(null)
|
|
48
|
+
const cookieTextareaRef = ref<HTMLTextAreaElement | null>(null)
|
|
49
|
+
|
|
50
|
+
const resizeTextarea = (el: HTMLTextAreaElement | null) => {
|
|
51
|
+
if (!el) return
|
|
52
|
+
// Reset to default when empty
|
|
53
|
+
if (!el.value) {
|
|
54
|
+
el.style.height = ''
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
el.style.height = 'auto'
|
|
58
|
+
el.style.height = `${el.scrollHeight}px`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const loadStorageEntries = () => {
|
|
62
|
+
if (typeof window === 'undefined') {
|
|
63
|
+
localStorageEntries.value = []
|
|
64
|
+
sessionStorageEntries.value = []
|
|
65
|
+
cookieEntries.value = []
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const extractEntries = (storage: Storage): StorageEntry[] => {
|
|
70
|
+
const entries: StorageEntry[] = []
|
|
71
|
+
for (let i = 0; i < storage.length; i += 1) {
|
|
72
|
+
const key = storage.key(i)
|
|
73
|
+
if (!key) continue
|
|
74
|
+
let value: string
|
|
75
|
+
try {
|
|
76
|
+
value = storage.getItem(key) ?? ''
|
|
77
|
+
} catch {
|
|
78
|
+
value = '[unreadable value]'
|
|
79
|
+
}
|
|
80
|
+
entries.push({ key, value })
|
|
81
|
+
}
|
|
82
|
+
return entries
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
localStorageEntries.value = extractEntries(window.localStorage)
|
|
86
|
+
sessionStorageEntries.value = extractEntries(window.sessionStorage)
|
|
87
|
+
|
|
88
|
+
const cookiesSource = typeof document !== 'undefined' && document.cookie ? document.cookie.split('; ') : []
|
|
89
|
+
cookieEntries.value = cookiesSource.filter(Boolean).map((cookie) => {
|
|
90
|
+
const [rawKey, ...rest] = cookie.split('=')
|
|
91
|
+
const key = decodeURIComponent(rawKey || '')
|
|
92
|
+
const value = decodeURIComponent(rest.join('='))
|
|
93
|
+
return {
|
|
94
|
+
key,
|
|
95
|
+
value,
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const handleOpenChange = (value: boolean) => {
|
|
101
|
+
if (value) {
|
|
102
|
+
loadStorageEntries()
|
|
103
|
+
}
|
|
104
|
+
isOpen.value = value
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
watch(
|
|
108
|
+
() => props.open,
|
|
109
|
+
(newVal) => {
|
|
110
|
+
if (newVal) {
|
|
111
|
+
loadStorageEntries()
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const refreshStorageEntries = () => {
|
|
117
|
+
loadStorageEntries()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const selectLocalEntry = (entry: StorageEntry) => {
|
|
121
|
+
localEditKey.value = entry.key
|
|
122
|
+
localEditValue.value = entry.value
|
|
123
|
+
nextTick(() => resizeTextarea(localTextareaRef.value))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const selectSessionEntry = (entry: StorageEntry) => {
|
|
127
|
+
sessionEditKey.value = entry.key
|
|
128
|
+
sessionEditValue.value = entry.value
|
|
129
|
+
nextTick(() => resizeTextarea(sessionTextareaRef.value))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const selectCookieEntry = (entry: StorageEntry) => {
|
|
133
|
+
cookieEditKey.value = entry.key
|
|
134
|
+
cookieEditValue.value = entry.value
|
|
135
|
+
nextTick(() => resizeTextarea(cookieTextareaRef.value))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const saveLocalEntry = () => {
|
|
139
|
+
if (typeof window === 'undefined') return
|
|
140
|
+
if (!localEditKey.value) return
|
|
141
|
+
window.localStorage.setItem(localEditKey.value, localEditValue.value)
|
|
142
|
+
// Clear selection after save
|
|
143
|
+
localEditKey.value = ''
|
|
144
|
+
localEditValue.value = ''
|
|
145
|
+
nextTick(() => resizeTextarea(localTextareaRef.value))
|
|
146
|
+
loadStorageEntries()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const deleteLocalEntry = () => {
|
|
150
|
+
if (typeof window === 'undefined') return
|
|
151
|
+
if (!localEditKey.value) return
|
|
152
|
+
window.localStorage.removeItem(localEditKey.value)
|
|
153
|
+
localEditKey.value = ''
|
|
154
|
+
localEditValue.value = ''
|
|
155
|
+
nextTick(() => resizeTextarea(localTextareaRef.value))
|
|
156
|
+
loadStorageEntries()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const saveSessionEntry = () => {
|
|
160
|
+
if (typeof window === 'undefined') return
|
|
161
|
+
if (!sessionEditKey.value) return
|
|
162
|
+
window.sessionStorage.setItem(sessionEditKey.value, sessionEditValue.value)
|
|
163
|
+
sessionEditKey.value = ''
|
|
164
|
+
sessionEditValue.value = ''
|
|
165
|
+
nextTick(() => resizeTextarea(sessionTextareaRef.value))
|
|
166
|
+
loadStorageEntries()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const deleteSessionEntry = () => {
|
|
170
|
+
if (typeof window === 'undefined') return
|
|
171
|
+
if (!sessionEditKey.value) return
|
|
172
|
+
window.sessionStorage.removeItem(sessionEditKey.value)
|
|
173
|
+
sessionEditKey.value = ''
|
|
174
|
+
sessionEditValue.value = ''
|
|
175
|
+
nextTick(() => resizeTextarea(sessionTextareaRef.value))
|
|
176
|
+
loadStorageEntries()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const saveCookieEntry = () => {
|
|
180
|
+
if (typeof document === 'undefined') return
|
|
181
|
+
if (!cookieEditKey.value) return
|
|
182
|
+
// Basic cookie setter; consumers can override domain/path via browser devtools if needed
|
|
183
|
+
document.cookie = `${encodeURIComponent(cookieEditKey.value)}=${encodeURIComponent(cookieEditValue.value)}; path=/`
|
|
184
|
+
cookieEditKey.value = ''
|
|
185
|
+
cookieEditValue.value = ''
|
|
186
|
+
nextTick(() => resizeTextarea(cookieTextareaRef.value))
|
|
187
|
+
loadStorageEntries()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const deleteCookieEntry = () => {
|
|
191
|
+
if (typeof document === 'undefined') return
|
|
192
|
+
if (!cookieEditKey.value) return
|
|
193
|
+
// Expire cookie
|
|
194
|
+
document.cookie = `${encodeURIComponent(cookieEditKey.value)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`
|
|
195
|
+
cookieEditKey.value = ''
|
|
196
|
+
cookieEditValue.value = ''
|
|
197
|
+
nextTick(() => resizeTextarea(cookieTextareaRef.value))
|
|
198
|
+
loadStorageEntries()
|
|
199
|
+
}
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<template>
|
|
203
|
+
<FdsModal :open="isOpen" heading="DevTools" size="xl" @update:open="handleOpenChange">
|
|
204
|
+
<div class="space-y-6">
|
|
205
|
+
<FdsTabs variant="primary">
|
|
206
|
+
<FdsTabsItem label="localStorage" :active="activeTab === 'local'" as="button" @click="activeTab = 'local'" />
|
|
207
|
+
<FdsTabsItem
|
|
208
|
+
label="sessionStorage"
|
|
209
|
+
:active="activeTab === 'session'"
|
|
210
|
+
as="button"
|
|
211
|
+
@click="activeTab = 'session'"
|
|
212
|
+
/>
|
|
213
|
+
<FdsTabsItem label="Cookies" :active="activeTab === 'cookies'" as="button" @click="activeTab = 'cookies'" />
|
|
214
|
+
<FdsTabsItem
|
|
215
|
+
label="Placeholder 1"
|
|
216
|
+
:active="activeTab === 'placeholder1'"
|
|
217
|
+
as="button"
|
|
218
|
+
@click="activeTab = 'placeholder1'"
|
|
219
|
+
/>
|
|
220
|
+
<FdsTabsItem
|
|
221
|
+
label="Placeholder 2"
|
|
222
|
+
:active="activeTab === 'placeholder2'"
|
|
223
|
+
as="button"
|
|
224
|
+
@click="activeTab = 'placeholder2'"
|
|
225
|
+
/>
|
|
226
|
+
</FdsTabs>
|
|
227
|
+
|
|
228
|
+
<!-- localStorage TAB -->
|
|
229
|
+
<FdsBlockInfo v-if="activeTab === 'local'" heading="localStorage" icon="information" size="small">
|
|
230
|
+
<div v-if="!localStorageEntries.length" class="text-sm text-gray-600">Inga värden i localStorage.</div>
|
|
231
|
+
<div v-else class="max-h-72 overflow-auto border border-gray-200 rounded-md">
|
|
232
|
+
<table class="w-full text-left text-xs border-collapse">
|
|
233
|
+
<thead class="bg-gray-50">
|
|
234
|
+
<tr>
|
|
235
|
+
<th class="py-2 px-3 font-semibold">Nyckel</th>
|
|
236
|
+
<th class="py-2 px-3 font-semibold">Värde</th>
|
|
237
|
+
</tr>
|
|
238
|
+
</thead>
|
|
239
|
+
<tbody>
|
|
240
|
+
<tr
|
|
241
|
+
v-for="entry in localStorageEntries"
|
|
242
|
+
:key="`local-${entry.key}`"
|
|
243
|
+
class="border-t border-gray-200 align-top cursor-pointer hover:bg-gray-50"
|
|
244
|
+
@click="selectLocalEntry(entry)"
|
|
245
|
+
>
|
|
246
|
+
<td class="py-1.5 px-3 font-mono break-all">
|
|
247
|
+
{{ entry.key }}
|
|
248
|
+
</td>
|
|
249
|
+
<td class="py-1.5 px-3 font-mono whitespace-pre-wrap break-all">
|
|
250
|
+
{{ entry.value }}
|
|
251
|
+
</td>
|
|
252
|
+
</tr>
|
|
253
|
+
</tbody>
|
|
254
|
+
</table>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div class="mt-4 space-y-2">
|
|
258
|
+
<div class="flex flex-col gap-1">
|
|
259
|
+
<label class="text-xs font-semibold">Nyckel</label>
|
|
260
|
+
<input
|
|
261
|
+
v-model="localEditKey"
|
|
262
|
+
class="border border-gray-300 rounded px-2 py-1 text-xs font-mono bg-white"
|
|
263
|
+
placeholder="localStorage-nyckel"
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="flex flex-col gap-1">
|
|
267
|
+
<label class="text-xs font-semibold">Värde</label>
|
|
268
|
+
<textarea
|
|
269
|
+
v-model="localEditValue"
|
|
270
|
+
rows="3"
|
|
271
|
+
ref="localTextareaRef"
|
|
272
|
+
class="border border-gray-300 rounded px-2 py-1 text-xs font-mono bg-white overflow-hidden"
|
|
273
|
+
placeholder="Värde (lagras som sträng)"
|
|
274
|
+
/>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div v-if="localEditKey" class="mt-2 flex flex-wrap gap-2">
|
|
278
|
+
<FdsButtonSecondary text="Ta bort" size="sm" @click="deleteLocalEntry" />
|
|
279
|
+
<FdsButtonPrimary text="Spara" size="sm" @click="saveLocalEntry" />
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</FdsBlockInfo>
|
|
283
|
+
|
|
284
|
+
<!-- sessionStorage TAB -->
|
|
285
|
+
<FdsBlockInfo v-else-if="activeTab === 'session'" heading="sessionStorage" icon="information" size="small">
|
|
286
|
+
<div v-if="!sessionStorageEntries.length" class="text-sm text-gray-600">Inga värden i sessionStorage.</div>
|
|
287
|
+
<div v-else class="max-h-72 overflow-auto border border-gray-200 rounded-md">
|
|
288
|
+
<table class="w-full text-left text-xs border-collapse">
|
|
289
|
+
<thead class="bg-gray-50">
|
|
290
|
+
<tr>
|
|
291
|
+
<th class="py-2 px-3 font-semibold">Nyckel</th>
|
|
292
|
+
<th class="py-2 px-3 font-semibold">Värde</th>
|
|
293
|
+
</tr>
|
|
294
|
+
</thead>
|
|
295
|
+
<tbody>
|
|
296
|
+
<tr
|
|
297
|
+
v-for="entry in sessionStorageEntries"
|
|
298
|
+
:key="`session-${entry.key}`"
|
|
299
|
+
class="border-t border-gray-200 align-top cursor-pointer hover:bg-gray-50"
|
|
300
|
+
@click="selectSessionEntry(entry)"
|
|
301
|
+
>
|
|
302
|
+
<td class="py-1.5 px-3 font-mono break-all">
|
|
303
|
+
{{ entry.key }}
|
|
304
|
+
</td>
|
|
305
|
+
<td class="py-1.5 px-3 font-mono whitespace-pre-wrap break-all">
|
|
306
|
+
{{ entry.value }}
|
|
307
|
+
</td>
|
|
308
|
+
</tr>
|
|
309
|
+
</tbody>
|
|
310
|
+
</table>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<div class="mt-4 space-y-2">
|
|
314
|
+
<div class="flex flex-col gap-1">
|
|
315
|
+
<label class="text-xs font-semibold">Nyckel</label>
|
|
316
|
+
<input
|
|
317
|
+
v-model="sessionEditKey"
|
|
318
|
+
class="border border-gray-300 rounded px-2 py-1 text-xs font-mono bg-white"
|
|
319
|
+
placeholder="sessionStorage-nyckel"
|
|
320
|
+
/>
|
|
321
|
+
</div>
|
|
322
|
+
<div class="flex flex-col gap-1">
|
|
323
|
+
<label class="text-xs font-semibold">Värde</label>
|
|
324
|
+
<textarea
|
|
325
|
+
v-model="sessionEditValue"
|
|
326
|
+
rows="3"
|
|
327
|
+
ref="sessionTextareaRef"
|
|
328
|
+
class="border border-gray-300 rounded px-2 py-1 text-xs font-mono bg-white overflow-hidden"
|
|
329
|
+
placeholder="Värde (lagras som sträng)"
|
|
330
|
+
/>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<div v-if="sessionEditKey" class="mt-2 flex flex-wrap gap-2">
|
|
334
|
+
<FdsButtonSecondary text="Ta bort" size="sm" @click="deleteSessionEntry" />
|
|
335
|
+
<FdsButtonPrimary text="Spara" size="sm" @click="saveSessionEntry" />
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</FdsBlockInfo>
|
|
339
|
+
|
|
340
|
+
<!-- Cookies TAB -->
|
|
341
|
+
<FdsBlockInfo v-else-if="activeTab === 'cookies'" heading="Cookies" icon="information" size="small">
|
|
342
|
+
<div v-if="!cookieEntries.length" class="text-sm text-gray-600">Inga cookies satta för domänen.</div>
|
|
343
|
+
<div v-else class="max-h-72 overflow-auto border border-gray-200 rounded-md">
|
|
344
|
+
<table class="w-full text-left text-xs border-collapse">
|
|
345
|
+
<thead class="bg-gray-50">
|
|
346
|
+
<tr>
|
|
347
|
+
<th class="py-2 px-3 font-semibold">Namn</th>
|
|
348
|
+
<th class="py-2 px-3 font-semibold">Värde</th>
|
|
349
|
+
</tr>
|
|
350
|
+
</thead>
|
|
351
|
+
<tbody>
|
|
352
|
+
<tr
|
|
353
|
+
v-for="entry in cookieEntries"
|
|
354
|
+
:key="`cookie-${entry.key}`"
|
|
355
|
+
class="border-t border-gray-200 align-top cursor-pointer hover:bg-gray-50"
|
|
356
|
+
@click="selectCookieEntry(entry)"
|
|
357
|
+
>
|
|
358
|
+
<td class="py-1.5 px-3 font-mono break-all">
|
|
359
|
+
{{ entry.key }}
|
|
360
|
+
</td>
|
|
361
|
+
<td class="py-1.5 px-3 font-mono whitespace-pre-wrap break-all">
|
|
362
|
+
{{ entry.value }}
|
|
363
|
+
</td>
|
|
364
|
+
</tr>
|
|
365
|
+
</tbody>
|
|
366
|
+
</table>
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
<div class="mt-4 space-y-2">
|
|
370
|
+
<div class="flex flex-col gap-1">
|
|
371
|
+
<label class="text-xs font-semibold">Namn</label>
|
|
372
|
+
<input
|
|
373
|
+
v-model="cookieEditKey"
|
|
374
|
+
class="border border-gray-300 rounded px-2 py-1 text-xs font-mono bg-white"
|
|
375
|
+
placeholder="cookie-namn"
|
|
376
|
+
/>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="flex flex-col gap-1">
|
|
379
|
+
<label class="text-xs font-semibold">Värde</label>
|
|
380
|
+
<textarea
|
|
381
|
+
v-model="cookieEditValue"
|
|
382
|
+
rows="3"
|
|
383
|
+
ref="cookieTextareaRef"
|
|
384
|
+
class="border border-gray-300 rounded px-2 py-1 text-xs font-mono bg-white overflow-hidden"
|
|
385
|
+
placeholder="Värde (lagras som sträng)"
|
|
386
|
+
/>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<div v-if="cookieEditKey" class="mt-2 flex flex-wrap gap-2">
|
|
390
|
+
<FdsButtonSecondary text="Ta bort" size="sm" @click="deleteCookieEntry" />
|
|
391
|
+
<FdsButtonPrimary text="Spara" size="sm" @click="saveCookieEntry" />
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
</FdsBlockInfo>
|
|
395
|
+
|
|
396
|
+
<!-- Placeholder tabs -->
|
|
397
|
+
<FdsBlockInfo
|
|
398
|
+
v-else
|
|
399
|
+
heading="Placeholder"
|
|
400
|
+
icon="information"
|
|
401
|
+
size="small"
|
|
402
|
+
class="min-h-[200px] flex items-center justify-center text-sm text-gray-500"
|
|
403
|
+
>
|
|
404
|
+
(Tom flik – reserverad för framtida DevMode-verktyg)
|
|
405
|
+
</FdsBlockInfo>
|
|
406
|
+
|
|
407
|
+
<div class="mt-4 flex justify-end gap-3">
|
|
408
|
+
<FdsButtonSecondary text="Uppdatera" @click="refreshStorageEntries" />
|
|
409
|
+
<FdsButtonPrimary text="Stäng" @click="handleOpenChange(false)" />
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
</FdsModal>
|
|
413
|
+
</template>
|
|
@@ -27,6 +27,7 @@ const props = withDefaults(defineProps<FdsInputProps>(), {
|
|
|
27
27
|
mask: undefined,
|
|
28
28
|
maskOptions: undefined,
|
|
29
29
|
class: undefined,
|
|
30
|
+
pattern: undefined,
|
|
30
31
|
modelModifiers: () => ({}),
|
|
31
32
|
locale: 'sv',
|
|
32
33
|
ariaLabel: undefined,
|
|
@@ -300,6 +301,7 @@ watch(
|
|
|
300
301
|
:style="inputStyle"
|
|
301
302
|
:aria-label="props.ariaLabel"
|
|
302
303
|
:autocomplete="props.autocomplete"
|
|
304
|
+
:pattern="props.pattern"
|
|
303
305
|
v-bind="inputAttrs"
|
|
304
306
|
@input="handleInputChange"
|
|
305
307
|
@change="handleInputChange"
|