@bildvitta/quasar-ui-asteroid 3.19.0-beta.3 → 3.19.0-beta.4
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/package.json +1 -1
- package/src/asteroid.js +2 -0
- package/src/components/form-generator/QasFormGenerator.vue +13 -92
- package/src/components/grid-generator/QasGridGenerator.vue +138 -102
- package/src/components/grid-item/QasGridItem.vue +42 -17
- package/src/components/tip/QasTip.vue +48 -0
- package/src/components/tip/QasTip.yml +26 -0
- package/src/components/tooltip/QasTooltip.vue +23 -0
- package/src/components/tooltip/QasTooltip.yml +10 -0
- package/src/composables/private/use-generator.js +210 -1
package/package.json
CHANGED
package/src/asteroid.js
CHANGED
|
@@ -77,8 +77,10 @@ export const QasTableGenerator = defineAsyncComponent(() => import('./components
|
|
|
77
77
|
export const QasTabsGenerator = defineAsyncComponent(() => import('./components/tabs-generator/QasTabsGenerator.vue'))
|
|
78
78
|
export const QasTextTruncate = defineAsyncComponent(() => import('./components/text-truncate/QasTextTruncate.vue'))
|
|
79
79
|
export const QasTimeline = defineAsyncComponent(() => import('./components/timeline/QasTimeline.vue'))
|
|
80
|
+
export const QasTip = defineAsyncComponent(() => import('./components/tip/QasTip.vue'))
|
|
80
81
|
export const QasToggle = defineAsyncComponent(() => import('./components/toggle/QasToggle.vue'))
|
|
81
82
|
export const QasToggleVisibility = defineAsyncComponent(() => import('./components/toggle-visibility/QasToggleVisibility.vue'))
|
|
83
|
+
export const QasTooltip = defineAsyncComponent(() => import('./components/tooltip/QasTooltip.vue'))
|
|
82
84
|
export const QasTransfer = defineAsyncComponent(() => import('./components/transfer/QasTransfer.vue'))
|
|
83
85
|
export const QasTreeGenerator = defineAsyncComponent(() => import('./components/tree-generator/QasTreeGenerator.vue'))
|
|
84
86
|
export const QasUploader = defineAsyncComponent(() => import('./components/uploader/QasUploader.vue'))
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
-
<div v-if="fieldsetItem.__hasSubset" class="column q-col-gutter-y-
|
|
27
|
+
<div v-if="fieldsetItem.__hasSubset" class="column q-col-gutter-y-md q-mt-none">
|
|
28
28
|
<div v-for="(subsetItem, subsetKey) in fieldsetItem.subset" :key="subsetKey" class="col-12">
|
|
29
29
|
<slot :name="`legend-${fieldsetItemKey}-${subsetKey}`">
|
|
30
30
|
<qas-header v-if="subsetItem.__hasHeader" v-bind="getHeaderProps({ values: subsetItem, isSubset: true} )" />
|
|
@@ -127,7 +127,13 @@ const emit = defineEmits(['update:modelValue'])
|
|
|
127
127
|
provide('isFormGenerator', true)
|
|
128
128
|
|
|
129
129
|
// composables
|
|
130
|
-
const {
|
|
130
|
+
const {
|
|
131
|
+
classes,
|
|
132
|
+
getFieldClass,
|
|
133
|
+
getFieldSetColumnClass,
|
|
134
|
+
getHeaderProps,
|
|
135
|
+
getNormalizedFields
|
|
136
|
+
} = useGenerator({ props })
|
|
131
137
|
|
|
132
138
|
const { fieldsetClasses, hasFieldset } = useFieldset({ props })
|
|
133
139
|
|
|
@@ -189,70 +195,13 @@ const normalizedFields = computed(() => {
|
|
|
189
195
|
}
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
return getNormalizedFields(
|
|
198
|
+
return getNormalizedFields({
|
|
199
|
+
items: props.fieldset,
|
|
200
|
+
fields: props.fields,
|
|
201
|
+
result: props.modelValue
|
|
202
|
+
})
|
|
193
203
|
})
|
|
194
204
|
|
|
195
|
-
/**
|
|
196
|
-
* Função recursiva na qual se é utilizada para definir a estrutura de um fieldset e também de um subset (presente dentro do fieldset).
|
|
197
|
-
*
|
|
198
|
-
* @param items {Object} items - Objeto contendo a estrutura de um fieldset ou subset
|
|
199
|
-
*/
|
|
200
|
-
function getNormalizedFields (items) {
|
|
201
|
-
const result = {}
|
|
202
|
-
|
|
203
|
-
for (const key in items) {
|
|
204
|
-
const item = items[key]
|
|
205
|
-
|
|
206
|
-
// Propriedades que são recebidas em cada fieldset / subset desconstruida
|
|
207
|
-
const {
|
|
208
|
-
label,
|
|
209
|
-
description,
|
|
210
|
-
headerProps,
|
|
211
|
-
column,
|
|
212
|
-
buttonProps,
|
|
213
|
-
fields = [],
|
|
214
|
-
subset = {}
|
|
215
|
-
} = item
|
|
216
|
-
|
|
217
|
-
// Valida caso tenha um header
|
|
218
|
-
const hasHeader = !!(label || description || Object.keys(headerProps || {}).length)
|
|
219
|
-
|
|
220
|
-
// Valida caso tenha um subset dentro de um fieldset
|
|
221
|
-
const hasSubset = !!Object.keys(subset).length
|
|
222
|
-
|
|
223
|
-
// Estrutura de um fieldset e subset
|
|
224
|
-
const structure = {
|
|
225
|
-
label,
|
|
226
|
-
description,
|
|
227
|
-
column,
|
|
228
|
-
buttonProps,
|
|
229
|
-
headerProps,
|
|
230
|
-
fields: {},
|
|
231
|
-
subset: hasSubset ? getNormalizedFields(subset) : {},
|
|
232
|
-
|
|
233
|
-
// Propriedades auxiliares para controle na view
|
|
234
|
-
__hasHeader: hasHeader,
|
|
235
|
-
__hasSubset: hasSubset,
|
|
236
|
-
__hasFieldset: true
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Percorre as lista de fields (keys) que estão dentro do fieldset / subset
|
|
240
|
-
fields.forEach(fieldKey => {
|
|
241
|
-
// Encontra dentro da prop fields o field correspondente a key
|
|
242
|
-
const field = props.fields[fieldKey]
|
|
243
|
-
|
|
244
|
-
if (!field) return
|
|
245
|
-
|
|
246
|
-
// Adiciona o field ao fields da estrutura
|
|
247
|
-
Object.assign(structure.fields, { [fieldKey]: field })
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
result[key] = structure
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return result
|
|
254
|
-
}
|
|
255
|
-
|
|
256
205
|
const fieldContainerClasses = computed(() => {
|
|
257
206
|
return [
|
|
258
207
|
...classes.value,
|
|
@@ -267,34 +216,6 @@ function getFieldType ({ type }) {
|
|
|
267
216
|
return type === 'hidden' ? 'hidden' : 'visible'
|
|
268
217
|
}
|
|
269
218
|
|
|
270
|
-
/**
|
|
271
|
-
* Retorna as propriedades de um header.
|
|
272
|
-
*
|
|
273
|
-
* @param {Object} values - Contém as propriedades de um fieldset/subset.
|
|
274
|
-
* @param {Boolean} isSubset - Verifica se o header será usado em um subset.
|
|
275
|
-
*
|
|
276
|
-
* @returns {Object} Retorna as propriedades do header.
|
|
277
|
-
*/
|
|
278
|
-
function getHeaderProps ({ values, isSubset }) {
|
|
279
|
-
const typography = isSubset ? 'h5' : 'h4'
|
|
280
|
-
|
|
281
|
-
// Separa as propriedades de label e o restante.
|
|
282
|
-
const { labelProps, ...headerProps } = values.headerProps || {}
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
description: values.description,
|
|
286
|
-
|
|
287
|
-
// Mesmo que passado a tipografia no headerProps, a tipografia é sobreposta para manter a hierarquia sempre.
|
|
288
|
-
labelProps: {
|
|
289
|
-
...labelProps,
|
|
290
|
-
...(values.label && { label: values.label }),
|
|
291
|
-
typography
|
|
292
|
-
},
|
|
293
|
-
|
|
294
|
-
...headerProps
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
219
|
function isFieldDisabled ({ disable }) {
|
|
299
220
|
return disable || props.disable
|
|
300
221
|
}
|
|
@@ -1,32 +1,76 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<component :is="component.is" v-bind="component.props">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
<div class="column q-col-gutter-y-lg">
|
|
4
|
+
<div v-for="(fieldsetItem, fieldsetItemKey) in normalizedFields" :key="fieldsetItemKey">
|
|
5
|
+
<q-separator v-if="hasSeparator({ item: fieldsetItem })" class="q-mb-lg" />
|
|
6
|
+
|
|
7
|
+
<qas-header v-if="fieldsetItem.__hasHeader" v-bind="getHeaderProps({ values: fieldsetItem })" />
|
|
8
|
+
|
|
9
|
+
<div :class="classes">
|
|
10
|
+
<div v-for="(field, key) in fieldsetItem.fields" :key="key" :class="getContainerClasses({ key })">
|
|
11
|
+
<slot :field="field" :name="`field-${field.name}`">
|
|
12
|
+
<qas-grid-item v-bind="getGridItemProps(field)">
|
|
13
|
+
<template #header>
|
|
14
|
+
<slot :field="field" :name="`header-field-${field.name}`">
|
|
15
|
+
<slot :field="field" name="header">
|
|
16
|
+
<div :class="headerClasses" :data-cy="`grid-generator-${field.name}-field`" :title="getTitle(field, 'label')">
|
|
17
|
+
{{ field.label }}
|
|
18
|
+
</div>
|
|
19
|
+
</slot>
|
|
20
|
+
</slot>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<template #content>
|
|
24
|
+
<slot :field="field" :name="`content-field-${field.name}`">
|
|
25
|
+
<slot :field="field" name="content">
|
|
26
|
+
<div :class="getContentClasses(field)" :data-cy="`grid-generator-${field.name}-result`" :title="getTitle(field, 'formattedResult')">
|
|
27
|
+
{{ field.formattedResult }}
|
|
28
|
+
</div>
|
|
29
|
+
</slot>
|
|
30
|
+
</slot>
|
|
31
|
+
</template>
|
|
32
|
+
</qas-grid-item>
|
|
33
|
+
</slot>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div v-if="fieldsetItem.__hasSubset" class="q-gutter-y-md q-mt-md">
|
|
38
|
+
<div v-for="(subsetItem, subsetKey) in fieldsetItem.subset" :key="subsetKey">
|
|
39
|
+
<div v-if="hasSeparator({ item: subsetItem, isSubset: true })" class="q-pb-md">
|
|
40
|
+
<q-separator />
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<qas-header v-if="subsetItem.__hasHeader" v-bind="getHeaderProps({ values: subsetItem, isSubset: true })" />
|
|
44
|
+
|
|
45
|
+
<div :class="classes">
|
|
46
|
+
<div v-for="(field, key) in subsetItem.fields" :key="key" :class="getContainerClasses({ key })">
|
|
47
|
+
<slot :field="field" :name="`field-${field.name}`">
|
|
48
|
+
<qas-grid-item v-bind="getGridItemProps(field)">
|
|
49
|
+
<template #header>
|
|
50
|
+
<slot :field="field" :name="`header-field-${field.name}`">
|
|
51
|
+
<slot :field="field" name="header">
|
|
52
|
+
<div :class="headerClasses" :data-cy="`grid-generator-${field.name}-field`" :title="getTitle(field, 'label')">
|
|
53
|
+
{{ field.label }}
|
|
54
|
+
</div>
|
|
55
|
+
</slot>
|
|
56
|
+
</slot>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<template #content>
|
|
60
|
+
<slot :field="field" :name="`content-field-${field.name}`">
|
|
61
|
+
<slot :field="field" name="content">
|
|
62
|
+
<div :class="getContentClasses(field)" :data-cy="`grid-generator-${field.name}-result`" :title="getTitle(field, 'formattedResult')">
|
|
63
|
+
{{ field.formattedResult }}
|
|
64
|
+
</div>
|
|
65
|
+
</slot>
|
|
66
|
+
</slot>
|
|
67
|
+
</template>
|
|
68
|
+
</qas-grid-item>
|
|
15
69
|
</slot>
|
|
16
|
-
</
|
|
17
|
-
</
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<slot :field="field" :name="`content-field-${field.name}`">
|
|
21
|
-
<slot :field="field" name="content">
|
|
22
|
-
<div :class="getContentClasses(field)" :data-cy="`grid-generator-${field.name}-result`" :title="getTitle(field, 'formattedResult')">
|
|
23
|
-
{{ field.formattedResult }}
|
|
24
|
-
</div>
|
|
25
|
-
</slot>
|
|
26
|
-
</slot>
|
|
27
|
-
</template>
|
|
28
|
-
</qas-grid-item>
|
|
29
|
-
</slot>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
30
74
|
</div>
|
|
31
75
|
</div>
|
|
32
76
|
</component>
|
|
@@ -38,10 +82,8 @@ import QasGridItem from '../grid-item/QasGridItem.vue'
|
|
|
38
82
|
import QasHeader from '../header/QasHeader.vue'
|
|
39
83
|
|
|
40
84
|
import useGenerator, { baseProps } from '../../composables/private/use-generator'
|
|
41
|
-
import { isEmpty, humanize } from '../../helpers'
|
|
42
85
|
import { useScreen } from '../../composables'
|
|
43
|
-
import
|
|
44
|
-
import { ref, computed, watch } from 'vue'
|
|
86
|
+
import { computed } from 'vue'
|
|
45
87
|
|
|
46
88
|
// define component name
|
|
47
89
|
defineOptions({ name: 'QasGridGenerator' })
|
|
@@ -58,13 +100,23 @@ const props = defineProps({
|
|
|
58
100
|
},
|
|
59
101
|
|
|
60
102
|
contentClass: {
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
type: [Array, Object, String],
|
|
104
|
+
default: ''
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
fieldset: {
|
|
108
|
+
type: Object,
|
|
109
|
+
default: () => ({})
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
fieldsProps: {
|
|
113
|
+
type: Object,
|
|
114
|
+
default: () => ({})
|
|
63
115
|
},
|
|
64
116
|
|
|
65
117
|
headerClass: {
|
|
66
|
-
|
|
67
|
-
|
|
118
|
+
type: [Array, Object, String],
|
|
119
|
+
default: ''
|
|
68
120
|
},
|
|
69
121
|
|
|
70
122
|
headerProps: {
|
|
@@ -73,13 +125,13 @@ const props = defineProps({
|
|
|
73
125
|
},
|
|
74
126
|
|
|
75
127
|
emptyResultText: {
|
|
76
|
-
|
|
77
|
-
|
|
128
|
+
type: String,
|
|
129
|
+
default: '-'
|
|
78
130
|
},
|
|
79
131
|
|
|
80
132
|
result: {
|
|
81
|
-
|
|
82
|
-
|
|
133
|
+
type: Object,
|
|
134
|
+
default: () => ({})
|
|
83
135
|
},
|
|
84
136
|
|
|
85
137
|
useBox: {
|
|
@@ -87,13 +139,13 @@ const props = defineProps({
|
|
|
87
139
|
},
|
|
88
140
|
|
|
89
141
|
useEmptyResult: {
|
|
90
|
-
|
|
91
|
-
|
|
142
|
+
type: Boolean,
|
|
143
|
+
default: true
|
|
92
144
|
},
|
|
93
145
|
|
|
94
146
|
useEllipsis: {
|
|
95
|
-
|
|
96
|
-
|
|
147
|
+
type: Boolean,
|
|
148
|
+
default: true
|
|
97
149
|
},
|
|
98
150
|
|
|
99
151
|
useInline: {
|
|
@@ -102,13 +154,15 @@ const props = defineProps({
|
|
|
102
154
|
})
|
|
103
155
|
|
|
104
156
|
// composables
|
|
105
|
-
const {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
157
|
+
const {
|
|
158
|
+
classes,
|
|
159
|
+
getFieldClass,
|
|
160
|
+
getHeaderProps,
|
|
161
|
+
getNormalizedFields,
|
|
162
|
+
getFieldsByResult
|
|
163
|
+
} = useGenerator({ props, isGrid: true })
|
|
164
|
+
|
|
165
|
+
// computeds
|
|
112
166
|
const component = computed(() => {
|
|
113
167
|
return {
|
|
114
168
|
is: props.useBox ? QasBox : 'div',
|
|
@@ -116,7 +170,7 @@ const component = computed(() => {
|
|
|
116
170
|
}
|
|
117
171
|
})
|
|
118
172
|
|
|
119
|
-
const
|
|
173
|
+
const headerClasses = computed(() => {
|
|
120
174
|
return [
|
|
121
175
|
props.headerClass,
|
|
122
176
|
|
|
@@ -126,65 +180,30 @@ const headerClass = computed(() => {
|
|
|
126
180
|
]
|
|
127
181
|
})
|
|
128
182
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const fields = {}
|
|
141
|
-
|
|
142
|
-
for (const key in props.fields) {
|
|
143
|
-
const result = props.result[key]
|
|
144
|
-
|
|
145
|
-
const validate = Array.isArray(result)
|
|
146
|
-
? result.length
|
|
147
|
-
: isObject(result) ? Object.keys(result).length : result
|
|
148
|
-
|
|
149
|
-
if (validate) {
|
|
150
|
-
fields[key] = props.fields[key]
|
|
183
|
+
const normalizedFields = computed(() => {
|
|
184
|
+
const hasFieldset = !!Object.keys(props.fieldset).length
|
|
185
|
+
|
|
186
|
+
// No caso de não ter "fieldset", virá via props o result e fields.
|
|
187
|
+
if (!hasFieldset) {
|
|
188
|
+
return {
|
|
189
|
+
default: {
|
|
190
|
+
fields: getFieldsByResult({ fields: props.fields, result: props.result }),
|
|
191
|
+
headerProps: props.headerProps,
|
|
192
|
+
__hasHeader: Object.keys(props.headerProps).length
|
|
193
|
+
}
|
|
151
194
|
}
|
|
152
195
|
}
|
|
153
196
|
|
|
154
|
-
|
|
197
|
+
// No caso de ter "fieldset", deve normalizar os fields/result de acordo com o fieldset.
|
|
198
|
+
return getNormalizedFields({
|
|
199
|
+
items: props.fieldset,
|
|
200
|
+
fields: props.fields,
|
|
201
|
+
result: props.result,
|
|
202
|
+
isGrid: true
|
|
203
|
+
})
|
|
155
204
|
})
|
|
156
205
|
|
|
157
|
-
// watch
|
|
158
|
-
watch(() => formattedFields.value, setFieldsByResult, { immediate: true })
|
|
159
|
-
|
|
160
206
|
// functions
|
|
161
|
-
function getFieldsByResult () {
|
|
162
|
-
if (!hasResult.value || !hasFields.value) return {}
|
|
163
|
-
|
|
164
|
-
const result = { ...props.result }
|
|
165
|
-
const fieldsByResult = {}
|
|
166
|
-
|
|
167
|
-
for (const key in formattedFields.value) {
|
|
168
|
-
const field = formattedFields.value[key] || {}
|
|
169
|
-
|
|
170
|
-
if (!field.type) continue
|
|
171
|
-
|
|
172
|
-
const humanizedResult = humanize(field, result[key])
|
|
173
|
-
const formattedResult = isEmpty({ value: humanizedResult }) ? props.emptyResultText : humanizedResult
|
|
174
|
-
|
|
175
|
-
fieldsByResult[key] = {
|
|
176
|
-
...field,
|
|
177
|
-
formattedResult
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return fieldsByResult
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function setFieldsByResult () {
|
|
185
|
-
fieldsByResult.value = getFieldsByResult()
|
|
186
|
-
}
|
|
187
|
-
|
|
188
207
|
function getContainerClasses ({ key }) {
|
|
189
208
|
if (props.useInline) return 'row justify-between col-12'
|
|
190
209
|
|
|
@@ -211,4 +230,21 @@ function getContentClasses (field) {
|
|
|
211
230
|
}
|
|
212
231
|
]
|
|
213
232
|
}
|
|
233
|
+
|
|
234
|
+
function getGridItemProps (field) {
|
|
235
|
+
return {
|
|
236
|
+
useEllipsis: hasEllipsis(field),
|
|
237
|
+
useInline: props.useInline,
|
|
238
|
+
|
|
239
|
+
...(props.fieldsProps[field.name] || {})
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function hasSeparator ({ item, isSubset }) {
|
|
244
|
+
/**
|
|
245
|
+
* No caso de for subset, por padrão terá o separator, à não ser que for passado
|
|
246
|
+
* o separator como false. Caso seja um fieldset, verificar se foi passado a chave "useSeparator".
|
|
247
|
+
*/
|
|
248
|
+
return isSubset ? (item.useSeparator ?? true) : item.useSeparator
|
|
249
|
+
}
|
|
214
250
|
</script>
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="classes.container">
|
|
3
|
-
<header
|
|
3
|
+
<header :class="classes.header">
|
|
4
4
|
<slot name="header">
|
|
5
|
-
|
|
5
|
+
<div v-bind="textAttributes">
|
|
6
|
+
{{ props.label }}
|
|
7
|
+
</div>
|
|
6
8
|
</slot>
|
|
9
|
+
|
|
10
|
+
<qas-tip v-if="props.tip" class="q-ml-xs" :text="props.tip" />
|
|
7
11
|
</header>
|
|
8
12
|
|
|
9
13
|
<div v-bind="contentProps">
|
|
@@ -15,6 +19,7 @@
|
|
|
15
19
|
</template>
|
|
16
20
|
|
|
17
21
|
<script setup>
|
|
22
|
+
import QasTip from '../tip/QasTip.vue'
|
|
18
23
|
import { useScreen } from '../../composables'
|
|
19
24
|
|
|
20
25
|
import { computed } from 'vue'
|
|
@@ -39,6 +44,16 @@ const props = defineProps({
|
|
|
39
44
|
value: {
|
|
40
45
|
type: [String, Number, Boolean],
|
|
41
46
|
default: ''
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
tip: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: ''
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
content: {
|
|
55
|
+
type: Object,
|
|
56
|
+
default: () => ({})
|
|
42
57
|
}
|
|
43
58
|
})
|
|
44
59
|
|
|
@@ -50,40 +65,50 @@ const hasEllipsis = computed(() => props.useEllipsis && !screen.isSmall)
|
|
|
50
65
|
const classes = computed(() => {
|
|
51
66
|
const isInline = props.useInline && !screen.isSmall
|
|
52
67
|
|
|
68
|
+
const { typography } = props.content
|
|
69
|
+
|
|
53
70
|
return {
|
|
54
71
|
container: {
|
|
55
|
-
flex: isInline
|
|
56
|
-
'q-col-gutter-x-md': isInline,
|
|
57
|
-
'justify-between': isInline,
|
|
58
|
-
'col-12': isInline,
|
|
59
|
-
'no-wrap': isInline
|
|
72
|
+
'flex justify-between col-12 no-wrap': isInline
|
|
60
73
|
},
|
|
61
74
|
|
|
62
75
|
header: {
|
|
63
76
|
'text-caption': !isInline,
|
|
64
|
-
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Necessário adicionar o padding à direita no header ao invés do gutter, pois ao usar o gutter no eixo x,
|
|
80
|
+
* ele adiciona um espaçamento à direita do content de forma errada, deixando o espaçamento maior que o do header.
|
|
81
|
+
*/
|
|
82
|
+
'text-body1 q-pr-md': isInline,
|
|
83
|
+
|
|
84
|
+
// classes por conta do tip.
|
|
85
|
+
'row no-wrap': props.tip,
|
|
86
|
+
'items-center': props.tip || typography,
|
|
87
|
+
flex: typography
|
|
65
88
|
},
|
|
66
89
|
|
|
67
90
|
content: {
|
|
68
91
|
'text-grey-10': true,
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
|
|
93
|
+
// é possível passar uma tipografia personalizada.
|
|
94
|
+
[`text-${typography}`]: typography,
|
|
95
|
+
|
|
96
|
+
'text-body1': !isInline && !typography,
|
|
97
|
+
'text-subtitle1': isInline && !typography,
|
|
98
|
+
|
|
71
99
|
ellipsis: hasEllipsis.value
|
|
72
100
|
}
|
|
73
101
|
}
|
|
74
102
|
})
|
|
75
103
|
|
|
76
|
-
const headerProps = computed(() => {
|
|
77
|
-
return {
|
|
78
|
-
class: classes.value.header,
|
|
79
|
-
...(hasEllipsis.value && { title: props.label })
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
|
|
83
104
|
const contentProps = computed(() => {
|
|
84
105
|
return {
|
|
85
106
|
class: classes.value.content,
|
|
86
107
|
...(hasEllipsis.value && { title: props.value })
|
|
87
108
|
}
|
|
88
109
|
})
|
|
110
|
+
|
|
111
|
+
const textAttributes = computed(() => {
|
|
112
|
+
return hasEllipsis.value ? { title: props.label } : undefined
|
|
113
|
+
})
|
|
89
114
|
</script>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<q-icon v-bind="iconProps" aria-hidden="false" :aria-label="props.text" class="qas-tip">
|
|
3
|
+
<qas-tooltip :text="props.text" />
|
|
4
|
+
</q-icon>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { computed } from 'vue'
|
|
9
|
+
|
|
10
|
+
defineOptions({ name: 'QasTip' })
|
|
11
|
+
|
|
12
|
+
const props = defineProps({
|
|
13
|
+
text: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: ''
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
icon: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: 'sym_r_help'
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
size: {
|
|
24
|
+
type: String,
|
|
25
|
+
default: '16px'
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
color: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: 'grey-8'
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// computeds
|
|
35
|
+
const iconProps = computed(() => {
|
|
36
|
+
return {
|
|
37
|
+
name: props.icon,
|
|
38
|
+
size: props.size,
|
|
39
|
+
color: props.color
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<style lang="scss">
|
|
45
|
+
.qas-tip {
|
|
46
|
+
cursor: help;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type: component
|
|
2
|
+
|
|
3
|
+
meta:
|
|
4
|
+
desc: Componente de dica com tooltip
|
|
5
|
+
|
|
6
|
+
props:
|
|
7
|
+
text:
|
|
8
|
+
desc: Valor exibido no tooltip
|
|
9
|
+
default: ''
|
|
10
|
+
type: String
|
|
11
|
+
|
|
12
|
+
icon:
|
|
13
|
+
desc: Ícone utilizado no tip.
|
|
14
|
+
default: sym_r_help
|
|
15
|
+
type: String
|
|
16
|
+
|
|
17
|
+
size:
|
|
18
|
+
desc: Tamanho do ícone.
|
|
19
|
+
type: String
|
|
20
|
+
default: 16px
|
|
21
|
+
examples: ['sm', '24px']
|
|
22
|
+
|
|
23
|
+
color:
|
|
24
|
+
desc: Cor do ícone.
|
|
25
|
+
type: String
|
|
26
|
+
default: grey-8
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<q-tooltip v-bind="tooltipProps" class="bg-grey-10">
|
|
3
|
+
{{ props.text }}
|
|
4
|
+
</q-tooltip>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
defineOptions({ name: 'QasTooltip' })
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
text: {
|
|
12
|
+
type: String,
|
|
13
|
+
default: ''
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// consts
|
|
18
|
+
const tooltipProps = {
|
|
19
|
+
anchor: 'center right',
|
|
20
|
+
self: 'center left',
|
|
21
|
+
offset: [5, 5]
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import isObject from 'lodash-es/isObject'
|
|
1
2
|
import { Spacing } from '../../enums/Spacing'
|
|
2
3
|
import { gutterValidator } from '../../helpers/private/gutter-validator'
|
|
3
4
|
import useScreen from '../use-screen'
|
|
5
|
+
import { isEmpty, humanize, filterObject } from '../../helpers'
|
|
4
6
|
|
|
5
7
|
import { computed } from 'vue'
|
|
6
8
|
|
|
@@ -90,6 +92,209 @@ export default function ({ props = {}, isGrid = false }) {
|
|
|
90
92
|
return typeof column === 'string' ? _getStringColumns(column) : _getBreakpoint(column)
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Normaliza as propriedades do header, e a tipografia com base se é um subset.
|
|
97
|
+
*
|
|
98
|
+
* @function
|
|
99
|
+
* @param {Object} options
|
|
100
|
+
* @param {Object} options.values - Contém as propriedades de um fieldset/subset.
|
|
101
|
+
* @param {boolean} options.isSubset - Verifica se o header será usado em um subset.
|
|
102
|
+
*
|
|
103
|
+
* @returns {Object} Retorna as propriedades do header.
|
|
104
|
+
*/
|
|
105
|
+
function getHeaderProps ({ values, isSubset }) {
|
|
106
|
+
const typography = isSubset ? 'h5' : 'h4'
|
|
107
|
+
|
|
108
|
+
// Separa as propriedades de label e o restante.
|
|
109
|
+
const { labelProps, ...headerProps } = values.headerProps || {}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
description: values.description,
|
|
113
|
+
|
|
114
|
+
// Mesmo que passado a tipografia no headerProps, a tipografia é sobreposta para manter a hierarquia sempre.
|
|
115
|
+
labelProps: {
|
|
116
|
+
...labelProps,
|
|
117
|
+
...(values.label && { label: values.label }),
|
|
118
|
+
typography
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
...headerProps
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Normaliza o objeto para retornar um fieldset.
|
|
127
|
+
*
|
|
128
|
+
* @function
|
|
129
|
+
* @param {Object} params
|
|
130
|
+
* @param {Object} params.items - Objeto contendo a estrutura de um fieldset ou subset
|
|
131
|
+
* @param {Object} params.fields - Campos
|
|
132
|
+
* @param {Object} params.result - Resultado com base nos fields
|
|
133
|
+
* @param {boolean} params.isGrid - Chave para indicar se é um grid-generator.
|
|
134
|
+
* @param {boolean} params.useEmptyResult - Se o resultado de algum campo for vazio e esta propriedade for "false", ele remove todo o campo.
|
|
135
|
+
*
|
|
136
|
+
* @returns {Object} - Retorna um objeto com a estrutura do fieldset.
|
|
137
|
+
*/
|
|
138
|
+
function getNormalizedFields (params) {
|
|
139
|
+
const result = {}
|
|
140
|
+
|
|
141
|
+
for (const key in params.items) {
|
|
142
|
+
const item = params.items[key]
|
|
143
|
+
|
|
144
|
+
// Propriedades que são recebidas em cada fieldset / subset desconstruida
|
|
145
|
+
const {
|
|
146
|
+
label,
|
|
147
|
+
description,
|
|
148
|
+
headerProps,
|
|
149
|
+
column,
|
|
150
|
+
buttonProps,
|
|
151
|
+
fields = [],
|
|
152
|
+
subset = {},
|
|
153
|
+
useSeparator
|
|
154
|
+
} = item
|
|
155
|
+
|
|
156
|
+
// Valida caso tenha um header
|
|
157
|
+
const hasHeader = !!(label || description || Object.keys(headerProps || {}).length)
|
|
158
|
+
|
|
159
|
+
// Valida caso tenha um subset dentro de um fieldset
|
|
160
|
+
const hasSubset = !!Object.keys(subset).length
|
|
161
|
+
|
|
162
|
+
// Estrutura de um fieldset e subset
|
|
163
|
+
const structure = {
|
|
164
|
+
label,
|
|
165
|
+
description,
|
|
166
|
+
column,
|
|
167
|
+
buttonProps,
|
|
168
|
+
headerProps,
|
|
169
|
+
useSeparator,
|
|
170
|
+
fields: {},
|
|
171
|
+
|
|
172
|
+
subset: hasSubset
|
|
173
|
+
? getNormalizedFields({
|
|
174
|
+
items: subset,
|
|
175
|
+
isGrid: params.isGrid,
|
|
176
|
+
fields: params.fields,
|
|
177
|
+
result: params.result,
|
|
178
|
+
useEmptyResult: params.useEmptyResult
|
|
179
|
+
})
|
|
180
|
+
: {},
|
|
181
|
+
|
|
182
|
+
// Propriedades auxiliares para controle na view
|
|
183
|
+
__hasHeader: hasHeader,
|
|
184
|
+
__hasSubset: hasSubset,
|
|
185
|
+
__hasFieldset: true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Pegar os fields com base na key.
|
|
189
|
+
const fieldsByfieldsKeys = filterObject(params.fields, fields)
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Foi adicionado essa lógica para o grid-generator, pois cada field deve ter o "formattedResult", onde o
|
|
193
|
+
* "formattedResult" é basicamente o result formatado com base no tipo do field através do humanize,
|
|
194
|
+
* exemplo: field do tipo "date", virá do back "2025-08-20", e deverá exibir "20/08/2025".
|
|
195
|
+
*/
|
|
196
|
+
if (params.isGrid) {
|
|
197
|
+
// Adicionar "formattedResult" com base no result.
|
|
198
|
+
const formattedFields = getFieldsByResult({
|
|
199
|
+
fields: fieldsByfieldsKeys,
|
|
200
|
+
result: params.result,
|
|
201
|
+
useEmptyResult: params.useEmptyResult
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// Adiciona o fields no structure
|
|
205
|
+
Object.assign(structure.fields, formattedFields)
|
|
206
|
+
} else {
|
|
207
|
+
Object.assign(structure.fields, fieldsByfieldsKeys)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
result[key] = structure
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Para todos campos passados, adiciona o "formattedResult" já formatado com base no result
|
|
218
|
+
*
|
|
219
|
+
* @function
|
|
220
|
+
* @param {Object} params
|
|
221
|
+
* @param {Object} params.fields - Campos
|
|
222
|
+
* @param {Object} params.result - Resultado com base nos fields
|
|
223
|
+
* @param {boolean} params.useEmptyResult - Se o resultado de algum campo for vazio e esta propriedade for "false", ele remove todo o campo.
|
|
224
|
+
*
|
|
225
|
+
* @returns {Object} - Retorna os "fields" com um "formattedResult" pra cada field.
|
|
226
|
+
*/
|
|
227
|
+
function getFieldsByResult ({ fields, result, useEmptyResult }) {
|
|
228
|
+
const hasFields = Object.keys(fields).length
|
|
229
|
+
const hasResult = Object.keys(result).length
|
|
230
|
+
|
|
231
|
+
if (!hasResult || !hasFields) return {}
|
|
232
|
+
|
|
233
|
+
const unformattedResult = { ...result }
|
|
234
|
+
const fieldsByResult = {}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Retorna os "fields" com base na prop "useEmptyResult". se ela for "false",
|
|
238
|
+
* ela remove todo campo com valor vazio.
|
|
239
|
+
*/
|
|
240
|
+
const formattedFields = getFormattedFields({ fields, result, hasResult, useEmptyResult })
|
|
241
|
+
|
|
242
|
+
for (const key in formattedFields) {
|
|
243
|
+
const field = formattedFields[key] || {}
|
|
244
|
+
|
|
245
|
+
if (!field.type) continue
|
|
246
|
+
|
|
247
|
+
// Formata o result com base no "type", exemplo tipo date: "2025-08-20" -> "20/08/2025"
|
|
248
|
+
const humanizedResult = humanize(field, unformattedResult[key])
|
|
249
|
+
|
|
250
|
+
const formattedResult = isEmpty({ value: humanizedResult }) ? props.emptyResultText : humanizedResult
|
|
251
|
+
|
|
252
|
+
fieldsByResult[key] = {
|
|
253
|
+
...field,
|
|
254
|
+
formattedResult
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return fieldsByResult
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @private
|
|
263
|
+
*
|
|
264
|
+
* @function
|
|
265
|
+
* @param {Object} params
|
|
266
|
+
* @param {Object} params.fields - Campos
|
|
267
|
+
* @param {Object} params.result - Resultado com base nos fields
|
|
268
|
+
* @param {boolean} params.hasResult - Casotenha resultado
|
|
269
|
+
* @param {boolean} params.useEmptyResult - Se o resultado de algum campo for vazio e esta propriedade for "false", ele remove todo o campo.
|
|
270
|
+
*
|
|
271
|
+
* @returns {Object} - Retorna os "fields" formatados, caso a propriedade "useEmptyResult" seja "true",
|
|
272
|
+
* retorna todos os "fields", mesmo que não tenha resultado. Caso a propriedade "useEmptyResult" seja "false",
|
|
273
|
+
* retorna apenas os "fields" que possuem resultado.
|
|
274
|
+
*/
|
|
275
|
+
function getFormattedFields ({ fields, result, hasResult, useEmptyResult }) {
|
|
276
|
+
if (useEmptyResult) return fields
|
|
277
|
+
|
|
278
|
+
if (!hasResult) return {}
|
|
279
|
+
|
|
280
|
+
const formattedFields = {}
|
|
281
|
+
|
|
282
|
+
for (const key in fields) {
|
|
283
|
+
const currentResult = result[key]
|
|
284
|
+
|
|
285
|
+
// Verifica se o field tem result, caso tenha, adiciona no "formattedFields"
|
|
286
|
+
const validate = Array.isArray(currentResult)
|
|
287
|
+
? currentResult.length
|
|
288
|
+
: isObject(currentResult) ? Object.keys(currentResult).length : result
|
|
289
|
+
|
|
290
|
+
if (validate) {
|
|
291
|
+
formattedFields[key] = fields[key]
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return formattedFields
|
|
296
|
+
}
|
|
297
|
+
|
|
93
298
|
/**
|
|
94
299
|
* @private
|
|
95
300
|
*/
|
|
@@ -184,6 +389,10 @@ export default function ({ props = {}, isGrid = false }) {
|
|
|
184
389
|
classes,
|
|
185
390
|
|
|
186
391
|
getFieldClass,
|
|
187
|
-
getFieldSetColumnClass
|
|
392
|
+
getFieldSetColumnClass,
|
|
393
|
+
|
|
394
|
+
getHeaderProps,
|
|
395
|
+
getNormalizedFields,
|
|
396
|
+
getFieldsByResult
|
|
188
397
|
}
|
|
189
398
|
}
|