@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.
@@ -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" :for="inputIdentifier" class="cpInput__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
- :id="inputIdentifier"
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
- <base-input-label
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
- </base-input-label>
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
- import BaseInputLabel from '@/components/core/BaseInputLabel/index.vue'
58
- import CpIcon from '@/components/visual/CpIcon.vue'
59
- import TransitionExpand from '@/components/helpers-utilities/TransitionExpand.vue'
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
- inheritAttrs: false,
69
- props: {
70
- modelValue: {
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
- emits: ['update:modelValue'],
112
- setup(props, { attrs }) {
113
- // class is a reserved work, we can't remove 'class' property from attrs
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
- data() {
124
- return {
125
- isDOMElementValid: true,
126
- }
66
+ isInvalid: {
67
+ type: Boolean,
68
+ default: false,
127
69
  },
128
- computed: {
129
- isDisabled() {
130
- return this.checkAttribute('disabled')
131
- },
132
- isRequired() {
133
- return this.checkAttribute('required')
134
- },
135
- isReadonly() {
136
- return this.checkAttribute('readonly')
137
- },
138
- dynamicClasses() {
139
- return [
140
- this.$attrs.class,
141
- {
142
- 'cpInput--isInvalid': this.isInputInvalid,
143
- 'cpInput--isDisabled': this.isDisabled,
144
- 'cpInput--hasNoBorder': this.removeBorder,
145
- 'cpInput--isLarge': this.isLarge,
146
- 'cpInput--isSearch': this.isSearch,
147
- },
148
- ]
149
- },
150
- iconValidityClasses() {
151
- return {
152
- 'cpInput__icon--isInvalid': this.isInputInvalid,
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
- const requiredMark = this.isRequired ? '*' : ''
96
+ const emit = defineEmits(['update:modelValue'])
161
97
 
162
- return `${this.label} ${requiredMark}`
163
- },
164
- isValid() {
165
- return this.modelValue && !this.isReadonly && !this.isInvalid && this.isDOMElementValid
166
- },
167
- isInputInvalid() {
168
- return this.isInvalid || !this.isDOMElementValid
169
- },
170
- hasBeforeIconSlot() {
171
- return !!this.$slots['input-icon']
172
- },
173
- hasBeforeIcon() {
174
- return this.hasBeforeIconSlot || this.isSearch
175
- },
176
- hasAfterIconSlot() {
177
- return !!this.$slots['input-icon-after']
178
- },
179
- hasAfterIcon() {
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
- methods: {
196
- handleChange(e) {
197
- this.$emit('update:modelValue', e.target.value)
198
- this.checkInputValidity()
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
- focusOnInput() {
201
- if (!this.DOMElement) return
202
- this.DOMElement.focus()
203
- },
204
- checkInputValidity() {
205
- if (!this.DOMElement) return false
136
+ ]
137
+ })
206
138
 
207
- this.isDOMElementValid =
208
- (this.DOMElement.validity && this.DOMElement.validity.valid) ||
209
- (this.DOMElement.validity && this.DOMElement.validity.valueMissing)
210
- },
211
- checkAttribute(attribute) {
212
- return this.$attrs[attribute] === '' || this.$attrs[attribute] === true
213
- },
214
- clearInputValue() {
215
- this.$emit('update:modelValue', '')
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
- &:not(#{&}--isAfter) {
329
- margin-bottom: px-to-em(6);
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
- &--isAfter {
333
- margin-top: px-to-em(6);
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" :for="inputReferenceId" class="cpTextarea__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
- :id="inputReferenceId"
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
- :for="inputReferenceId"
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
- export default {
37
- components: {
38
- BaseInputLabel,
39
- TransitionExpand,
37
+ const props = defineProps({
38
+ modelValue: {
39
+ type: String,
40
+ default: '',
41
+ required: false,
40
42
  },
41
- props: {
42
- modelValue: {
43
- type: String,
44
- default: '',
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
- emits: ['update:modelValue'],
89
- data() {
90
- return {
91
- inputReferenceId: this.inputId,
92
- }
48
+ placeholder: {
49
+ type: String,
50
+ default: '',
51
+ required: true,
93
52
  },
94
- computed: {
95
- inputLabelTitle() {
96
- const requiredLabel = this.required && this.label ? '*' : ''
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
- mounted() {
105
- if (!this.inputReferenceId) this.inputReferenceId = randomString()
58
+ inputId: {
59
+ type: String,
60
+ default: '',
61
+ required: false,
106
62
  },
107
- methods: {
108
- handleChange(e) {
109
- this.$emit('update:modelValue', e.target.value)
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">