@veritree/ui 0.22.2 → 0.23.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.
Files changed (37) hide show
  1. package/mixins/floating-ui-content.js +0 -21
  2. package/mixins/floating-ui-item.js +93 -47
  3. package/mixins/floating-ui.js +4 -13
  4. package/mixins/form-control-icon.js +2 -2
  5. package/mixins/form-control.js +9 -5
  6. package/nuxt.js +1 -0
  7. package/package.json +1 -1
  8. package/src/components/Button/VTButton.vue +5 -5
  9. package/src/components/Dialog/VTDialog.vue +6 -15
  10. package/src/components/Dialog/VTDialogClose.vue +19 -25
  11. package/src/components/Dialog/VTDialogContent.vue +18 -21
  12. package/src/components/Dialog/VTDialogFooter.vue +7 -18
  13. package/src/components/Dialog/VTDialogHeader.vue +15 -18
  14. package/src/components/Dialog/VTDialogMain.vue +11 -18
  15. package/src/components/Dialog/VTDialogOverlay.vue +14 -18
  16. package/src/components/Dialog/VTDialogTitle.vue +10 -7
  17. package/src/components/Drawer/VTDrawerContent.vue +1 -1
  18. package/src/components/Drawer/VTDrawerFooter.vue +1 -1
  19. package/src/components/DropdownMenu/VTDropdownMenu.vue +19 -0
  20. package/src/components/DropdownMenu/VTDropdownMenuContent.vue +5 -6
  21. package/src/components/DropdownMenu/VTDropdownMenuItem.vue +2 -2
  22. package/src/components/Form/VTFormFeedback.vue +5 -5
  23. package/src/components/Form/VTInput.vue +5 -2
  24. package/src/components/Form/VTTextarea.vue +5 -2
  25. package/src/components/Listbox/VTListbox.vue +35 -11
  26. package/src/components/Listbox/VTListboxContent.vue +4 -7
  27. package/src/components/Listbox/VTListboxItem.vue +117 -6
  28. package/src/components/Listbox/VTListboxList.vue +1 -24
  29. package/src/components/Listbox/VTListboxSearch.vue +58 -52
  30. package/src/components/Listbox/VTListboxTrigger.vue +7 -4
  31. package/src/components/Modal/VTModal.vue +1 -1
  32. package/src/components/Popover/VTPopover.vue +19 -0
  33. package/src/components/Popover/VTPopoverContent.vue +3 -3
  34. package/src/components/Tooltip/VTTooltip.vue +65 -0
  35. package/src/components/Tooltip/VTTooltipContent.vue +59 -0
  36. package/src/components/Tooltip/VTTooltipTrigger.vue +100 -0
  37. package/src/components/Utils/FloatingUi.vue +27 -13
@@ -10,16 +10,11 @@ export const floatingUiContentMixin = {
10
10
  type: Boolean,
11
11
  default: false,
12
12
  },
13
- floatingUiClass: {
14
- type: [String, Function],
15
- default: null,
16
- },
17
13
  },
18
14
 
19
15
  data() {
20
16
  return {
21
17
  el: null,
22
- isMousemove: false,
23
18
  visible: false,
24
19
  };
25
20
  },
@@ -79,22 +74,6 @@ export const floatingUiContentMixin = {
79
74
  }
80
75
  },
81
76
 
82
- // Mousemove instead of mouseover to support keyboard navigation.
83
- // The problem with mouseover is that when scrolling (scrollIntoView),
84
- // mouseover event gets triggered.
85
- setMousemove() {
86
- this.isMousemove = true;
87
- },
88
-
89
- unsetMousemove() {
90
- this.isMousemove = false;
91
- },
92
-
93
- getMousemove() {
94
- return this.isMousemove;
95
- },
96
-
97
- //
98
77
  setActiveDescedant(id) {
99
78
  this.activeDescedant = id;
100
79
  },
@@ -19,10 +19,6 @@ export const floatingUiItemMixin = {
19
19
  return this.apiInjected().items;
20
20
  },
21
21
 
22
- el() {
23
- return this.$el;
24
- },
25
-
26
22
  componentContent() {
27
23
  return this.apiInjected().componentContent;
28
24
  },
