tantee-nuxt-commons 0.0.168 → 0.0.169

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.
@@ -1,167 +1,139 @@
1
1
  <script lang="ts" setup>
2
- import { VueSignaturePad } from 'vue-signature-pad'
2
+ import { VueSignaturePad } from 'vue-signature-pad';
3
3
  import { VInput } from 'vuetify/components/VInput'
4
- import { ref, computed, withDefaults, defineProps, defineExpose, watch } from 'vue'
5
- import { useAssetFile, type Base64Asset } from '../../composables/assetFile'
4
+ import { type Ref, ref, onMounted, onBeforeUnmount, nextTick,watch,defineExpose,useTemplateRef,computed } from 'vue'
5
+
6
+ interface SignatureOptions {
7
+ penColor: string
8
+ minWidth?: number
9
+ maxWidth?: number
10
+ }
6
11
 
7
12
  interface SignatureProps extends /* @vue-ignore */ InstanceType<typeof VInput['$props']> {
8
13
  title?: string
9
14
  btnName?: string
10
15
  titleConfirm?: string
11
- penColor?: string
12
- /** hydrate when id is present but base64String is missing */
13
- autoHydrate?: boolean
16
+ modelValue?: string
14
17
  }
15
18
 
16
19
  const props = withDefaults(defineProps<SignatureProps>(), {
17
- title: 'Signature',
20
+ title: 'Draw Your Signature',
18
21
  btnName: 'Draw Your Signature',
19
22
  titleConfirm: 'I Accept My Signature',
20
- penColor: '#303F9F',
21
- autoHydrate: true,
22
23
  })
23
24
 
24
- const { hydrateAssetFile } = useAssetFile()
25
-
26
- const model = defineModel<Base64Asset | string | null>({ default: null })
27
-
28
- // refs
29
- const inputRef = ref<InstanceType<typeof VInput> | null>(null)
30
- const signaturePadRef = ref<InstanceType<typeof VueSignaturePad> | null>(null)
31
-
32
- const isDialogOpen = ref(false)
25
+ const inputRef = useTemplateRef<VInput>("inputRef")
26
+ const signaturePadRef = useTemplateRef<typeof VueSignaturePad>("signaturePadRef")
33
27
 
34
- // signature pad options from props
35
- const signatureOptions = computed(() => ({
36
- penColor: props.penColor,
37
- minWidth: 0.5,
38
- maxWidth: 4,
39
- }))
28
+ const signatureData: Ref<string|null> = ref(null)
29
+ const colorOptions: string[] = ['#303F9F', '#1A2023', '#2E7D32', '#AC04BF']
30
+ const defaultColor: string = colorOptions[0]
31
+ const selectedColor: Ref<string> = ref(defaultColor)
32
+ const signatureOptions: Ref<SignatureOptions> = ref({ penColor: defaultColor, minWidth: 0.5, maxWidth: 4 })
33
+ const isDialogOpen: Ref<boolean> = ref(false)
40
34
 
41
- // internal normalized value
42
- const normalized = ref<Base64Asset | null>(null)
43
- const imageDataUrl = computed(() => {
44
- const val = normalized.value
45
- if (!val?.base64String) return null
46
-
47
- const base = val.base64String.trim()
35
+ const undoSignature = (): void => {
36
+ signaturePadRef.value?.undoSignature()
37
+ }
48
38
 
49
- if (base.startsWith("data:")) {
50
- return base
51
- }
39
+ const clearSignature = (): void => {
40
+ signaturePadRef.value?.clearSignature()
41
+ }
52
42
 
53
- const mime = (val as Base64File).fileType || "image/png"
54
- return `data:${mime};base64,${base}`
55
- })
43
+ const closeDialog = (): void => {
44
+ isDialogOpen.value = false
45
+ signaturePadRef.value?.clearSignature()
46
+ signaturePadRef.value?.clearCacheImages()
47
+ }
56
48
 
49
+ const emit = defineEmits<{
50
+ (event: 'update:modelValue', value: string|null): void
51
+ }>()
57
52
 
58
- // guards
59
- let syncing = false // block re-entrancy while normalizing/hydrating
53
+ const saveSignature = (): void => {
54
+ isDialogOpen.value = false
55
+ const { isEmpty, data } = signaturePadRef.value?.saveSignature()
56
+ signatureData.value = isEmpty ? null : data
57
+ }
60
58
 
