@bildvitta/quasar-ui-asteroid 3.20.0-beta.16 → 3.20.0-beta.18-alpha.0

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.20.0-beta.16",
4
+ "version": "3.20.0-beta.18-alpha.0",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "./src/asteroid.js",
@@ -388,8 +388,6 @@ const hasColumnsLength = computed(() => !!Object.keys(columnsResultsModel.value)
388
388
 
389
389
  const containerStyle = computed(() => `width: ${props.columnWidth};`)
390
390
 
391
- const hasConfirmDialogProps = computed(() => !!Object.keys(props.confirmDialogProps).length)
392
-
393
391
  const defaultConfirmDialogProps = computed(() => {
394
392
  const defaultProps = {
395
393
  ok: {
@@ -1124,7 +1122,7 @@ function onDropCard (event) {
1124
1122
  return
1125
1123
  }
1126
1124
 
1127
- hasConfirmDialogProps.value
1125
+ props.useConfirmDialog
1128
1126
  ? openConfirmDialog()
1129
1127
  : confirmDrop(event)
1130
1128
  }
@@ -1143,7 +1141,7 @@ function closeConfirmDialog () {
1143
1141
  function cancelDrop (event) {
1144
1142
  revertDomDrag(event)
1145
1143
 
1146
- if (hasConfirmDialogProps.value) closeConfirmDialog()
1144
+ if (props.useConfirmDialog) closeConfirmDialog()
1147
1145
 
1148
1146
  stopDragging()
1149
1147
  }
@@ -115,6 +115,10 @@ props:
115
115
  default: "{ animation: 500, swapThreshold: 1, delay: 50, delayOnTouchOnly: true, emptyInsertThreshold: 0 }"
116
116
  debugger: true
117
117
 
118
+ use-confirm-dialog:
119
+ desc: Controla se o dialog de confirmação para o drag and drop será utilizado. Caso seja "true", o dialog é aberto utilizando as props passadas em "confirm-dialog-props" e as ações de confirmar e cancelar são feitas por padrão no componente. Caso seja "false", o drag and drop é realizado sem confirmação.
120
+ type: Boolean
121
+
118
122
  use-drag-and-drop-x:
119
123
  desc: Controla se irá ter drag and drop no eixo X (entre colunas).
120
124
  type: Boolean
@@ -1,37 +1,29 @@
1
1
  <template>
2
- <q-dialog ref="dialogRef" class="qas-dialog" :class="classes" data-cy="dialog" v-bind="dialogProps" :persistent="props.persistent" @update:model-value="updateModelValue">
3
- <div class="bg-white q-pa-md" :style="style">
4
- <header v-if="hasHeader" class="q-mb-md">
5
- <slot name="header">
6
- <div class="items-center justify-between row">
7
- <qas-label data-cy="dialog-title" :label="props.card.title" margin="none">
8
- <slot name="title" />
9
- </qas-label>
10
-
11
- <qas-btn v-if="isInfoDialog" v-close-popup color="grey-10" data-cy="dialog-close-btn" icon="sym_r_close" variant="tertiary" />
12
- </div>
13
- </slot>
2
+ <q-dialog ref="dialogRef" v-model="model" class="qas-dialog" :class="classes" data-cy="dialog" v-bind="dialogProps">
3
+ <div class="bg-white full-width q-pa-md qas-dialog__container" :style="containerStyles">
4
+ <header v-if="hasHeaderSlot" class="q-mb-md">
5
+ <slot name="header" />
14
6
  </header>
15
7
 
8
+ <qas-header v-else-if="props.title" v-bind="headerProps">
9
+ <template v-if="hasTitleSlot" #label>
10
+ <qas-label>
11
+ <slot name="title" />
12
+ </qas-label>
13
+ </template>
14
+ </qas-header>
15
+
16
16
  <section class="relative-position text-body1 text-grey-8">
17
17
  <component :is="mainComponent.is" ref="form" v-bind="mainComponent.props">
18
18
  <slot name="description">
19
- <component :is="descriptionComponent" data-cy="dialog-description">{{ props.card.description }}</component>
19
+ <div v-if="props.useHtmlDescription" data-cy="dialog-description" v-html="props.description" />
20
+
21
+ <component :is="descriptionComponent" v-else data-cy="dialog-description">
22
+ {{ props.description }}
23
+ </component>
20
24
  </slot>
21
25
 
22
- <div v-if="!isInfoDialog">
23
- <slot name="actions">
24
- <qas-actions v-bind="defaultActionsProps">
25
- <template v-if="hasOk" #primary>
26
- <qas-btn v-close-popup="!props.useForm" class="full-width" data-cy="dialog-ok-btn" size="lg" variant="primary" v-bind="defaultOk" />
27
- </template>
28
-
29
- <template v-if="hasCancel" #secondary>
30
- <qas-btn v-close-popup class="full-width" data-cy="dialog-cancel-btn" size="lg" v-bind="defaultCancel" variant="secondary" />
31
- </template>
32
- </qas-actions>
33
- </slot>
34
- </div>
26
+ <qas-actions v-if="hasActions" class="qas-dialog__actions" v-bind="defaultActionsProps" />
35
27
  </component>
36
28
  </section>
37
29
  </div>
@@ -39,24 +31,19 @@
39
31
  </template>
40
32
 
41
33
  <script setup>
42
- import QasActions from '../actions/QasActions.vue'
43
- import QasBtn from '../btn/QasBtn.vue'
44
34
  import QasLabel from '../label/QasLabel.vue'
35
+ import QasActions from '../actions/QasActions.vue'
36
+ import QasHeader from '../header/QasHeader.vue'
45
37
 
46
- import useCancel from './composables/use-cancel'
47
- import useDynamicComponents from './composables/use-dynamic-components'
48
- import useOk from './composables/use-ok'
49
- import { useScreen } from '../../composables'
50
-
51
- import { computed, ref, useAttrs, useSlots, provide } from 'vue'
52
- import { useDialogPluginComponent } from 'quasar'
38
+ import { computed, ref, useAttrs, provide, useSlots, inject } from 'vue'
39
+ import { useDialogPluginComponent, QForm } from 'quasar'
53
40
 
54
41
  defineOptions({ name: 'QasDialog' })
55
42
 
56
43
  const props = defineProps({
57
- actionsProps: {
58
- default: () => ({}),
59
- type: Object
44
+ badges: {
45
+ default: () => [],
46
+ type: [Array, Object]
60
47
  },
61
48
 
62
49
  cancel: {
@@ -64,19 +51,13 @@ const props = defineProps({
64
51
  type: [Object, Boolean]
65
52
  },
66
53
 
67
- card: {
68
- default: () => ({}),
69
- type: Object
70
- },
71
-
72
- maxWidth: {
73
- default: '',
74
- type: String
54
+ description: {
55
+ type: [String, Object],
56
+ default: ''
75
57
  },
76
58
 
77
- minWidth: {
78
- default: '',
79
- type: String
59
+ disableCloseButton: {
60
+ type: Boolean
80
61
  },
81
62
 
82
63
  ok: {
@@ -84,16 +65,23 @@ const props = defineProps({
84
65
  type: [Object, Boolean]
85
66
  },
86
67
 
87
- persistent: {
88
- default: true,
89
- type: Boolean
68
+ size: {
69
+ type: String,
70
+ default: 'sm',
71
+ validator: value => ['sm', 'md', 'lg', 'xl'].includes(value)
90
72
  },
91
73
 
92
- useForm: {
93
- type: Boolean
74
+ title: {
75
+ type: String,
76
+ required: true
94
77
  },
95
78
 
96
- modelValue: {
79
+ tertiary: {
80
+ default: () => ({}),
81
+ type: Object
82
+ },
83
+
84
+ useForm: {
97
85
  type: Boolean
98
86
  },
99
87
 
@@ -101,7 +89,22 @@ const props = defineProps({
101
89
  type: Boolean
102
90
  },
103
91
 
104
- useFullMaxWidth: {
92
+ useAutoCloseOnCancel: {
93
+ type: Boolean,
94
+ default: true
95
+ },
96
+
97
+ useAutoCloseOnOk: {
98
+ type: Boolean,
99
+ default: true
100
+ },
101
+
102
+ useCloseButton: {
103
+ type: Boolean,
104
+ default: true
105
+ },
106
+
107
+ useHtmlDescription: {
105
108
  type: Boolean
106
109
  },
107
110
 
@@ -110,10 +113,8 @@ const props = defineProps({
110
113
  }
111
114
  })
112
115
 
116
+ // emtis
113
117
  const emit = defineEmits([
114
- // model
115
- 'update:modelValue',
116
-
117
118
  // actions
118
119
  'cancel',
119
120
  'ok',
@@ -123,23 +124,32 @@ const emit = defineEmits([
123
124
  ...useDialogPluginComponent.emits
124
125
  ])
125
126
 
126
- provide('isDialog', true)
127
+ // models
128
+ const model = defineModel({ type: Boolean })
127
129
 
128
- const attrs = useAttrs()
129
- const screen = useScreen()
130
- const slots = useSlots()
130
+ // globals
131
+ provide('isDialog', true)
132
+ provide('btnPropsDefaults', { size: 'md' }) // define o tamanho padrão para os botões dentro do dialog
131
133
 
132
- // usado para o plugin
133
- const { dialogRef, onDialogHide } = useDialogPluginComponent()
134
+ // necessário para pegar as props default do dialog quando usado no QasDrawer
135
+ const defaultProps = inject('dialogDefaultProps', null)
134
136
 
135
- // QForm template
137
+ // refs
136
138
  const form = ref(null)
137
139
 
138
- const composablesParams = { emit, form, props, screen, slots }
140
+ // composables
141
+ const slots = useSlots()
142
+ const attrs = useAttrs()
143
+ const { dialogRef, onDialogHide } = useDialogPluginComponent() // usado para o plugin
144
+ const { defaultCancel, hasCancel } = useCancel()
145
+ const { defaultOk, hasOk, onOk } = useOk()
146
+ const { descriptionComponent, mainComponent } = useDynamicComponents()
139
147
 
140
- const { defaultCancel, hasCancel } = useCancel(composablesParams)
141
- const { defaultOk, hasOk, onOk } = useOk(composablesParams)
142
- const { descriptionComponent, mainComponent } = useDynamicComponents({ ...composablesParams, onOk, hasOk })
148
+ // computeds
149
+ /**
150
+ * Necessária logica via provide para controle interno no componente QasDrawer.
151
+ */
152
+ const hasDefaultMaxWidth = computed(() => !!defaultProps?.value.maxWidth)
143
153
 
144
154
  /**
145
155
  * Classes criadas para serem utilizadas quando usado com a prop "position", pois
@@ -150,60 +160,241 @@ const classes = computed(() => {
150
160
  const isRightPosition = attrs.position === 'right'
151
161
  const isLeftPosition = attrs.position === 'left'
152
162
 
163
+ const sizes = {
164
+ sm: 'qas-dialog--sm', // 450px
165
+ md: 'qas-dialog--md', // 550px
166
+ lg: 'qas-dialog--lg', // 800px
167
+ xl: 'qas-dialog--xl' // 1100px
168
+ }
169
+
170
+ return [
171
+ {
172
+ [sizes[props.size]]: !hasDefaultMaxWidth.value,
173
+ 'qas-dialog--right': isRightPosition,
174
+ 'qas-dialog--left': isLeftPosition
175
+ }
176
+ ]
177
+ })
178
+
179
+ /**
180
+ * TODO-ISSUE: Manter dessa forma até issue #1431 ser resolvida.
181
+ */
182
+ const containerStyles = computed(() => {
183
+ if (!hasDefaultMaxWidth.value) return
184
+
153
185
  return {
154
- 'qas-dialog--right': isRightPosition,
155
- 'qas-dialog--left': isLeftPosition
186
+ maxWidth: defaultProps?.value?.maxWidth
156
187
  }
157
188
  })
158
189
 
159
190
  const dialogProps = computed(() => {
191
+ const { title, ...attributes } = attrs
192
+
160
193
  return {
161
194
  ...(!props.usePlugin && { modelValue: props.modelValue }),
162
- ...attrs,
195
+ ...attributes,
196
+ persistent: defaultProps?.value?.persistent ?? hasActions.value,
163
197
 
164
198
  onHide: onDialogHide
165
199
  }
166
200
  })
167
201
 
168
- const style = computed(() => {
202
+ const hasActions = computed(() => hasOk.value || hasCancel.value || !!Object.keys(props.tertiary).length)
203
+
204
+ const headerProps = computed(() => {
205
+ return {
206
+ labelProps: {
207
+ label: props.title
208
+ },
209
+
210
+ badges: props.badges,
211
+
212
+ buttonProps: {
213
+ ...(props.useCloseButton && {
214
+ color: 'grey-10',
215
+ disable: props.disableCloseButton,
216
+ icon: 'sym_r_close',
217
+ variant: 'tertiary',
218
+ 'data-cy': 'dialog-close-btn',
219
+ onClick: () => updateModelValue(false)
220
+ })
221
+ }
222
+ }
223
+ })
224
+
225
+ const defaultActionsProps = computed(() => {
169
226
  return {
170
- ...(props.useFullMaxWidth && { width: '100%' }),
227
+ ...(hasOk.value && { primaryButtonProps: defaultOk.value }),
228
+ ...(hasCancel.value && { secondaryButtonProps: defaultCancel.value }),
171
229
 
172
- maxWidth: props.maxWidth || '470px',
173
- minWidth: props.minWidth || (screen.isSmall ? '' : '366px')
230
+ tertiaryButtonProps: {
231
+ ...props.tertiary,
232
+ 'data-cy': 'dialog-tertiary-btn'
233
+ },
234
+
235
+ spacingTop: 'lg',
236
+ gutter: 'md'
174
237
  }
175
238
  })
176
239
 
177
- const hasHeader = computed(() => !!slots.header || props.card.title)
178
- const isInfoDialog = computed(() => !hasOk.value && !hasCancel.value)
240
+ const hasHeaderSlot = computed(() => !!slots.header)
241
+ const hasTitleSlot = computed(() => !!slots.title)
179
242
 
180
- const defaultActionsProps = computed(() => {
181
- const { useFullWidth, useEqualWidth } = props.actionsProps
243
+ // functions
244
+ function updateModelValue (value) {
245
+ model.value = value
246
+ }
182
247
 
183
- if (useFullWidth || useEqualWidth) return props.actionsProps
248
+ // composable definitions
249
+ function useOk () {
250
+ // computeds
251
+ const defaultOk = computed(() => {
252
+ const { onClick, ...attrs } = props.ok
184
253
 
185
- const hasAllActions = hasOk.value && hasCancel.value
186
- const hasSingleAction = (hasOk.value && !hasCancel.value) || (!hasOk.value && hasCancel.value)
254
+ return {
255
+ label: 'Ok',
256
+ type: (props.ok?.type || props.useForm) ? 'submit' : 'button',
257
+ 'data-cy': 'dialog-ok-btn',
258
+
259
+ ...attrs,
260
+
261
+ // adiciona somente se não estiver usando useForm pois o controle ficará no submit.
262
+ ...(!props.useForm && { onClick: onOk })
263
+ }
264
+ })
265
+
266
+ const hasOk = computed(() => typeof props.ok === 'boolean' ? props.ok : !!Object.keys(props.ok))
267
+
268
+ // functions
269
+ function onOk () {
270
+ if (!props.useForm && props.useAutoCloseOnOk) {
271
+ updateModelValue(false)
272
+ }
273
+
274
+ props.ok.onClick?.()
275
+
276
+ emit('ok')
277
+ }
187
278
 
188
279
  return {
189
- useFullWidth: hasSingleAction,
190
- useEqualWidth: hasAllActions,
280
+ defaultOk,
281
+ hasOk,
282
+ onOk
283
+ }
284
+ }
285
+
286
+ function useCancel () {
287
+ // computeds
288
+ const defaultCancel = computed(() => {
289
+ return {
290
+ label: 'Cancelar',
291
+ 'data-cy': 'dialog-cancel-btn',
292
+
293
+ ...props.cancel,
191
294
 
192
- ...props.actionsProps
295
+ onClick: onCancel
296
+ }
297
+ })
298
+
299
+ const hasCancel = computed(() => typeof props.cancel === 'boolean' ? props.cancel : !!Object.keys(props.cancel))
300
+
301
+ // functions
302
+ function onCancel () {
303
+ props.cancel.onClick?.()
304
+
305
+ if (props.useAutoCloseOnAction || props.useAutoCloseOnCancel) {
306
+ updateModelValue(false)
307
+ }
308
+
309
+ emit('cancel')
193
310
  }
194
- })
195
311
 
196
- function updateModelValue (value) {
197
- emit('update:modelValue', value)
312
+ return {
313
+ defaultCancel,
314
+ hasCancel
315
+ }
316
+ }
317
+
318
+ function useDynamicComponents () {
319
+ // computeds
320
+ const mainComponent = computed(() => {
321
+ return {
322
+ is: props.useForm ? QForm : 'div',
323
+
324
+ /**
325
+ * adiciona evento de submit caso useForm seja true,
326
+ * uma vez que somente o q-form possui este evento.
327
+ */
328
+ props: {
329
+ ...(props.useForm && { onSubmit })
330
+ }
331
+ }
332
+ })
333
+
334
+ const hasRenderFunction = computed(() => {
335
+ const description = props.description
336
+
337
+ return typeof description === 'object' && description !== null && !Array.isArray(description)
338
+ })
339
+
340
+ const descriptionComponent = computed(() => hasRenderFunction.value ? props.description : 'div')
341
+
342
+ // functions
343
+ function submitHandler () {
344
+ if (!props.useForm) return
345
+
346
+ if (props.useValidationAllAtOnce) {
347
+ let isAllComponentValid = true
348
+ const components = form.value.getValidationComponents() || []
349
+
350
+ for (const component of components) {
351
+ const isValid = component?.validate?.()
352
+
353
+ if (!isValid) {
354
+ isAllComponentValid = false
355
+ }
356
+ }
357
+
358
+ emit('validate', isAllComponentValid)
359
+
360
+ return
361
+ }
362
+
363
+ emit('validate', form.value.validate())
364
+ }
365
+
366
+ /**
367
+ * Sem este método, ao clicar enter com a prop useForm ativada a tela era recarregada,
368
+ * e a ação de click do botão não era chamada pois ele não esta dentro do form.
369
+ */
370
+ function onSubmit (event) {
371
+ event.preventDefault()
372
+
373
+ if (hasOk.value) {
374
+ onOk()
375
+ submitHandler()
376
+ }
377
+ }
378
+
379
+ return {
380
+ mainComponent,
381
+ descriptionComponent
382
+ }
198
383
  }
199
384
  </script>
200
385
 
201
386
  <style lang="scss">
202
387
  .qas-dialog {
388
+ $root: &;
389
+
203
390
  .q-dialog__inner > div {
204
391
  box-shadow: $shadow-2;
205
392
  }
206
393
 
394
+ .q-dialog__inner--minimized {
395
+ padding: var(--qas-spacing-md);
396
+ }
397
+
207
398
  &--right {
208
399
  .q-dialog__inner {
209
400
  width: 100%;
@@ -216,5 +407,39 @@ function updateModelValue (value) {
216
407
  width: 100%;
217
408
  }
218
409
  }
410
+
411
+ // tamanhos
412
+ &--sm {
413
+ #{$root}__container {
414
+ max-width: 450px !important;
415
+ }
416
+ }
417
+
418
+ &--md {
419
+ #{$root}__container {
420
+ max-width: 550px !important;
421
+ }
422
+ }
423
+
424
+ &--lg {
425
+ #{$root}__container {
426
+ max-width: 800px !important;
427
+ }
428
+ }
429
+
430
+ &--xl {
431
+ #{$root}__container {
432
+ max-width: 1100px !important;
433
+ }
434
+ }
435
+
436
+ // tamanho mínimo dos botões de ação (primário e secundário)
437
+ &__actions {
438
+ .qas-btn--primary,
439
+ .qas-btn--secondary {
440
+ min-width: 120px;
441
+ width: 100%;
442
+ }
443
+ }
219
444
  }
220
445
  </style>
@@ -4,10 +4,10 @@ meta:
4
4
  desc: Componente de dialog.
5
5
 
6
6
  props:
7
- actions-props:
8
- desc: Props repassadas para o "QasActions".
9
- default: {}
10
- type: Object
7
+ badges:
8
+ desc: Badges que aparecem no canto superior direito do dialog.
9
+ type: [Array, Object]
10
+ default: []
11
11
 
12
12
  cancel:
13
13
  desc: Props para o botão de cancelar, pode ser objeto com as propriedades ou um boolean, caso for "false", remove o botão de cancelar.
@@ -15,19 +15,13 @@ props:
15
15
  type: [Object, Boolean]
16
16
  examples: ["{ label: 'Meu botão de cancelar', onClick: () => alert('fui clicado!') }"]
17
17
 
18
- card:
19
- desc: Objeto contendo as informações para serem exibidas dentro do dialog como "title" e "description".
20
- default: {}
21
- type: Object
22
- examples: ["{ title: 'Meu título', description: 'Minha descrição' }"]
23
-
24
- max-width:
25
- desc: Tamanho máximo do dialog.
26
- type: String
18
+ description:
19
+ desc: Descrição do dialog, pode ser uma string, um componente ou uma string com HTML (passando a prop useHtmlDescription).
20
+ type: [String, Object]
27
21
 
28
- min-width:
29
- desc: Tamanho mínimo do dialog.
30
- type: String
22
+ disable-close-button:
23
+ desc: Define se o botão de fechar no canto superior direito estará desabilitado.
24
+ type: Boolean
31
25
 
32
26
  model-value:
33
27
  desc: Model do componente, abre ou fecha o dialog.
@@ -42,27 +36,49 @@ props:
42
36
  type: [Object, Boolean]
43
37
  examples: ["{ label: 'Meu botão de confirmar', onClick: () => alert('fui clicado!') }"]
44
38
 
45
- persistent:
46
- desc: Define se o dialog vai fechar ou não após clicar fora do dialog.
39
+ size:
40
+ desc: Tamanho do dialog.
41
+ type: String
42
+ default: 'md'
43
+ options: ['xs', 'sm', 'md', 'lg', 'xl']
44
+
45
+ use-auto-close-on-cancel:
46
+ desc: Define se o dialog vai fechar automaticamente ao clicar no botão de cancelar.
47
+ type: Boolean
48
+ default: true
49
+
50
+ use-auto-close-on-ok:
51
+ desc: Define se o dialog vai fechar automaticamente ao clicar no botão de confirmar (ok).
52
+ type: Boolean
47
53
  default: true
54
+
55
+ use-close-button:
56
+ desc: Define se o dialog vai ter um botão de fechar no canto superior direito.
48
57
  type: Boolean
49
58
 
50
59
  use-form:
51
60
  desc: Define se a tag onde fica a descrição no dialog vai ser um "<q-form />" ou "<div />".
52
61
  type: Boolean
53
62
 
54
- use-full-max-width:
55
- desc: propriedade para utilizar `100% do maxWidth`.
63
+ use-html-description:
64
+ desc: Define se a descrição aceita HTML (utilize com cuidado para evitar XSS).
56
65
  type: Boolean
57
66
 
58
67
  use-validation-all-at-once:
59
68
  desc: Valida todos os campos de uma única vez, ao invés de ser um por vez (que é o padrão).
60
69
  type: Boolean
61
70
 
62
- slots:
63
- actions:
64
- desc: Slot para ações (botões por exemplo).
71
+ tertiary:
72
+ desc: Props para o botão terciário
73
+ type: Object
74
+ default: {}
75
+
76
+ title:
77
+ desc: Título do dialog.
78
+ type: String
79
+ required: true
65
80
 
81
+ slots:
66
82
  description:
67
83
  desc: Slot para descrição.
68
84
 
@@ -104,6 +120,10 @@ selectors:
104
120
  desc: Seletor do botão de cancelar do componente.
105
121
  examples: ['data-cy="dialog-cancel-btn"']
106
122
 
123
+ dialog-tertiary-btn:
124
+ desc: Seletor do botão terciário do componente.
125
+ examples: ['data-cy="dialog-tertiary-btn"']
126
+
107
127
  dialog-close-btn:
108
128
  desc: Seletor do botão de fechar do componente.
109
129
  examples: ['data-cy="dialog-close-btn"']
@@ -15,7 +15,7 @@ export default function useCancel (config = {}) {
15
15
  const defaultCancel = computed(() => {
16
16
  return {
17
17
  label: 'Cancelar',
18
- outline: true,
18
+ 'data-cy': 'dialog-cancel-btn',
19
19
 
20
20
  ...props.cancel,
21
21
 
@@ -35,12 +35,12 @@ export default function useDynamicComponents (config = {}) {
35
35
  })
36
36
 
37
37
  const hasRenderFunction = computed(() => {
38
- const description = props.card.description
38
+ const description = props.description
39
39
 
40
40
  return typeof description === 'object' && description !== null && !Array.isArray(description)
41
41
  })
42
42
 
43
- const descriptionComponent = computed(() => hasRenderFunction.value ? props.card.description : 'div')
43
+ const descriptionComponent = computed(() => hasRenderFunction.value ? props.description : 'div')
44
44
 
45
45
  // métodos
46
46
  function submitHandler () {
@@ -18,6 +18,7 @@ export default function useOk (config = {}) {
18
18
  return {
19
19
  label: 'Ok',
20
20
  type: (props.ok?.type || props.useForm) ? 'submit' : 'button',
21
+ 'data-cy': 'dialog-ok-btn',
21
22
 
22
23
  ...attrs,
23
24
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <qas-dialog class="qas-drawer" v-bind="attributes" @update:model-value="onUpdateModelValue">
2
+ <qas-dialog v-model="model" class="qas-drawer" v-bind="attributes">
3
3
  <template #header>
4
4
  <slot name="header">
5
5
  <div class="items-center justify-between row">
@@ -11,7 +11,7 @@
11
11
  </slot>
12
12
  </span>
13
13
 
14
- <qas-btn v-close-popup class="z-max" color="grey-10" data-cy="drawer-close-btn" icon="sym_r_close" variant="tertiary" @click="emit('update:modelValue', false)" />
14
+ <qas-btn class="z-max" color="grey-10" data-cy="drawer-close-btn" icon="sym_r_close" variant="tertiary" @click="close" />
15
15
  </div>
16
16
  </slot>
17
17
  </template>
@@ -40,7 +40,7 @@ import QasBtn from '../btn/QasBtn.vue'
40
40
 
41
41
  import useScreen from '../../composables/use-screen.js'
42
42
 
43
- import { computed, useAttrs } from 'vue'
43
+ import { computed, provide } from 'vue'
44
44
 
45
45
  defineOptions({
46
46
  name: 'QasDrawer',
@@ -58,6 +58,10 @@ const props = defineProps({
58
58
  default: '60%'
59
59
  },
60
60
 
61
+ persistent: {
62
+ type: Boolean
63
+ },
64
+
61
65
  position: {
62
66
  type: String,
63
67
  default: 'left',
@@ -74,9 +78,10 @@ const props = defineProps({
74
78
  }
75
79
  })
76
80
 
77
- const emit = defineEmits(['update:modelValue'])
81
+ // emits
82
+ const model = defineModel({ type: Boolean })
78
83
 
79
- const attrs = useAttrs()
84
+ // composables
80
85
  const screen = useScreen()
81
86
 
82
87
  // computed
@@ -89,25 +94,31 @@ const loadingStyle = computed(() => {
89
94
  })
90
95
 
91
96
  const attributes = computed(() => {
92
- const { modelValue } = attrs
93
-
94
97
  return {
95
- persistent: false,
96
- modelValue,
97
-
98
98
  ...props.dialogProps,
99
99
 
100
+ title: props.title,
100
101
  cancel: false,
101
- maxWidth: normalizedMaxWidth.value,
102
102
  maximized: true,
103
103
  ok: false,
104
- position: props.position,
105
- useFullMaxWidth: true
104
+ position: props.position
106
105
  }
107
106
  })
108
107
 
109
- function onUpdateModelValue (value) {
110
- emit('update:modelValue', value)
108
+ // globals
109
+ /**
110
+ * Manter dessa forma até issue #1431 ser resolvida.
111
+ */
112
+ provide('dialogDefaultProps', computed(() => {
113
+ return {
114
+ maxWidth: normalizedMaxWidth.value,
115
+ persistent: props.persistent
116
+ }
117
+ }))
118
+
119
+ // functions
120
+ function close () {
121
+ model.value = false
111
122
  }
112
123
  </script>
113
124
 
@@ -20,6 +20,11 @@ props:
20
20
  examples: [v-model="value"]
21
21
  model: true
22
22
 
23
+ persistent:
24
+ desc: Define se o drawer pode ser fechado clicando fora dele ou pressionando a tecla ESC.
25
+ default: false
26
+ type: Boolean
27
+
23
28
  position:
24
29
  desc: 'Posição do drawer, sendo possível apenas 2 opções: [left, right].'
25
30
  default: left
@@ -190,9 +190,9 @@ export default {
190
190
  ignoreRouterGuard: false,
191
191
 
192
192
  defaultDialogProps: {
193
- card: {
194
- description: 'Você está deixando a página e suas alterações serão perdidas. Tem certeza que deseja sair sem salvar?'
195
- },
193
+ title: 'Alterações não salvas',
194
+ description: 'Você está deixando a página e suas alterações serão perdidas. Tem certeza que deseja sair sem salvar?',
195
+ size: 'md',
196
196
 
197
197
  ok: { label: 'Continuar editando' },
198
198
 
@@ -28,9 +28,8 @@ export default function useDelete ({ props, destroyFn, emit }) {
28
28
 
29
29
  const defaultDialogProps = computed(() => {
30
30
  return {
31
- card: {
32
- description: 'Tem certeza que deseja excluir este item?'
33
- },
31
+ title: 'Excluir',
32
+ description: 'Tem certeza que deseja excluir este item?',
34
33
 
35
34
  ok: {
36
35
  label: 'Excluir',
@@ -1,12 +1,6 @@
1
1
  <template>
2
2
  <div class="pv-gallery-carousel-dialog">
3
- <qas-dialog v-model="model" :cancel="false" class="q-pa-xl" max-width="1100px" :ok="false" :persistent="false" use-full-max-width>
4
- <template #header>
5
- <div class="text-right">
6
- <qas-btn v-close-popup color="grey-10" icon="sym_r_close" variant="tertiary" @click="close" />
7
- </div>
8
- </template>
9
-
3
+ <qas-dialog v-model="model" v-bind="dialogProps">
10
4
  <template #description>
11
5
  <q-carousel v-model="imageIndexModel" animated :arrows="!screen.isSmall" class="pv-gallery-carousel-dialog__carousel" control-text-color="primary" data-cy="gallery-carousel" :fullscreen="screen.isSmall" :height="carouselImageHeight" next-icon="sym_r_chevron_right" prev-icon="sym_r_chevron_left" swipeable :thumbnails="!isSingleImage">
12
6
  <q-carousel-slide v-for="(image, index) in props.images" :key="index" class="bg-no-repeat bg-size-contain" :data-cy="`gallery-carousel-slide-${index}`" :img-src="image.url" :name="index">
@@ -52,6 +46,13 @@ const screen = useScreen()
52
46
 
53
47
  const carouselImageHeight = 'calc((500/976) * 100vh)'
54
48
 
49
+ const dialogProps = {
50
+ cancel: false,
51
+ ok: false,
52
+ size: 'xl',
53
+ title: 'Galeria de imagens'
54
+ }
55
+
55
56
  const model = computed({
56
57
  get () {
57
58
  return props.modelValue
@@ -15,7 +15,7 @@
15
15
  </slot>
16
16
 
17
17
  <div v-if="hasBadges" class="col-auto items-center q-col-gutter-sm row">
18
- <div v-for="(badge, badgeIndex) in props.badges" :key="badgeIndex">
18
+ <div v-for="(badge, badgeIndex) in normalizedBadges" :key="badgeIndex">
19
19
  <qas-skeleton v-if="props.skeleton" type="QasBadge" />
20
20
 
21
21
  <qas-badge v-else v-bind="badge" />
@@ -73,7 +73,7 @@ const props = defineProps({
73
73
  },
74
74
 
75
75
  badges: {
76
- type: Array,
76
+ type: [Array, Object],
77
77
  default: () => []
78
78
  },
79
79
 
@@ -203,13 +203,15 @@ const actionsComponent = computed(() => {
203
203
  return component.true
204
204
  })
205
205
 
206
+ const normalizedBadges = computed(() => Array.isArray(props.badges) ? props.badges : [props.badges])
207
+
206
208
  const hasActionsComponent = computed(() => {
207
209
  return hasDefaultButton.value || hasDefaultActionsMenu.value || hasDefaultFilters.value
208
210
  })
209
211
 
210
212
  const hasActionsSection = computed(() => !!slots.actions || hasActionsComponent.value)
211
213
 
212
- const hasBadges = computed(() => !!props.badges.length)
214
+ const hasBadges = computed(() => !!normalizedBadges.value.length)
213
215
  const hasLabel = computed(() => !!Object.keys(props.labelProps).length)
214
216
  const hasDefaultButton = computed(() => !!Object.keys(props.buttonProps).length)
215
217
  const hasDefaultFilters = computed(() => !!Object.keys(props.filtersProps).length)
@@ -12,7 +12,7 @@ props:
12
12
  badges:
13
13
  desc: Adiciona badges ao lado do titulo (QasLabel), caso não tenha titulo fica somente as bages acima da descrição.
14
14
  default: []
15
- type: Array
15
+ type: [Array, Object]
16
16
 
17
17
  button-props:
18
18
  desc: Propriedades do QasBtn.
@@ -54,7 +54,7 @@
54
54
 
55
55
  <template #description>
56
56
  <slot name="dialog-description">
57
- <div v-if="dialogDescription" class="q-mb-xl text-center">
57
+ <div v-if="dialogDescription" class="q-mb-md">
58
58
  {{ dialogDescription }}
59
59
  </div>
60
60
 
@@ -342,8 +342,8 @@ function useSelectDialog () {
342
342
 
343
343
  const defaultDialogProps = computed(() => {
344
344
  return {
345
- useFullMaxWidth: true,
346
-
345
+ size: 'md',
346
+ title: 'Adicionar itens',
347
347
  ...props.dialogProps,
348
348
 
349
349
  onBeforeShow: event => {
@@ -3,12 +3,8 @@
3
3
  <qas-uploader ref="uploader" v-model="model" :add-button-fn="openDialog" :use-resize="false" v-bind="defaultUploaderProps" />
4
4
 
5
5
  <qas-dialog v-model="isOpenedDialog" v-bind="defaultDialogProps">
6
- <template #header>
7
- <div class="text-bold text-center">Insira sua assinatura digital no campo abaixo</div>
8
- </template>
9
-
10
6
  <template #description>
11
- <div :style="signaturePadWidth">
7
+ <div>
12
8
  <qas-signature-pad ref="signaturePadModal" v-model:empty="isEmpty" :height="signaturePadHeight" />
13
9
  </div>
14
10
  </template>
@@ -82,10 +78,11 @@ export default {
82
78
 
83
79
  defaultDialogProps () {
84
80
  return {
85
- maxWidth: '620px',
81
+ title: 'Assinatura digital',
82
+ size: 'md',
86
83
  ...this.dialogProps,
87
84
  ok: {
88
- label: 'Salvar',
85
+ label: 'Assinar',
89
86
  onClick: () => this.getSignatureData()
90
87
  }
91
88
  }
@@ -111,16 +108,6 @@ export default {
111
108
  return sizes.true
112
109
  },
113
110
 
114
- signaturePadWidth () {
115
- const sizes = {
116
- [this.$qas.screen.isSmall]: { width: '100%' },
117
- [this.$qas.screen.isMedium]: { width: '570px' },
118
- [this.$qas.screen.isLarge]: { width: '350px' }
119
- }
120
-
121
- return sizes.true
122
- },
123
-
124
111
  uploaderScope () {
125
112
  return this.$refs?.uploader?.uploader
126
113
  }
@@ -19,7 +19,7 @@
19
19
  <qas-btn v-if="hasButton" class="q-ml-xs" :label="buttonLabel" @click.stop.prevent="toggle" />
20
20
  </div>
21
21
 
22
- <qas-dialog v-model="show" v-bind="defaultProps" aria-label="Diálogo de texto completo" role="dialog">
22
+ <qas-dialog v-model="show" v-bind="defaultProps" aria-label="Diálogo de texto completo" role="dialog" size="md">
23
23
  <template v-if="showDescriptionSlot" #description>
24
24
  <component :is="dialogComponent.is" v-bind="dialogComponent.props" v-model:results="searchModel">
25
25
  <q-list separator>
@@ -225,10 +225,8 @@ function useDialog ({ props, textContent }) {
225
225
 
226
226
  ...props.dialogProps,
227
227
 
228
- card: {
229
- title: props.dialogTitle,
230
- description: description.value
231
- }
228
+ title: props.dialogTitle,
229
+ description: description.value
232
230
  }
233
231
  })
234
232
 
@@ -186,9 +186,9 @@ export default {
186
186
 
187
187
  destroyDialogConfig () {
188
188
  return {
189
- card: {
190
- description: 'Todas as informações serão perdidas. Deseja realmente continuar?'
191
- },
189
+ title: 'Excluir',
190
+ description: 'Todas as informações serão perdidas. Deseja realmente continuar?',
191
+ size: 'md',
192
192
  ok: {
193
193
  label: 'Excluir',
194
194
  onClick: this.destroy
@@ -201,9 +201,7 @@ export default {
201
201
 
202
202
  formDialogConfig () {
203
203
  return {
204
- card: {
205
- title: this.isAdd ? 'Adicionar ramo' : 'Editar ramo'
206
- },
204
+ title: this.isAdd ? 'Adicionar ramo' : 'Editar ramo',
207
205
  ok: {
208
206
  label: 'Salvar',
209
207
  loading: this.isSubmitting
@@ -317,7 +317,17 @@ export default {
317
317
  } = this.$props
318
318
 
319
319
  return {
320
- dialogProps,
320
+ dialogProps: {
321
+ title: 'Editar arquivo',
322
+
323
+ ok: {
324
+ label: 'Salvar',
325
+ ...dialogProps.ok
326
+ },
327
+
328
+ ...dialogProps
329
+ },
330
+
321
331
  fields,
322
332
  formGeneratorProps,
323
333
  galleryCardProps,
@@ -17,22 +17,62 @@ const historyRoute = ref({
17
17
  const canLeaveOverlay = ref(true)
18
18
 
19
19
  /**
20
- * Definição de callbacks locais para esta instância.
21
- * Obs: são arrays para permitir múltiplos callbacks, ex se você usa "onCloseOverlay" em 2 componentes diferentes,
22
- * ambos serão executados.
20
+ * Cria o estado de callbacks para uma entidade (ou para o modo padrão sem entidade).
21
+ *
22
+ * Definição de callbacks para a entidade.
23
+ * Obs: são arrays para permitir múltiplos callbacks, ex se você usa "onCloseOverlay" em 2 componentes
24
+ * diferentes da mesma entidade, ambos serão executados.
25
+ * @private
23
26
  */
24
- const callbackFunctions = {
25
- onCloseOverlay: [],
26
- onExpandOverlay: [],
27
- onHideOverlay: [],
28
- onBackgroundChange: [],
29
- onOverlayChange: []
27
+ function createCallbackFunctionsByEntity () {
28
+ return {
29
+ onCloseOverlay: [],
30
+ onExpandOverlay: [],
31
+ onHideOverlay: [],
32
+ onBackgroundChange: [],
33
+ onOverlayChange: []
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Registro de callbacks por entidade.
39
+ * A chave 'default' representa o modo padrão (sem entidade).
40
+ * @type {Map<string, ReturnType<typeof createCallbackFunctionsByEntity>>}
41
+ */
42
+ const callbackFunctionsByEntity = new Map()
43
+
44
+ /**
45
+ * Retorna (ou cria) o estado compartilhado para a entidade informada.
46
+ * @private
47
+ * @param {string|undefined} entity
48
+ */
49
+ function getCallbackFunctionsByEntity (entity) {
50
+ const entityKey = entity ?? 'default'
51
+
52
+ if (!callbackFunctionsByEntity.has(entityKey)) {
53
+ callbackFunctionsByEntity.set(entityKey, createCallbackFunctionsByEntity())
54
+ }
55
+
56
+ return callbackFunctionsByEntity.get(entityKey)
30
57
  }
31
58
 
32
59
  /**
33
60
  * Composable para gerenciar navegação em overlays, sempre que for lidar com overlays utilize esse composable.
61
+ *
62
+ * @example
63
+ * Modo padrão — comportamento legado, estado global compartilhado
64
+ * const nav = useOverlayNavigation()
65
+ *
66
+ * Modo com entidade — página A e componente B compartilham 'activities', isolado de 'funnels'
67
+ * const nav = useOverlayNavigation('activities')
68
+ *
69
+ * @param {string} [entity] - Nome da entidade para isolar os callbacks.
70
+ * Quando informado, instâncias com a mesma entidade compartilham os callbacks entre si, mas são isoladas de
71
+ * instâncias com entidades diferentes.
34
72
  */
35
- export default function useOverlayNavigation () {
73
+ export default function useOverlayNavigation (entity) {
74
+ const callbackFunctions = getCallbackFunctionsByEntity(entity)
75
+
36
76
  // composables
37
77
  const route = useRoute()
38
78
  const router = useRouter()
@@ -255,6 +295,71 @@ export default function useOverlayNavigation () {
255
295
  callbackFunctions[callbackName].forEach(fn => fn(payload))
256
296
  }
257
297
 
298
+ /**
299
+ * Remove listeners registrados no composable.
300
+ *
301
+ * @param {Function|Function[]|string} [target] - Função, array de funções, nome da entidade ou vazio para limpar
302
+ * a entidade `default`.
303
+ *
304
+ * @example
305
+ * ```js
306
+ * const { onCloseOverlay, removeListeners } = useOverlayNavigation('activities')
307
+ *
308
+ * function handleClose () { ... }
309
+ * onCloseOverlay(handleClose)
310
+ *
311
+ * removeListeners(handleClose) - Remove apenas handleClose da entidade atual
312
+ *
313
+ * removeListeners([handleClose, handleExpand]) - Remove handleClose e handleExpand da entidade atual
314
+ *
315
+ * removeListeners('activities') - Remove todas as funções registradas na entidade 'activities'
316
+ *
317
+ * removeListeners() - Remove todas as funções das instâncias sem entidade (entity 'default')
318
+ * ```
319
+ */
320
+ function removeListeners (target) {
321
+ // remove todos os callbacks da entidade 'default' (modo padrão sem entidade)
322
+ if (!target) {
323
+ const defaultCallbackFunctions = callbackFunctionsByEntity.get('default')
324
+
325
+ if (!defaultCallbackFunctions) return
326
+
327
+ for (const functionKey of Object.keys(defaultCallbackFunctions)) {
328
+ defaultCallbackFunctions[functionKey] = []
329
+ }
330
+
331
+ return
332
+ }
333
+
334
+ // Remove todos os callbacks de uma entidade pelo nome
335
+ if (typeof target === 'string') {
336
+ // Pego o estado da entidade informada.
337
+ const callbackFunctions = callbackFunctionsByEntity.get(target)
338
+
339
+ if (!callbackFunctions) return
340
+
341
+ // Reseto as funções de callback para a entidade, mantendo a estrutura mas limpando os arrays.
342
+ for (const functionKey of Object.keys(callbackFunctions)) {
343
+ callbackFunctions[functionKey] = []
344
+ }
345
+
346
+ return
347
+ }
348
+
349
+ // Normaliza para array e remove as funções da entidade atual
350
+ const functionsToRemove = Array.isArray(target) ? target : [target]
351
+
352
+ const callbackFunctionsKeys = Object.keys(callbackFunctions)
353
+
354
+ /**
355
+ * Percorro cada função de callback registrada na entidade atual e filtro as funções que devem ser removidas,
356
+ * mantendo as que não devem ser removidas.
357
+ */
358
+ for (const key of callbackFunctionsKeys) {
359
+ callbackFunctions[key] = callbackFunctions[key].filter(fn => !functionsToRemove.includes(fn))
360
+ }
361
+ }
362
+
258
363
  /**
259
364
  * Função para disparar mudanças no background componente.
260
365
  *
@@ -404,6 +509,7 @@ export default function useOverlayNavigation () {
404
509
  triggerOverlayChange,
405
510
  getNormalizedRoute,
406
511
  toggleCanLeaveOverlay,
512
+ removeListeners,
407
513
 
408
514
  // callbacks functions
409
515
  onBackgroundChange,
@@ -21,16 +21,12 @@ export default function (config = {}) {
21
21
  const { entity, id, url } = deleteActionParams
22
22
 
23
23
  const defaultDialogProps = {
24
- useForm: true,
24
+ useAutoCloseOnOk: false,
25
25
 
26
- ...dialogProps,
27
-
28
- card: {
29
- title: 'Excluir',
30
- description: 'Tem certeza que deseja excluir este item?',
26
+ title: 'Excluir',
27
+ description: 'Tem certeza que deseja excluir este item?',
31
28
 
32
- ...dialogProps.card
33
- },
29
+ ...dialogProps,
34
30
 
35
31
  ok: {
36
32
  label: 'Excluir',