@@ -51,7 +47,7 @@ export const floatingUiItemMixin = {
51
47
  ? `${this.componentName}--selected`
52
48
  : null
53
49
  : this.selected
54
- ? 'bg-secondary-200/10'
50
+ ? 'bg-gray-200'
55
51
  : null,
56
52
  ];
57
53
  },
@@ -62,20 +58,24 @@ export const floatingUiItemMixin = {
62
58
  index: null,
63
59
  selected: false,
64
60
  tabIndex: 0,
61
+ eventType: null,
65
62
  };
66
63
  },
67
64
 
68
65
  watch: {
69
66
  selected(newValue) {
70
- if (!newValue || !this.componentContent) return;
67
+ if (!newValue || !this.componentContent) {
68
+ return;
69
+ }
71
70
 
72
71
  this.componentContent.setActiveDescedant(this.id);
73
72
 
74
- const isMousemove = this.componentContent.getMousemove();
75
-
76
- if (!isMousemove) {
77
- this.el.scrollIntoView({ block: 'nearest' });
73
+ // do not scroll into view if it's a mouse event
74
+ if (this.eventType && this.eventType.includes('mouse')) {
75
+ return;
78
76
  }
77
+
78
+ this.$el.scrollIntoView({ block: 'nearest' });
79
79
  },
80
80
  },
81
81
 
@@ -91,30 +91,45 @@ export const floatingUiItemMixin = {
91
91
 
92
92
  methods: {
93
93
  init() {
94
+ this.index = this.items.length;
95
+
94
96
  const item = {
95
97
  id: this.id,
98
+ text: this.$slots.default[0].text,
96
99
  select: this.select,
97
100
  unselect: this.unselect,
101
+ isSelected: this.isSelected,
98
102
  focus: this.focus,
99
103
  onClick: this.onClick,
100
104
  };
101
-
105
+
102
106
  this.apiInjected().registerItem(item);
103
-
104
- this.index = this.items.length - 1;
105
107
  },
106
108
 
109
+ /**
110
+ * mousemove and mouseleave events will be attached to
111
+ * the element here instead of using vue @mousemove
112
+ * and @mouseleave in the element since they did
113
+ * not worker properly
114
+ */
107
115
  addMouseEventListeners() {
108
- this.el.addEventListener('mousemove', this.onMousemove);
109
- this.el.addEventListener('mouseleave', this.onMouseleave);
116
+ this.$el.addEventListener('mousemove', this.onMousemove);
117
+ this.$el.addEventListener('mouseleave', this.onMouseleave);
110
118
  },
111
119
 
120
+ /**
121
+ * remove the mousemove and mouseleave added for
122
+ * avoiding memory leaks, if @mousemove and
123
+ * @mouseleave were in the element, this
124
+ * wound't be needed
125
+ */
112
126
  removeMouseEventListeners() {
113
- this.el.removeEventListener('mousemove', this.onMousemove);
114
- this.el.removeEventListener('mouseleave', this.onMouseleave);
127
+ this.$el.removeEventListener('mousemove', this.onMousemove);
128
+ this.$el.removeEventListener('mouseleave', this.onMouseleave);
115
129
  },
116
130
 
117
- select() {
131
+ select(eventType) {
132
+ this.eventType = eventType;
118
133
  this.selected = true;
119
134
  },
120
135
 
@@ -122,59 +137,82 @@ export const floatingUiItemMixin = {
122
137
  this.selected = false;
123
138
  },
124
139
 
140
+ isSelected() {
141
+ return this.selected;
142
+ },
143
+
125
144
  focus() {
126
- if (!this.el) return;
145
+ if (!this.$el) return;
127
146
 
128
147
  this.tabIndex = -1;
129
148
  this.selected = true;
130
- this.el.focus();
149
+ this.$el.focus();
131
150
  },
132
151
 
133
152
  focusFirstItem() {
134
- this.setFocusToItem(0);
153
+ const selectedIndex = this.getItemSelectedIndex();
154
+ const newSelectedIndex = 0;
155
+
156
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
135
157
  },
136
158
 
137
159
  focusLastItem() {
138
- this.setFocusToItem(this.items.length - 1);
160
+ const selectedIndex = this.getItemSelectedIndex();
161
+ const newSelectedIndex = this.items.length - 1;
162
+
163
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
139
164
  },
140
165
 
141
166
  /**
142
- * Focus the previous item in the menu.
143
- * If is the first item, jump to the last item.
167
+ * Focus the next item in the menu.
168
+ * If is the last item, jump to the first item.
144
169
  */
145
- focusPreviousItem() {
146
- const isLast = this.index === this.items.length - 1;
147
- const goToIndex = isLast ? 0 : this.index + 1;
170
+ focusNextItem() {
171
+ const selectedIndex = this.getItemSelectedIndex();
172
+ const isLast = selectedIndex === this.items.length - 1;
173
+ const firstItemIndex = 0;
174
+ const nextItemIndex = selectedIndex + 1;
175
+ const newSelectedIndex = isLast ? firstItemIndex : nextItemIndex;
148
176
 
149
- this.setFocusToItem(goToIndex);
177
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
150
178
  },
151
179
 
152
180
  /**
153
- * Focus the next item in the menu.
154
- * If is the last item, jump to the first item.
181
+ * Focus the previous item in the menu.
182
+ * If is the first item, jump to the last item.
155
183
  */
156
- focusNextItem() {
157
- const isFirst = this.index === 0;
158
- const goToIndex = isFirst ? this.items.length - 1 : this.index - 1;
184
+ focusPreviousItem() {
185
+ const selectedIndex = this.getItemSelectedIndex();
186
+ const isFirst = selectedIndex === 0;
187
+ const lastItemIndex = this.items.length - 1;
188
+ const previousItemIndex = selectedIndex - 1;
189
+ const newSelectedIndex = isFirst ? lastItemIndex : previousItemIndex;
159
190
 
160
- this.setFocusToItem(goToIndex);
191
+ this.setFocusToItem(selectedIndex, newSelectedIndex);
161
192
  },
162
193
 
163
194
  /**
164
- * Focus item by remove its tabindex and calling
195
+ * Focus/select item by removing its tabindex and calling
165
196
  * focus to the element.
166
197
  *
167
- * @param {Number, String} goToIndex
198
+ * @param {Number, String} selectedIndex
199
+ * @param {Number, String} newSelectedIndex
168
200
  */
169
- setFocusToItem(goToIndex) {
201
+ setFocusToItem(selectedIndex, newSelectedIndex) {
170
202
  this.tabIndex = 0;
171
- this.selected = false;
172
203
 
173
- // set all selected to false
174
- this.items.forEach((item) => item.unselect());
204
+ // before focusing, let's unselect selected
205
+ // item that were previously focused
206
+ if (selectedIndex >= 0) {
207
+ this.items[selectedIndex].unselect();
208
+ }
209
+
210
+ // focus new item
211
+ this.items[newSelectedIndex].focus();
212
+ },
175
213
 
176
- // focus item
177
- this.items[goToIndex].focus();
214
+ getItemSelectedIndex() {
215
+ return this.items.findIndex((item) => item.isSelected());
178
216
  },
179
217
 
180
218
  leaveMenu() {
@@ -189,7 +227,10 @@ export const floatingUiItemMixin = {
189
227
  return;
190
228
  }
191
229
 
192
- this.value ? this.apiInjected().emit(this.value) : this.$emit('click');
230
+ this.value !== undefined
231
+ ? this.apiInjected().emit(this.value)
232
+ : this.$emit('click');
233
+
193
234
  this.$nextTick(() => this.leaveMenu());
194
235
  },
195
236
 
@@ -197,20 +238,25 @@ export const floatingUiItemMixin = {
197
238
  this.leaveMenu();
198
239
  },
199
240
 
200
- onMousemove() {
241
+ onMousemove(event) {
201
242
  if (this.selected) {
202
243
  return;
203
244
  }
204
245
 
246
+ // unselect all items before selecting new item
205
247
  this.items.forEach((item) => item.unselect());
206
248
 
207
- this.select();
208
- this.componentContent.setMousemove();
249
+ /**
250
+ * Select item passing the event type to decide if
251
+ * scrolling will be disabled or not. It is
252
+ * expected that on mouse move event, the scroll
253
+ * doesn't happen automatically.
254
+ */
255
+ this.select(event.type);
209
256
  },
210
257
 
211
258
  onMouseleave() {
212
259
  this.unselect();
213
- this.componentContent.unsetMousemove();
214
260
  },
215
261
  },
216
262
  };
@@ -1,13 +1,6 @@
1
1
  import { computePosition, flip, shift, offset, size } from '@floating-ui/dom';
2
2
 
3
3
  export const floatingUiMixin = {
4
- props: {
5
- placement: {
6
- type: String,
7
- default: 'bottom-start',
8
- },
9
- },
10
-
11
4
  data() {
12
5
  return {
13
6
  component: null,
@@ -40,22 +33,20 @@ export const floatingUiMixin = {
40
33
  },
41
34
 
42
35
  positionContentToTrigger() {
43
- // console.log(window.innerWidth)
44
-
45
36
  const trigger = document.getElementById(this.componentTrigger.id);
46
37
  const content = document.getElementById(this.componentContent.id);
38
+ const minWidthLimit = Number(this.floatingUiMinWidth);
47
39
 
48
40
  computePosition(trigger, content, {
49
41
  placement: this.placement,
50
42
  middleware: [
51
- offset(6),
43
+ offset(5),
52
44
  flip(),
53
45
  shift({ padding: 5 }),
54
46
  size({
55
47
  apply({ rects }) {
56
- // the min width to floating uis should be 200
57
- // since less than that can look not as nice
58
- const minWidthLimit = 200;
48
+ if (!minWidthLimit) return;
49
+
59
50
  const width = rects.reference.width;
60
51
  const minWidth = width < minWidthLimit ? minWidthLimit : width;
61
52
 
@@ -1,9 +1,9 @@
1
- import { formControlMixin } from '../mixins/form-control';
1
+ import { formControlMixin, formControlStyleMixin } from '../mixins/form-control';
2
2
 
3
3
  export const formControlIconMixin = {
4
4
  inheritAttrs: false,
5
5
 
6
- mixins: [formControlMixin],
6
+ mixins: [formControlMixin, formControlStyleMixin],
7
7
 
8
8
  data() {
9
9
  return {
@@ -45,11 +45,19 @@ export const formControlMixin = {
45
45
  );
46
46
  },
47
47
 
48
+ isError() {
49
+ return this.variant === 'error';
50
+ },
51
+ },
52
+ };
53
+
54
+ export const formControlStyleMixin = {
55
+ computed: {
48
56
  classComputed() {
49
57
  return [
50
58
  this.headless
51
59
  ? `${this.name}`
52
- : 'leading-0 flex w-full max-w-full appearance-none items-center justify-between gap-3 rounded border border-solid px-3 py-2 font-inherit text-base text-inherit file:hidden focus:border-secondary-200',
60
+ : 'leading-0 flex w-full max-w-full appearance-none items-center justify-between rounded border border-solid px-3 py-2 font-inherit text-base text-inherit file:hidden focus:border-secondary-200',
53
61
  // variant styles
54
62
  this.headless
55
63
  ? `${this.name}--${this.variant}`
@@ -64,9 +72,5 @@ export const formControlMixin = {
64
72
  : 'h-10',
65
73
  ];
66
74
  },
67
-
68
- isError() {
69
- return this.variant === 'error';
70
- },
71
75
  },
72
76
  };
package/nuxt.js CHANGED
@@ -14,6 +14,7 @@ const components = [
14
14
  'src/components/Popover',
15
15
  'src/components/ProgressBar',
16
16
  'src/components/Tabs',
17
+ 'src/components/Tooltip',
17
18
  ]
18
19
 
19
20
  export default function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veritree/ui",
3
- "version": "0.22.2",
3
+ "version": "0.23.0",
4
4
  "description": "veritree ui library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -3,7 +3,7 @@
3
3
  <component
4
4
  :is="tag"
5
5
  :to="to"
6
- :href="href"
6
+ :href="href || to"
7
7
  :type="type"
8
8
  :disabled="isDisabled"
9
9
  :class="[
@@ -17,11 +17,11 @@
17
17
  headless
18
18
  ? `button--${variant}`
19
19
  : isPrimary
20
- ? 'border-transparent bg-secondary-400 text-white hover:bg-secondary-500 active:bg-secondary-600 disabled:bg-gray-200 disabled:text-gray-400'
20
+ ? 'bg-secondary-400 hover:bg-secondary-500 focus:bg-secondary-600 active:bg-secondary-600 border-transparent text-white disabled:bg-gray-200 disabled:text-gray-400'
21
21
  : isSecondary
22
- ? 'border-gray-400 bg-white text-gray-700 hover:bg-gray-100 active:bg-gray-200 disabled:border-gray-300 disabled:text-gray-400'
22
+ ? 'border-gray-400 bg-white text-gray-700 hover:bg-gray-100 hover:bg-gray-200 active:bg-gray-200 disabled:border-gray-300 disabled:text-gray-400'
23
23
  : isTertiary
24
- ? 'border-transparent text-secondary-400 hover:text-secondary-500 active:text-secondary-500 disabled:text-gray-400'
24
+ ? 'text-secondary-400 hover:text-secondary-500 focus:text-secondary-600 active:text-secondary-600 border-transparent disabled:text-gray-400'
25
25
  : isIcon
26
26
  ? 'text-primary-100 focus-within:bg-gray-200 hover:bg-gray-200 active:bg-gray-300'
27
27
  : null,
@@ -43,7 +43,7 @@
43
43
  <VTSpinner v-if="busy" class="absolute inset-0 m-auto" />
44
44
  <span
45
45
  :class="[
46
- headless ? null : 'inline-flex items-center gap-2 self-center mx-auto',
46
+ headless ? null : 'mx-auto inline-flex items-center gap-2 self-center',
47
47
  headless && busy ? 'button--busy' : busy ? 'invisible' : null,
48
48
  ]"
49
49
  >
@@ -3,10 +3,11 @@
3
3
  <div
4
4
  v-if="visible"
5
5
  :id="id"
6
- :class="{
7
- Dialog: headless,
8
- 'fixed inset-0 z-50 grid grid-cols-1 grid-rows-1 p-4 md:p-8': !headless,
9
- }"
6
+ :class="[
7
+ headless
8
+ ? 'dialog'
9
+ : 'fixed inset-0 z-50 grid grid-cols-1 grid-rows-1 p-4 md:p-8',
10
+ ]"
10
11
  aria-modal="true"
11
12
  @click="onClick"
12
13
  >
@@ -28,11 +29,8 @@ export default {
28
29
 
29
30
  provide() {
30
31
  return {
31
- api: () => {
32
- const id = this.id;
32
+ apiDialog: () => {
33
33
  const componentId = this.componentId;
34
- const isDark = this.dark;
35
- const isHeadless = this.headless;
36
34
 
37
35
  const registerContent = (content) => {
38
36
  if (!content) return;
@@ -49,10 +47,7 @@ export default {
49
47
  const emit = () => this.emit();
50
48
 
51
49
  return {
52
- id,
53
50
  componentId,
54
- isDark,
55
- isHeadless,
56
51
  hide,
57
52
  emit,
58
53
  registerContent,
@@ -75,10 +70,6 @@ export default {
75
70
  type: Boolean,
76
71
  default: false,
77
72
  },
78
- dark: {
79
- type: Boolean,
80
- default: false,
81
- },
82
73
  },
83
74
 
84
75
  data() {
@@ -2,48 +2,42 @@
2
2
  <VTButton
3
3
  variant="icon"
4
4
  :id="id"
5
- :class="{
6
- 'Dialog-close': headless,
7
- }"
8
- :theme="theme"
5
+ :class="[headless ? 'dialog-close' : null]"
9
6
  @click.prevent="hide"
10
- ><slot><IconClose class="h-5 w-5" /></slot
11
- ></VTButton>
7
+ >
8
+ <slot>
9
+ <IconClose class="h-5 w-5" />
10
+ </slot>
11
+ </VTButton>
12
12
  </template>
13
13
 
14
14
  <script>
15
- import { IconClose } from "@veritree/icons";
16
- import VTButton from "../Button/VTButton.vue";
15
+ import { IconClose } from '@veritree/icons';
16
+ import VTButton from '../Button/VTButton.vue';
17
17
 
18
18
  export default {
19
- name: "VTDialogClose",
19
+ name: 'VTDialogClose',
20
20
 
21
21
  components: { IconClose, VTButton },
22
22
 
23
- inject: ["api"],
23
+ inject: ['apiDialog'],
24
24
 
25
- computed: {
26
- id() {
27
- return `dialog-close-${this.api().componentId}`;
28
- },
29
-
30
- dark() {
31
- return this.api().isDark;
32
- },
33
-
34
- headless() {
35
- return this.api().isHeadless;
25
+ props: {
26
+ headless: {
27
+ type: Boolean,
28
+ default: false,
36
29
  },
30
+ },
37
31
 
38
- // temporary till button theme is implemented
39
- theme() {
40
- return this.dark ? "dark" : null;
32
+ computed: {
33
+ id() {
34
+ return `dialog-close-${this.apiDialog().componentId}`;
41
35
  },
42
36
  },
43
37
 
44
38
  methods: {
45
39
  hide() {
46
- this.api().hide();
40
+ this.apiDialog().hide();
47
41
  },
48
42
  },
49
43
  };
@@ -11,15 +11,13 @@
11
11
  <div
12
12
  v-show="visible"
13
13
  :id="id"
14
- :class="{
15
- 'Dialog-content': headless,
16
- 'relative m-auto max-h-full max-w-full overflow-auto rounded p-6 focus:outline-none sm:p-10 flex flex-col':
17
- !headless,
18
- 'bg-white': !dark,
19
- 'bg-fd-600': dark,
20
- }"
14
+ :class="[
15
+ headless
16
+ ? 'dialog-content'
17
+ : 'relative m-auto flex max-h-full max-w-full flex-col overflow-auto rounded bg-white p-6 focus:outline-none sm:p-10',
18
+ ]"
21
19
  tabindex="-1"
22
- @keyup.esc="hide"
20
+ @keydown.esc.stop="hide"
23
21
  >
24
22
  <slot></slot>
25
23
  </div>
@@ -28,9 +26,16 @@
28
26
 
29
27
  <script>
30
28
  export default {
31
- name: "VTDialogContent",
29
+ name: 'VTDialogContent',
32
30
 
33
- inject: ["api"],
31
+ inject: ['apiDialog'],
32
+
33
+ props: {
34
+ headless: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
38
+ },
34
39
 
35
40
  data() {
36
41
  return {
@@ -40,20 +45,12 @@ export default {
40
45
 
41
46
  computed: {
42
47
  id() {
43
- return `dialog-content-${this.api().componentId}`;
44
- },
45
-
46
- dark() {
47
- return this.api().isDark;
48
- },
49
-
50
- headless() {
51
- return this.api().isHeadless;
48
+ return `dialog-content-${this.apiDialog().componentId}`;
52
49
  },
53
50
  },
54
51
 
55
52
  mounted() {
56
- this.api().registerContent(this);
53
+ this.apiDialog().registerContent(this);
57
54
  this.show();
58
55
 
59
56
  this.$nextTick(() => this.$el.focus());
@@ -69,7 +66,7 @@ export default {
69
66
  },
70
67
 
71
68
  hideDialog() {
72
- this.api().emit();
69
+ this.apiDialog().emit();
73
70
  },
74
71
  },
75
72
  };
@@ -1,32 +1,21 @@
1
1
  <template>
2
- <component
3
- :is="as"
4
- :class="{ 'Dialog-footer': headless, 'w-full': !headless }"
5
- >
2
+ <component :is="as" :class="[headless ? 'dialog-footer' : 'w-full']">
6
3
  <slot></slot>
7
4
  </component>
8
5
  </template>
9
6
 
10
7
  <script>
11
8
  export default {
12
- name: "VTDialogFooter",
13
-
14
- inject: ["api"],
9
+ name: 'VTDialogFooter',
15
10
 
16
11
  props: {
12
+ headless: {
13
+ type: Boolean,
14
+ default: false,
15
+ },
17
16
  as: {
18
17
  type: String,
19
- default: "footer",
20
- },
21
- },
22
-
23
- computed: {
24
- dark() {
25
- return this.api().isDark;
26
- },
27
-
28
- headless() {
29
- return this.api().isHeadless;
18
+ default: 'footer',
30
19
  },
31
20
  },
32
21
  };