@bildvitta/quasar-ui-asteroid 3.16.0-beta.3 → 3.16.0-beta.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bildvitta/quasar-ui-asteroid",
3
3
  "description": "Asteroid",
4
- "version": "3.16.0-beta.3",
4
+ "version": "3.16.0-beta.5",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -43,6 +43,7 @@
43
43
  "sass": "^1.62.1"
44
44
  },
45
45
  "dependencies": {
46
+ "@bildvitta/composables": "^1.0.0-beta.7",
46
47
  "@bildvitta/store-adapter": "^1.0.0",
47
48
  "autonumeric": "^4.9.0",
48
49
  "axios": "^1.4.0",
@@ -1,13 +1,13 @@
1
1
  <template>
2
- <div class="qas-expansion-item" :class="errorClasses">
3
- <qas-box class="qas-expansion-item__box">
2
+ <div ref="expansionItem" class="qas-expansion-item" :class="errorClasses">
3
+ <component :is="component.is" class="qas-expansion-item__box">
4
4
  <q-expansion-item header-class="text-bold q-mt-sm q-pa-none" :label="props.label">
5
5
  <template #header>
6
6
  <slot name="header">
7
7
  <div class="full-width">
8
8
  <div class="items-center q-col-gutter-sm row">
9
9
  <slot name="label">
10
- <h5 class="col-auto text-h5 text-weight-medium">
10
+ <h5 class="col-auto text-h5">
11
11
  {{ props.label }}
12
12
  </h5>
13
13
  </slot>
@@ -18,17 +18,23 @@
18
18
  </div>
19
19
  </div>
20
20
  </div>
21
+
22
+ <div v-if="hasHeaderBottom" class="q-mt-sm">
23
+ <slot name="header-bottom" />
24
+ </div>
21
25
  </div>
22
26
  </slot>
23
27
  </template>
24
28
 
25
- <q-separator class="q-my-md" />
29
+ <q-separator v-if="!isNestedExpansionItem" class="q-my-md" />
26
30
 
27
31
  <slot name="content">
28
32
  <qas-grid-generator v-if="hasGridGenerator" v-bind="gridGeneratorProps" use-inline />
29
33
  </slot>
34
+
35
+ <q-separator v-if="hasBottomSeparator" class="q-mt-md" />
30
36
  </q-expansion-item>
31
- </qas-box>
37
+ </component>
32
38
 
33
39
  <div v-if="hasError" class="q-pt-sm qas-expansion-item__error-message text-caption text-negative">
34
40
  {{ props.errorMessage }}
@@ -37,7 +43,9 @@
37
43
  </template>
38
44
 
39
45
  <script setup>
40
- import { computed } from 'vue'
46
+ import QasBox from '../box/QasBox.vue'
47
+
48
+ import { computed, provide, inject, onMounted, ref } from 'vue'
41
49
 
42
50
  defineOptions({ name: 'QasExpansionItem' })
43
51
 
@@ -67,17 +75,55 @@ const props = defineProps({
67
75
  }
68
76
  })
69
77
 
70
- const hasError = computed(() => props.error || !!props.errorMessage)
78
+ provide('isExpansionItem', true)
79
+
80
+ // slots
81
+ const slots = defineSlots()
82
+
83
+ // refs
84
+ const expansionItem = ref(null)
85
+ const hasNextSibling = ref(false)
86
+
87
+ onMounted(setHasNextSibling)
88
+
89
+ // constants
90
+ const isNestedExpansionItem = inject('isExpansionItem', false)
91
+ const component = {
92
+ is: isNestedExpansionItem ? 'div' : QasBox
93
+ }
71
94
 
95
+ // computed
96
+ const hasError = computed(() => props.error || !!props.errorMessage)
72
97
  const errorClasses = computed(() => ({ 'qas-expansion-item--error': hasError.value }))
73
98
 
74
99
  const hasGridGenerator = computed(() => !!Object.keys(props.gridGeneratorProps).length)
100
+ const hasBottomSeparator = computed(() => isNestedExpansionItem && hasNextSibling.value)
101
+ const hasHeaderBottom = computed(() => !!slots['header-bottom'])
102
+
103
+ // functions
104
+
105
+ /**
106
+ * Caso o componente esteja dentro de um QasExpansionItem, verifica se existe um próximo irmão
107
+ * para adicionar um separador.
108
+ */
109
+ function setHasNextSibling (value) {
110
+ if (!isNestedExpansionItem) return
111
+
112
+ const hasTextContentSibling = !!expansionItem.value.nextSibling.textContent?.trim?.()
113
+ const hasElementSibling = !!expansionItem.value.nextElementSibling
114
+
115
+ hasNextSibling.value = hasElementSibling || hasTextContentSibling
116
+ }
75
117
  </script>