61
- function wrapToAsset(input: Base64Asset | string | null): Base64Asset | null {
62
- if (input == null) return null
63
- return typeof input === 'string' ? { base64String: input } : input
59
+ const changePenColor = (color: string): void => {
60
+ selectedColor.value = color
61
+ signatureOptions.value = { penColor: color }
64
62
  }
65
63
 
66
- /** Normalize & (optionally) hydrate whenever external model changes */
67
- watch(
68
- model,
69
- async (val) => {
70
- if (syncing) return
71
- syncing = true
72
- try {
73
- const asAsset = wrapToAsset(val)
74
- normalized.value = asAsset
75
-
76
- // If parent provided a string, convert and write back once
77
- if (typeof val === 'string') {
78
- model.value = asAsset
79
- }
80
-
81
- // Hydrate if requested and needed (id present, no base64String)
82
- if (props.autoHydrate && asAsset?.id != null && !asAsset.base64String) {
83
- await hydrateAssetFile(asAsset) // mutates asAsset in-place
84
- // reflect hydrated base64String back to parent (guarded)
85
- model.value = asAsset
86
- }
87
- } finally {
88
- syncing = false
89
- }
90
- },
91
- { immediate: true }
92
- )
93
-
94
- // signature actions
95
- const undoSignature = () => signaturePadRef.value?.undoSignature()
96
- const clearSignature = () => signaturePadRef.value?.clearSignature()
97
-
98
- const closeDialog = () => {
99
- isDialogOpen.value = false
100
- signaturePadRef.value?.clearSignature()
101
- signaturePadRef.value?.clearCacheImages?.()
64
+ const openSignatureDialog = (): void => {
65
+ selectedColor.value = defaultColor
66
+ signatureOptions.value = { penColor: defaultColor }
67
+ isDialogOpen.value = true
68
+ nextTick(() =>{
69
+ if (props.modelValue) {
70
+ signaturePadRef.value?.fromDataURL(props.modelValue)
71
+ }
72
+ });
102
73
  }
103
74
 
104
- const saveSignature = () => {
105
- isDialogOpen.value = false
106
- const result = signaturePadRef.value?.saveSignature()
107
- if (!result) return
108
- const { isEmpty, data } = result
109
- if (isEmpty) {
110
- normalized.value = null
111
- model.value = null
112
- } else {
113
- const asset: Base64Asset = { base64String: data }
114
- normalized.value = asset
115
- model.value = asset
116
- }
75
+ const signaturePadHeight: Ref<string> = ref('')
76
+ const updateSignaturePadHeight = () => {
77
+ const screenHeight = window.innerHeight
78
+ signaturePadHeight.value = `${screenHeight * 0.4}px`
117
79
  }
118
80
 
119
- const openSignatureDialog = async () => {
120
- // ensure hydration before opening to preview correctly
121
- if (props.autoHydrate && normalized.value?.id != null && !normalized.value.base64String) {
122
- await hydrateAssetFile(normalized.value)
123
- model.value = normalized.value
124
- }
81
+ watch(signatureData,()=>{
82
+ emit('update:modelValue', signatureData.value)
83
+ })
125
84
 
126
- isDialogOpen.value = true
85
+ watch(()=>props.modelValue, (newValue) => {
86
+ signatureData.value = newValue || null
87
+ },{immediate: true})
127
88
 
128
- const existing = normalized.value?.base64String
129
- if (existing) {
130
- // seed after dialog mount
131
- requestAnimationFrame(() => signaturePadRef.value?.fromDataURL(existing))
132
- }
133
- }
89
+ onMounted(() => {
90
+ updateSignaturePadHeight()
91
+ window.addEventListener('resize', updateSignaturePadHeight)
92
+ })
134
93
 
135
- // validation passthrough
136
- const isValid = computed(() => inputRef.value?.isValid)
137
- const errorMessages = computed(() => inputRef.value?.errorMessages)
94
+ onBeforeUnmount(() => {
95
+ window.removeEventListener('resize', updateSignaturePadHeight)
96
+ })
97
+
98
+ const isValid = computed(()=>{
99
+ return inputRef.value?.isValid
100
+ })
101
+
102
+ const errorMessages = computed(()=>{
103
+ return inputRef.value?.errorMessages
104
+ })
138
105
 
139
106
  defineExpose({
140
107
  errorMessages,
141
108
  isValid,
142
- reset: () => inputRef.value?.reset(),
143
- resetValidation: () => inputRef.value?.resetValidation(),
144
- validate: () => inputRef.value?.validate(),
109
+ reset: ()=>inputRef.value?.reset(),
110
+ resetValidation : ()=>inputRef.value?.resetValidation(),
111
+ validate : ()=>inputRef.value?.validate(),
145
112
  })
146
113
  </script>
147
114
 
148
115
  <template>
149
- <v-input v-model="model" v-bind="$attrs" ref="inputRef">
150
- <template #default="{ isReadonly, isDisabled }">
151
- <v-card class="w-100" flat :variant="$attrs.variant" :title="props.title">
152
- <v-card-text v-if="normalized?.base64String">
153
- <v-img :src="imageDataUrl" cover />
116
+ <v-input v-model="signatureData" v-bind="$attrs" ref="inputRef">
117
+ <template #default="{isReadonly,isDisabled}">
118
+ <v-card
119
+ class="w-100"
120
+ flat
121
+ >
122
+ <v-card-text v-if="signatureData">
123
+ <v-img
124
+ :src="signatureData"
125
+ cover
126
+ />
154
127
  <v-icon
155
128
  class="position-absolute"
156
129
  style="top: 8px; right: 8px; z-index: 10;"
157
- @click="model = null; normalized = null"
158
- v-if="!isReadonly?.value"
130
+ @click="signatureData=null"
131
+ v-if="signatureData && !isReadonly.value"
159
132
  >
160
133
  mdi mdi-close-circle
161
134
  </v-icon>
162
135
  </v-card-text>
163
-
164
- <v-card-actions v-if="!isReadonly?.value">
136
+ <v-card-actions>
165
137
  <v-btn
166
138
  append-icon="mdi mdi-draw-pen"
167
139
  block
@@ -169,43 +141,79 @@ defineExpose({
169
141
  color="primary"
170
142
  variant="flat"
171
143
  @click="openSignatureDialog"
172
- :readonly="isReadonly?.value"
173
- :disabled="isDisabled?.value"
144
+ :readonly="isReadonly.value"
145
+ :disabled="isDisabled.value"
174
146
  >
175
147
  {{ props.btnName }}
176
148
  </v-btn>
177
149
  </v-card-actions>
178
-
179
- <v-dialog v-model="isDialogOpen" height="auto" persistent width="100%">
150
+ <v-dialog
151
+ v-model="isDialogOpen"
152
+ height="auto"
153
+ persistent
154
+ width="100%"
155
+ >
180
156
  <v-card>
181
157
  <v-toolbar>
182
158
  <v-toolbar-title class="text-no-wrap">
183
159
  {{ props.title }}
184
160
  </v-toolbar-title>
185
-
186
- <v-btn icon @click="undoSignature">
161
+ <v-btn
162
+ icon
163
+ @click="undoSignature"
164
+ >
187
165
  <v-icon>fa-solid fa-arrow-rotate-left</v-icon>
188
166
  </v-btn>
189
-
190
- <v-btn icon @click="clearSignature">
167
+ <v-btn
168
+ icon
169
+ @click="clearSignature"
170
+ >
191
171
  <v-icon>fa-solid fa-trash</v-icon>
192
172
  </v-btn>
193
-
194
- <v-btn icon @click="closeDialog">
173
+ <v-menu>
174
+ <template #activator="{ props: activatorProps }">
175
+ <v-btn
176
+ :color="selectedColor"
177
+ :icon="true"
178
+ v-bind="activatorProps"
179
+ >
180
+ <v-icon>fa-solid fa-paintbrush</v-icon>
181
+ </v-btn>
182
+ </template>
183
+ <v-list>
184
+ <v-row>
185
+ <v-col class="text-center">
186
+ <v-avatar
187
+ v-for="(color, index) in colorOptions"
188
+ :key="index"
189
+ :color="color"
190
+ :value="color"
191
+ class="mr-1"
192
+ @click="changePenColor(color)"
193
+ >
194
+ <v-icon color="white">
195
+ {{ selectedColor === color ? 'fa-solid fa-check' : '' }}
196
+ </v-icon>
197
+ </v-avatar>
198
+ </v-col>
199
+ </v-row>
200
+ </v-list>
201
+ </v-menu>
202
+ <v-btn
203
+ icon
204
+ @click="closeDialog"
205
+ >
195
206
  <v-icon>fa-solid fa-xmark</v-icon>
196
207
  </v-btn>
197
208
  </v-toolbar>
198
-
199
209
  <v-card-text>
200
210
  <VueSignaturePad
201
211
  ref="signaturePadRef"
202
212
  :options="signatureOptions"
203
- height="40vh"
213
+ :height="signaturePadHeight"
204
214
  />
205
215
  </v-card-text>
206
-
207
216
  <v-divider />
208
-
209
217
  <v-card-actions class="justify-center">
210
218
  <v-btn
211
219
  class="text-none"