@citizenplane/pimp 8.5.2 → 8.5.6
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/pimp.es.js +2260 -2347
- package/dist/pimp.umd.js +2 -2
- package/dist/style.css +1 -1
- package/package.json +18 -14
- package/src/assets/styles/base/_base.scss +4 -0
- package/src/components/helpers-utilities/TransitionExpand.vue +2 -10
- package/src/components/index.js +2 -0
- package/src/components/inputs/CpInput.vue +149 -170
- package/src/components/inputs/CpTextarea.vue +71 -77
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cpInput" :class="dynamicClasses" :aria-disabled="isDisabled" @click="focusOnInput">
|
|
3
|
-
<base-input-label v-if="label"
|
|
3
|
+
<base-input-label v-if="label" v-bind-once="{ for: inputIdentifier }" class="cpInput__label">
|
|
4
4
|
{{ inputLabelTitle }}
|
|
5
5
|
</base-input-label>
|
|
6
6
|
<div
|
|
@@ -19,13 +19,12 @@
|
|
|
19
19
|
</transition>
|
|
20
20
|
</div>
|
|
21
21
|
<input
|
|
22
|
-
|
|
22
|
+
v-model="inputModel"
|
|
23
|
+
v-bind-once="{ id: inputIdentifier }"
|
|
23
24
|
v-maska
|
|
24
25
|
:data-maska="mask"
|
|
25
|
-
:value="modelValue"
|
|
26
26
|
v-bind="restAttributes"
|
|
27
27
|
class="cpInput__inner"
|
|
28
|
-
@input="handleChange"
|
|
29
28
|
/>
|
|
30
29
|
<div v-if="hasAfterIcon" class="cpInput__icon cpInput__icon--isAfter">
|
|
31
30
|
<slot v-if="hasAfterIconSlot" name="input-icon-after" />
|
|
@@ -37,185 +36,161 @@
|
|
|
37
36
|
</div>
|
|
38
37
|
</div>
|
|
39
38
|
<transition-expand>
|
|
40
|
-
<
|
|
41
|
-
v-if="displayErrorMessage"
|
|
42
|
-
:is-invalid="isInvalid"
|
|
43
|
-
:for="inputIdentifier"
|
|
44
|
-
class="cpInput__label cpInput__label--isAfter"
|
|
45
|
-
>
|
|
39
|
+
<p v-if="displayErrorMessage" class="cpInput__error">
|
|
46
40
|
{{ errorMessage }}
|
|
47
|
-
</
|
|
41
|
+
</p>
|
|
48
42
|
</transition-expand>
|
|
49
43
|
</div>
|
|
50
44
|
</template>
|
|
51
45
|
|
|
52
|
-
<script>
|
|
53
|
-
import { ref } from 'vue'
|
|
46
|
+
<script setup>
|
|
47
|
+
import { ref, useAttrs, useSlots, computed, nextTick, onMounted } from 'vue'
|
|
48
|
+
|
|
49
|
+
import BaseInputLabel from '@/components/core/BaseInputLabel/index.vue'
|
|
54
50
|
|
|
55
51
|
import { randomString } from '@/helpers'
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export default {
|
|
62
|
-
name: 'CpInput',
|
|
63
|
-
components: {
|
|
64
|
-
CpIcon,
|
|
65
|
-
BaseInputLabel,
|
|
66
|
-
TransitionExpand,
|
|
53
|
+
const props = defineProps({
|
|
54
|
+
modelValue: {
|
|
55
|
+
type: [String, Number, Boolean],
|
|
56
|
+
default: '',
|
|
67
57
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
type: [String, Number, Boolean],
|
|
72
|
-
default: '',
|
|
73
|
-
},
|
|
74
|
-
label: {
|
|
75
|
-
type: String,
|
|
76
|
-
default: '',
|
|
77
|
-
},
|
|
78
|
-
inputId: {
|
|
79
|
-
type: String,
|
|
80
|
-
default: null,
|
|
81
|
-
},
|
|
82
|
-
isInvalid: {
|
|
83
|
-
type: Boolean,
|
|
84
|
-
default: false,
|
|
85
|
-
},
|
|
86
|
-
errorMessage: {
|
|
87
|
-
type: String,
|
|
88
|
-
default: '',
|
|
89
|
-
},
|
|
90
|
-
mask: {
|
|
91
|
-
type: [String, Object],
|
|
92
|
-
default: null,
|
|
93
|
-
},
|
|
94
|
-
removeBorder: {
|
|
95
|
-
type: Boolean,
|
|
96
|
-
default: false,
|
|
97
|
-
},
|
|
98
|
-
hideValidityIcon: {
|
|
99
|
-
type: Boolean,
|
|
100
|
-
default: false,
|
|
101
|
-
},
|
|
102
|
-
isLarge: {
|
|
103
|
-
type: Boolean,
|
|
104
|
-
default: false,
|
|
105
|
-
},
|
|
106
|
-
isSearch: {
|
|
107
|
-
type: Boolean,
|
|
108
|
-
default: false,
|
|
109
|
-
},
|
|
58
|
+
label: {
|
|
59
|
+
type: String,
|
|
60
|
+
default: '',
|
|
110
61
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// eslint-disable-next-line no-unused-vars
|
|
115
|
-
const { ['class']: value, id, ...restAttributes } = attrs
|
|
116
|
-
const inputIdentifier = id === undefined ? ref(randomString()) : id
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
inputIdentifier,
|
|
120
|
-
restAttributes,
|
|
121
|
-
}
|
|
62
|
+
inputId: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: null,
|
|
122
65
|
},
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
66
|
+
isInvalid: {
|
|
67
|
+
type: Boolean,
|
|
68
|
+
default: false,
|
|
127
69
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
'cpInput__icon--isValid': this.isValid,
|
|
154
|
-
'cpInput__icon--hasAfterAndValidityIcon': this.hasAfterIconSlot,
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
inputLabelTitle() {
|
|
158
|
-
if (this.label === '') return ''
|
|
70
|
+
errorMessage: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: '',
|
|
73
|
+
},
|
|
74
|
+
mask: {
|
|
75
|
+
type: [String, Object],
|
|
76
|
+
default: null,
|
|
77
|
+
},
|
|
78
|
+
removeBorder: {
|
|
79
|
+
type: Boolean,
|
|
80
|
+
default: false,
|
|
81
|
+
},
|
|
82
|
+
hideValidityIcon: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: false,
|
|
85
|
+
},
|
|
86
|
+
isLarge: {
|
|
87
|
+
type: Boolean,
|
|
88
|
+
default: false,
|
|
89
|
+
},
|
|
90
|
+
isSearch: {
|
|
91
|
+
type: Boolean,
|
|
92
|
+
default: false,
|
|
93
|
+
},
|
|
94
|
+
})
|
|
159
95
|
|
|
160
|
-
|
|
96
|
+
const emit = defineEmits(['update:modelValue'])
|
|
161
97
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return this.hasAfterIconSlot || this.isSearch
|
|
181
|
-
},
|
|
182
|
-
DOMElement() {
|
|
183
|
-
return this.$refs.cpInputContainer.children.namedItem(this.inputIdentifier)
|
|
184
|
-
},
|
|
185
|
-
displayErrorMessage() {
|
|
186
|
-
return this.isInputInvalid && this.errorMessage.length
|
|
187
|
-
},
|
|
188
|
-
isClearButtonVisible() {
|
|
189
|
-
return this.isSearch && this.modelValue.length
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
mounted() {
|
|
193
|
-
this.$nextTick(() => this.checkInputValidity())
|
|
98
|
+
defineOptions({ inheritAttrs: false })
|
|
99
|
+
|
|
100
|
+
const attrs = useAttrs()
|
|
101
|
+
|
|
102
|
+
// class is a reserved word, we can't remove 'class' property from attrs
|
|
103
|
+
// eslint-disable-next-line no-unused-vars
|
|
104
|
+
const { ['class']: value, id, ...restAttributes } = attrs
|
|
105
|
+
|
|
106
|
+
const inputIdentifier = ref(id || randomString())
|
|
107
|
+
|
|
108
|
+
const slots = useSlots()
|
|
109
|
+
|
|
110
|
+
const inputModel = defineModel({
|
|
111
|
+
type: [String, Number, Boolean],
|
|
112
|
+
default: '',
|
|
113
|
+
set(newValue) {
|
|
114
|
+
handleChange(newValue)
|
|
115
|
+
return newValue
|
|
194
116
|
},
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const isDOMElementValid = ref(true)
|
|
120
|
+
const cpInputContainer = ref()
|
|
121
|
+
|
|
122
|
+
const isDisabled = computed(() => checkAttribute('disabled'))
|
|
123
|
+
const isRequired = computed(() => checkAttribute('required'))
|
|
124
|
+
const isReadonly = computed(() => checkAttribute('readonly'))
|
|
125
|
+
|
|
126
|
+
const dynamicClasses = computed(() => {
|
|
127
|
+
return [
|
|
128
|
+
attrs.class,
|
|
129
|
+
{
|
|
130
|
+
'cpInput--isInvalid': isInputInvalid.value,
|
|
131
|
+
'cpInput--isDisabled': isDisabled.value,
|
|
132
|
+
'cpInput--hasNoBorder': props.removeBorder,
|
|
133
|
+
'cpInput--isLarge': props.isLarge,
|
|
134
|
+
'cpInput--isSearch': props.isSearch,
|
|
199
135
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
this.DOMElement.focus()
|
|
203
|
-
},
|
|
204
|
-
checkInputValidity() {
|
|
205
|
-
if (!this.DOMElement) return false
|
|
136
|
+
]
|
|
137
|
+
})
|
|
206
138
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
139
|
+
const inputLabelTitle = computed(() => {
|
|
140
|
+
if (props.label === '') return ''
|
|
141
|
+
|
|
142
|
+
const requiredMark = isRequired.value ? '*' : ''
|
|
143
|
+
return `${props.label} ${requiredMark}`
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const isValid = computed(() => inputModel.value && !isReadonly.value && !props.isInvalid && isDOMElementValid.value)
|
|
147
|
+
const isInputInvalid = computed(() => props.isInvalid || !isDOMElementValid.value)
|
|
148
|
+
|
|
149
|
+
const hasBeforeIconSlot = computed(() => !!slots['input-icon'])
|
|
150
|
+
const hasBeforeIcon = computed(() => hasBeforeIconSlot.value || props.isSearch)
|
|
151
|
+
|
|
152
|
+
const hasAfterIconSlot = computed(() => !!slots['input-icon-after'])
|
|
153
|
+
const hasAfterIcon = computed(() => hasAfterIconSlot.value || props.isSearch)
|
|
154
|
+
|
|
155
|
+
const iconValidityClasses = computed(() => {
|
|
156
|
+
return {
|
|
157
|
+
'cpInput__icon--isInvalid': isInputInvalid.value,
|
|
158
|
+
'cpInput__icon--isValid': isValid.value,
|
|
159
|
+
'cpInput__icon--hasAfterAndValidityIcon': hasAfterIconSlot.value,
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const DOMElement = computed(() => cpInputContainer.value.children.namedItem(inputIdentifier.value))
|
|
164
|
+
|
|
165
|
+
const displayErrorMessage = computed(() => isInputInvalid.value && props.errorMessage.length)
|
|
166
|
+
const isClearButtonVisible = computed(() => props.isSearch && inputModel.value.length)
|
|
167
|
+
|
|
168
|
+
const handleChange = (newValue) => {
|
|
169
|
+
emit('update:modelValue', newValue)
|
|
170
|
+
checkInputValidity()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const focusOnInput = () => {
|
|
174
|
+
if (!DOMElement.value) return
|
|
175
|
+
DOMElement.value.focus()
|
|
218
176
|
}
|
|
177
|
+
|
|
178
|
+
const checkInputValidity = () => {
|
|
179
|
+
if (!DOMElement.value) return false
|
|
180
|
+
|
|
181
|
+
isDOMElementValid.value =
|
|
182
|
+
(DOMElement.value.validity && DOMElement.value.validity.valid) ||
|
|
183
|
+
(DOMElement.value.validity && DOMElement.value.validity.valueMissing)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const checkAttribute = (attribute) => attrs[attribute] === '' || attrs[attribute] === true
|
|
187
|
+
|
|
188
|
+
const clearInputValue = () => emit('update:modelValue', '')
|
|
189
|
+
|
|
190
|
+
onMounted(async () => {
|
|
191
|
+
await nextTick()
|
|
192
|
+
checkInputValidity()
|
|
193
|
+
})
|
|
219
194
|
</script>
|
|
220
195
|
|
|
221
196
|
<style lang="scss">
|
|
@@ -324,13 +299,17 @@ export default {
|
|
|
324
299
|
|
|
325
300
|
&__label {
|
|
326
301
|
display: block;
|
|
302
|
+
margin-bottom: px-to-em(6);
|
|
303
|
+
}
|
|
327
304
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
305
|
+
&__error {
|
|
306
|
+
margin-top: px-to-em(6);
|
|
307
|
+
color: $error-color;
|
|
308
|
+
font-size: px-to-em(14);
|
|
309
|
+
font-weight: 500;
|
|
331
310
|
|
|
332
|
-
|
|
333
|
-
|
|
311
|
+
&::first-letter {
|
|
312
|
+
text-transform: capitalize;
|
|
334
313
|
}
|
|
335
314
|
}
|
|
336
315
|
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cpTextarea">
|
|
3
|
-
<base-input-label v-if="label"
|
|
3
|
+
<base-input-label v-if="label" v-bind-once="{ for: inputIdentifier }" class="cpTextarea__label">
|
|
4
4
|
{{ inputLabelTitle }}
|
|
5
5
|
</base-input-label>
|
|
6
6
|
<textarea
|
|
7
|
-
|
|
7
|
+
v-model="textareaModel"
|
|
8
|
+
v-bind-once="{ id: inputIdentifier }"
|
|
8
9
|
:disabled="disabled"
|
|
9
10
|
:placeholder="placeholder"
|
|
10
11
|
:required="required"
|
|
11
|
-
:value="modelValue"
|
|
12
12
|
:style="`min-height: ${height}px`"
|
|
13
13
|
:class="{ 'cpTextarea__input--isInvalid': isInvalid }"
|
|
14
14
|
class="cpTextarea__input"
|
|
15
|
-
@input="handleChange"
|
|
16
15
|
/>
|
|
17
16
|
<transition-expand>
|
|
18
17
|
<base-input-label
|
|
19
18
|
v-if="displayErrorMessage"
|
|
20
|
-
|
|
19
|
+
v-bind-once="{ for: inputIdentifier }"
|
|
21
20
|
is-invalid
|
|
22
21
|
class="cpTextarea__label cpTextarea__label--isAfter"
|
|
23
22
|
>
|
|
@@ -27,89 +26,84 @@
|
|
|
27
26
|
</div>
|
|
28
27
|
</template>
|
|
29
28
|
|
|
30
|
-
<script>
|
|
29
|
+
<script setup>
|
|
30
|
+
import { ref, computed } from 'vue'
|
|
31
|
+
|
|
31
32
|
import BaseInputLabel from '@/components/core/BaseInputLabel/index.vue'
|
|
32
33
|
import TransitionExpand from '@/components/helpers-utilities/TransitionExpand.vue'
|
|
33
34
|
|
|
34
35
|
import { randomString } from '@/helpers'
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const props = defineProps({
|
|
38
|
+
modelValue: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: '',
|
|
41
|
+
required: false,
|
|
40
42
|
},
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
required: false,
|
|
46
|
-
},
|
|
47
|
-
label: {
|
|
48
|
-
type: String,
|
|
49
|
-
default: '',
|
|
50
|
-
required: false,
|
|
51
|
-
},
|
|
52
|
-
placeholder: {
|
|
53
|
-
type: String,
|
|
54
|
-
default: '',
|
|
55
|
-
required: true,
|
|
56
|
-
},
|
|
57
|
-
required: {
|
|
58
|
-
type: Boolean,
|
|
59
|
-
default: false,
|
|
60
|
-
required: false,
|
|
61
|
-
},
|
|
62
|
-
inputId: {
|
|
63
|
-
type: String,
|
|
64
|
-
default: '',
|
|
65
|
-
required: false,
|
|
66
|
-
},
|
|
67
|
-
disabled: {
|
|
68
|
-
type: Boolean,
|
|
69
|
-
default: false,
|
|
70
|
-
required: false,
|
|
71
|
-
},
|
|
72
|
-
isInvalid: {
|
|
73
|
-
type: Boolean,
|
|
74
|
-
default: false,
|
|
75
|
-
required: false,
|
|
76
|
-
},
|
|
77
|
-
errorMessage: {
|
|
78
|
-
type: String,
|
|
79
|
-
default: '',
|
|
80
|
-
required: false,
|
|
81
|
-
},
|
|
82
|
-
height: {
|
|
83
|
-
type: Number,
|
|
84
|
-
default: 200,
|
|
85
|
-
required: false,
|
|
86
|
-
},
|
|
43
|
+
label: {
|
|
44
|
+
type: String,
|
|
45
|
+
default: '',
|
|
46
|
+
required: false,
|
|
87
47
|
},
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
48
|
+
placeholder: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: '',
|
|
51
|
+
required: true,
|
|
93
52
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return `${this.label} ${requiredLabel}`
|
|
99
|
-
},
|
|
100
|
-
displayErrorMessage() {
|
|
101
|
-
return this.isInvalid && this.errorMessage.length
|
|
102
|
-
},
|
|
53
|
+
required: {
|
|
54
|
+
type: Boolean,
|
|
55
|
+
default: false,
|
|
56
|
+
required: false,
|
|
103
57
|
},
|
|
104
|
-
|
|
105
|
-
|
|
58
|
+
inputId: {
|
|
59
|
+
type: String,
|
|
60
|
+
default: '',
|
|
61
|
+
required: false,
|
|
106
62
|
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
63
|
+
disabled: {
|
|
64
|
+
type: Boolean,
|
|
65
|
+
default: false,
|
|
66
|
+
required: false,
|
|
111
67
|
},
|
|
112
|
-
|
|
68
|
+
isInvalid: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: false,
|
|
71
|
+
required: false,
|
|
72
|
+
},
|
|
73
|
+
errorMessage: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: '',
|
|
76
|
+
required: false,
|
|
77
|
+
},
|
|
78
|
+
height: {
|
|
79
|
+
type: Number,
|
|
80
|
+
default: 200,
|
|
81
|
+
required: false,
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const emit = defineEmits(['update:modelValue'])
|
|
86
|
+
|
|
87
|
+
const textareaModel = defineModel({
|
|
88
|
+
type: String,
|
|
89
|
+
default: '',
|
|
90
|
+
required: false,
|
|
91
|
+
set(newValue) {
|
|
92
|
+
handleChange(newValue)
|
|
93
|
+
return newValue
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const inputIdentifier = ref(props.inputId || randomString())
|
|
98
|
+
|
|
99
|
+
const inputLabelTitle = computed(() => {
|
|
100
|
+
const requiredLabel = props.required && props.label ? '*' : ''
|
|
101
|
+
return `${props.label} ${requiredLabel}`
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const displayErrorMessage = computed(() => props.isInvalid && props.errorMessage.length)
|
|
105
|
+
|
|
106
|
+
const handleChange = (newValue) => emit('update:modelValue', newValue)
|
|
113
107
|
</script>
|
|
114
108
|
|
|
115
109
|
<style lang="scss">
|