76
118
 
77
119
  <style lang="scss">
78
120
  .qas-expansion-item {
79
121
  $root: &;
80
122
 
123
+ & + & {
124
+ margin-top: var(--qas-spacing-lg);
125
+ }
126
+
81
127
  &--error {
82
128
  #{$root}__box {
83
129
  border: 2px solid $negative;
@@ -28,6 +28,9 @@ slots:
28
28
  header:
29
29
  desc: 'Slot para substituir o conteúdo do header'
30
30
 
31
+ header-bottom:
32
+ desc: 'Slot para acessar o conteúdo que fica abaixo do header.'
33
+
31
34
  label:
32
35
  desc: 'Slot para substituir o conteúdo da label do header.'
33
36
 
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div :class="mx_componentClass">
3
- <header v-if="mx_hasHeaderSlot">
2
+ <div :class="componentClass">
3
+ <header v-if="hasHeaderSlot">
4
4
  <slot name="header" />
5
5
  </header>
6
6
 
@@ -8,117 +8,160 @@
8
8
  <slot />
9
9
  </template>
10
10
 
11
- <qas-empty-result-text v-else-if="!mx_isFetching" class="q-my-xl" />
11
+ <qas-empty-result-text v-else-if="!viewState.fetching" class="q-my-xl" />
12
12
 
13
- <footer v-if="mx_hasFooterSlot">
13
+ <footer v-if="hasFooterSlot">
14
14
  <slot name="footer" />
15
15
  </footer>
16
16
 
17
- <q-inner-loading :showing="mx_isFetching">
17
+ <q-inner-loading :showing="viewState.fetching">
18
18
  <q-spinner color="grey" size="3em" />
19
19
  </q-inner-loading>
20
20
  </div>
21
21
  </template>
22
22
 
23
- <script>
24
- import viewMixin from '../../mixins/view'
23
+ <script setup>
24
+ import useView, { baseProps, baseEmits } from '../../composables/private/use-view'
25
25
 
26
- import { markRaw } from 'vue'
27
- import { getGetter, getAction } from '@bildvitta/store-adapter'
28
26
  import debug from 'debug'
27
+ import { decamelize } from 'humps'
28
+ import { markRaw, computed, watch, inject } from 'vue'
29
+ import { useRoute } from 'vue-router'
29
30
 
30
31
  const log = debug('asteroid-ui:qas-single-view')
31
32
 
32
- export default {
33
- name: 'QasSingleView',
33
+ defineOptions({ name: 'QasSingleView' })
34
34
 
35
- mixins: [viewMixin],
35
+ const props = defineProps({
36
+ ...baseProps,
36
37
 
37
- props: {
38
- customId: {
39
- default: '',
40
- type: [Number, String]
41
- },
42
-
43
- result: {
44
- default: () => ({}),
45
- type: Object
46
- }
38
+ customId: {
39
+ default: '',
40
+ type: [Number, String]
47
41
  },
48
42
 
49
- emits: [
50
- 'update:result',
51
- 'fetch-success',
52
- 'fetch-error'
53
- ],
43
+ result: {
44
+ default: () => ({}),
45
+ type: Object
46
+ }
47
+ })
54
48
 
55
- computed: {
56
- hasResult () {
57
- return !!this.resultModel
58
- },
49
+ const emit = defineEmits([
50
+ ...baseEmits,
59
51
 
60
- id () {
61
- return this.customId || this.$route.params.id
62
- },
52
+ 'update:result',
53
+ 'fetch-success',
54
+ 'fetch-error'
55
+ ])
63
56
 
64
- resultModel () {
65
- return getGetter.call(this, { entity: this.entity, key: 'byId' })(this.id) || {}
66
- }
67
- },
57
+ const slots = defineSlots()
68
58
 
69
- watch: {
70
- $route (to, from) {
71
- if (to.name === from.name) {
72
- this.mx_fetchHandler({ id: this.id, url: this.url }, this.fetchSingle)
73
- }
74
- },
59
+ // globals
60
+ const axios = inject('axios')
61
+ const qas = inject('qas')
75
62
 
76
- resultModel (value) {
77
- this.$emit('update:result', markRaw({ ...value }))
78
- }
79
- },
63
+ // composables
64
+ const route = useRoute()
80
65
 
81
- created () {
82
- this.mx_fetchHandler({ id: this.id, url: this.url }, this.fetchSingle)
83
- },
66
+ const {
67
+ // state
68
+ viewState,
69
+
70
+ // computed
71
+ componentClass,
72
+ hasHeaderSlot,
73
+ hasFooterSlot,
74
+
75
+ // functions
76
+ errorHandler,
77
+ fetchHandler,
78
+ setErrors,
79
+ setFields,
80
+ setMetadata,
81
+ setResult,
82
+ updateModels
83
+ } = useView({ emit, props, slots, mode: 'single' })
84
+
85
+ // computed
86
+ const id = computed(() => props.customId || route.params.id)
87
+
88
+ const resultModel = computed(() => {
89
+ if (props.useStore) return qas.getGetter({ entity: props.entity, key: 'byId' })(id.value) || {}
90
+
91
+ return viewState.value.result || {}
92
+ })
93
+
94
+ const hasResult = computed(() => !!resultModel.value)
95
+
96
+ // watch
97
+ watch(() => route, (to, from) => {
98
+ if (to.name === from.name) {
99
+ fetchHandler({ id: id.value, url: props.url }, fetchSingle)
100
+ }
101
+ })
102
+
103
+ watch(() => resultModel.value, value => emit('update:result', markRaw({ ...value })))
104
+
105
+ // created
106
+ fetchHandler({ id: id.value, url: props.url }, fetchSingle)
107
+
108
+ // functions
109
+ async function fetchSingle (externalPayload = {}) {
110
+ viewState.value.fetching = true
111
+
112
+ try {
113
+ const payload = { id: id.value, url: props.url, ...externalPayload }
114
+
115
+ const response = await getAction(payload)
84
116
 
85
- methods: {
86
- async fetchSingle (externalPayload = {}) {
87
- this.mx_isFetching = true
117
+ const { errors, fields, metadata, result } = response.data
88
118
 
89
- try {
90
- const payload = { id: this.id, url: this.url, ...externalPayload }
119
+ setErrors(errors)
120
+ setFields(fields)
121
+ setMetadata(metadata)
91
122
 
92
- const response = await getAction.call(this, {
93
- entity: this.entity,
94
- key: 'fetchSingle',
95
- payload
96
- })
123
+ /**
124
+ * result só precisa ser adicionado ao estado se não estiver usando store,
125
+ * pois com a store é usado o getter.
126
+ */
127
+ !props.useStore && setResult(result)
97
128
 
98
- const { errors, fields, metadata } = response.data
129
+ updateModels({
130
+ errors: viewState.value.errors,
131
+ fields: viewState.value.fields,
132
+ metadata: viewState.value.metadata,
99
133
 
100
- this.mx_setErrors(errors)
101
- this.mx_setFields(fields)
102
- this.mx_setMetadata(metadata)
134
+ ...(!props.useStore && { result: viewState.value.result })
135
+ })
103
136
 
104
- this.mx_updateModels({
105
- errors: this.mx_errors,
106
- fields: this.mx_fields,
107
- metadata: this.mx_metadata
108
- })
137
+ emit('fetch-success', response)
109
138
 
110
- this.$emit('fetch-success', response)
139
+ log(`[${props.entity}]:fetchSingle:success`, response)
140
+ } catch (error) {
141
+ errorHandler(error)
142
+ emit('fetch-error', error)
111
143
 
112
- log(`[${this.entity}]:fetchSingle:success`, response)
113
- } catch (error) {
114
- this.mx_fetchError(error)
115
- this.$emit('fetch-error', error)
144
+ log(`[${props.entity}]:fetchSingle:error`, error)
145
+ } finally {
146
+ viewState.value.fetching = false
147
+ }
148
+ }
116
149
 
117
- log(`[${this.entity}]:fetchSingle:error`, error)
118
- } finally {
119
- this.mx_isFetching = false
120
- }
121
- }
150
+ function getAction (payload) {
151
+ if (props.useStore) {
152
+ return qas.getAction({
153
+ entity: props.entity,
154
+ key: 'fetchSingle',
155
+ payload
156
+ })
122
157
  }
158
+
159
+ const { id: unusedID, url: unusedURL, ...externalPayload } = payload
160
+
161
+ const decamelizedEntity = decamelize(props.entity, { separator: '-' })
162
+
163
+ const url = props.url || `${decamelizedEntity}/${id.value}`
164
+
165
+ return axios.get(url, { ...externalPayload })
123
166
  }
124
167
  </script>
@@ -62,6 +62,11 @@ props:
62
62
  default: true
63
63
  type: Boolean
64
64
 
65
+ use-route:
66
+ desc: Controla se o componente usará internamente uma store do vuex/pinia ou se vai ser utilizado o axios diretamente.
67
+ default: true
68
+ type: Boolean
69
+
65
70
  slots:
66
71
  default:
67
72
  desc: 'Slot para ter o conteúdo principal (dentro do main).'
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <div class="qas-toggle-visibility">
3
+ <div class="items-center no-wrap row" :style>
4
+ <div class="ellipsis qas-toggle-visibility__content">
5
+ <div
6
+ v-if="isVisible"
7
+ class="ellipsis full-width"
8
+ :title="props.text"
9
+ >
10
+ <slot>
11
+ {{ props.text }}
12
+ </slot>
13
+ </div>
14
+
15
+ <q-separator
16
+ v-else
17
+ size="4px"
18
+ />
19
+ </div>
20
+
21
+ <qas-btn
22
+ class="q-ml-sm qas-toggle-visibility__button"
23
+ :icon
24
+ @click="toggleVisibility"
25
+ />
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup>
31
+ import { useToggleVisibility } from '../../composables/private'
32
+
33
+ import { uid } from 'quasar'
34
+ import { computed } from 'vue'
35
+
36
+ defineOptions({ name: 'QasToggleVisibility' })
37
+
38
+ const props = defineProps({
39
+ group: {
40
+ type: String,
41
+ default: ''
42
+ },
43
+
44
+ text: {
45
+ type: String,
46
+ default: ''
47
+ },
48
+
49
+ uuid: {
50
+ type: String,
51
+ default: ''
52
+ },
53
+
54
+ width: {
55
+ type: String,
56
+ default: '140px'
57
+ }
58
+ })
59
+
60
+ const {
61
+ isVisible,
62
+ toggleVisibility
63
+ } = useToggleVisibility({ group: props.group, uuid: props.uuid || uid() })
64
+
65
+ const icon = computed(() => isVisible.value ? 'sym_r_visibility' : 'sym_r_visibility_off')
66
+ const style = computed(() => ({ width: props.width }))
67
+ </script>
68
+
69
+ <style lang="scss">
70
+ .qas-toggle-visibility {
71
+ &__content {
72
+ flex-grow: 1;
73
+ }
74
+
75
+ &__button {
76
+ flex-grow: 0;
77
+ }
78
+ }
79
+ </style>
@@ -0,0 +1,30 @@
1
+ type: component
2
+
3
+ meta:
4
+ desc: Componente para ocultar e exibir conteúdo, utilizado para dados sensíveis.
5
+
6
+ props:
7
+ group:
8
+ desc: Propriedade para criar grupos de separação.
9
+ default: ''
10
+ type: String
11
+ examples: ["name", "document", "users"]
12
+
13
+ text:
14
+ desc: Texto a ser oculto/exibido.
15
+ default: ''
16
+ type: String
17
+
18
+ uuid:
19
+ desc: É criado um uuid random para cada componente, para que possa ser feito o controle interno, porém é possível sobrescrever por esta propriedade.
20
+ default: ''
21
+ type: String
22
+
23
+ width:
24
+ desc: Tamanho do conteúdo a ser oculto/exibido.
25
+ default: '140px'
26
+ type: String
27
+
28
+ slots:
29
+ default:
30
+ desc: Slot para inserir conteúdo a ser oculto/exibido.
@@ -0,0 +1,3 @@
1
+ export { default as useView } from './use-view'
2
+ export { default as useGenerator } from './use-generator'
3
+ export { default as useToggleVisibility } from './use-toggle-visibility'
@@ -0,0 +1,48 @@
1
+ import { ref, computed } from 'vue'
2
+
3
+ const visibleState = ref({})
4
+
5
+ /**
6
+ * @param {{
7
+ * group?: string,
8
+ * uuid: string
9
+ * }}
10
+ */
11
+ export default function useToggleVisibility ({ group, uuid }) {
12
+ group = group || 'default'
13
+
14
+ visibleState.value[group] = {
15
+ ...visibleState.value[group],
16
+ [uuid]: false
17
+ }
18
+
19
+ const isVisible = computed(() => visibleState.value[group]?.[uuid] || false)
20
+
21
+ function toggleVisibility () {
22
+ const item = visibleState.value[group]
23
+ const lastVisibleItemKey = Object.keys(item).find(key => !!item[key])
24
+
25
+ /**
26
+ * cenário onde esta visível e clicou no mesmo item
27
+ */
28
+ if (visibleState.value[group][uuid]) {
29
+ visibleState.value[group][uuid] = false
30
+ return
31
+ }
32
+
33
+ /**
34
+ * cenário onde tem um item visível e clicou em outro item
35
+ */
36
+ if (lastVisibleItemKey) {
37
+ visibleState.value[group][lastVisibleItemKey] = false
38
+ }
39
+
40
+ visibleState.value[group][uuid] = true
41
+ }
42
+
43
+ return {
44
+ isVisible,
45
+
46
+ toggleVisibility
47
+ }
48
+ }
@@ -0,0 +1,180 @@
1
+ import { NotifyError } from '../../plugins'
2
+ import { camelizeFieldsName } from '../../helpers'
3
+
4
+ import { useView as useViewComposable } from '@bildvitta/composables'
5
+ import { ref, computed, watch, markRaw } from 'vue'
6
+ import { useRouter } from 'vue-router'
7
+
8
+ export const baseProps = {
9
+ beforeFetch: {
10
+ default: null,
11
+ type: Function
12
+ },
13
+
14
+ errors: {
15
+ default: () => ({}),
16
+ type: Object
17
+ },
18
+
19
+ entity: {
20
+ type: String,
21
+ required: ''
22
+ },
23
+
24
+ fetching: {
25
+ type: Boolean
26
+ },
27
+
28
+ fields: {
29
+ default: () => ({}),
30
+ type: Object
31
+ },
32
+
33
+ metadata: {
34
+ default: () => ({}),
35
+ type: Object
36
+ },
37
+
38
+ url: {
39
+ default: '',
40
+ type: String
41
+ },
42
+
43
+ useBoundary: {
44
+ default: true,
45
+ type: Boolean
46
+ },
47
+
48
+ useStore: {
49
+ default: true,
50
+ type: Boolean
51
+ }
52
+ }
53
+
54
+ export const baseEmits = [
55
+ 'update:fields',
56
+ 'update:errors',
57
+ 'update:metadata',
58
+ 'update:fetching'
59
+ ]
60
+
61
+ /**
62
+ * @param {{
63
+ * mode: import('@bildvitta/composables').ViewModeTypes,
64
+ * props: baseProps,
65
+ * emit: (baseEmits: string) => void,
66
+ * slots: import('vue').Slots
67
+ * }} config
68
+ */
69
+ export default function useView (config) {
70
+ const {
71
+ emit,
72
+ mode,
73
+ slots,
74
+ props
75
+ } = config
76
+
77
+ // composables
78
+ const router = useRouter()
79
+ const { viewState } = useViewComposable({ mode })
80
+
81
+ // refs
82
+ const cancelBeforeFetch = ref(false)
83
+
84
+ // constants
85
+ const fetchErrorMessage = 'Ops… Não conseguimos acessar as informações. Por favor, tente novamente em alguns minutos.'
86
+
87
+ // computed
88
+ const componentClass = computed(() => props.useBoundary && 'container spaced')
89
+ const hasFooterSlot = computed(() => !!slots.footer)
90
+ const hasHeaderSlot = computed(() => !!slots.header)
91
+
92
+ // watch
93
+ watch(() => viewState.value.fetching, value => emit('update:fetching', value))
94
+
95
+ // functions
96
+ function errorHandler (error) {
97
+ const { response } = error
98
+
99
+ const status = response?.status
100
+
101
+ const redirect = status >= 500
102
+ ? 'ServerError'
103
+ : ({ 403: 'Forbidden', 404: 'NotFound' })[status]
104
+
105
+ /**
106
+ * caso exista um desses status será redirecionado sem aparecer o "notify"
107
+ */
108
+ if (redirect) {
109
+ router.replace({ name: redirect })
110
+
111
+ return
112
+ }
113
+
114
+ NotifyError(fetchErrorMessage)
115
+ }
116
+
117
+ function setErrors (errors = {}) {
118
+ viewState.value.errors = markRaw(errors)
119
+ }
120
+
121
+ function setFields (fields = {}) {
122
+ const camelizedFields = camelizeFieldsName(fields)
123
+
124
+ viewState.value.fields = markRaw(camelizedFields)
125
+ }
126
+
127
+ function setMetadata (metadata = {}) {
128
+ viewState.value.metadata = markRaw(metadata)
129
+ }
130
+
131
+ function setResult (result = {}) {
132
+ viewState.value.result = result
133
+ }
134
+
135
+ function updateModels (models) {
136
+ for (const key in models) {
137
+ if (!models[key]) continue
138
+
139
+ emit(`update:${key}`, models[key])
140
+ }
141
+ }
142
+
143
+ function fetchHandler (payload, resolve) {
144
+ const hasBeforeFetch = typeof props.beforeFetch === 'function'
145
+
146
+ if (hasBeforeFetch && !cancelBeforeFetch.value) {
147
+ return props.beforeFetch({
148
+ payload,
149
+ resolve: payload => resolve(payload),
150
+ done: () => {
151
+ cancelBeforeFetch.value = true
152
+ }
153
+ })
154
+ }
155
+
156
+ resolve()
157
+ }
158
+
159
+ return {
160
+ // state
161
+ viewState,
162
+
163
+ // constants
164
+ fetchErrorMessage,
165
+
166
+ // computed
167
+ componentClass,
168
+ hasFooterSlot,
169
+ hasHeaderSlot,
170
+
171
+ // functions
172
+ errorHandler,
173
+ fetchHandler,
174
+ setErrors,
175
+ setFields,
176
+ setResult,
177
+ setMetadata,
178
+ updateModels
179
+ }
180
+ }
package/src/vue-plugin.js CHANGED
@@ -64,6 +64,7 @@ import QasTableGenerator from './components/table-generator/QasTableGenerator.vu
64
64
  import QasTabsGenerator from './components/tabs-generator/QasTabsGenerator.vue'
65
65
  import QasTextTruncate from './components/text-truncate/QasTextTruncate.vue'
66
66
  import QasTimeline from './components/timeline/QasTimeline.vue'
67
+ import QasToggleVisibility from './components/toggle-visibility/QasToggleVisibility.vue'
67
68
  import QasTransfer from './components/transfer/QasTransfer.vue'
68
69
  import QasTreeGenerator from './components/tree-generator/QasTreeGenerator.vue'
69
70
  import QasUploader from './components/uploader/QasUploader.vue'
@@ -72,7 +73,7 @@ import QasWhatsappLink from './components/whatsapp-link/QasWhatsappLink.vue'
72
73
 
73
74
  import { Notify, Loading, Quasar, Dialog as QuasarDialog } from 'quasar'
74
75
 
75
- import { getAction } from '@bildvitta/store-adapter'
76
+ import { getAction, getGetter } from '@bildvitta/store-adapter'
76
77
 
77
78
  // Plugins
78
79
  import {
@@ -157,6 +158,7 @@ async function install (app) {
157
158
  app.component('QasTabsGenerator', QasTabsGenerator)
158
159
  app.component('QasTextTruncate', QasTextTruncate)
159
160
  app.component('QasTimeline', QasTimeline)
161
+ app.component('QasToggleVisibility', QasToggleVisibility)
160
162
  app.component('QasTransfer', QasTransfer)
161
163
  app.component('QasTreeGenerator', QasTreeGenerator)
162
164
  app.component('QasUploader', QasUploader)
@@ -176,7 +178,8 @@ async function install (app) {
176
178
  app.provide('qas', {
177
179
  delete: params => Delete.call(app.config.globalProperties, params),
178
180
 
179
- getAction: params => getAction.call(app.config.globalProperties, params)
181
+ getAction: params => getAction.call(app.config.globalProperties, params),
182
+ getGetter: params => getGetter.call(app.config.globalProperties, params)
180
183
  })
181
184
 
182
185
  app.directive(Test.name, Test)
@@ -251,6 +254,7 @@ export {
251
254
  QasTabsGenerator,
252
255
  QasTextTruncate,
253
256
  QasTimeline,
257
+ QasToggleVisibility,
254
258
  QasTransfer,
255
259
  QasTreeGenerator,
256
260
  QasUploader,