@carbon/vue 3.0.27 → 3.0.30
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/dist/carbon-vue-3.common.js +235 -49
- package/dist/carbon-vue-3.common.js.map +1 -1
- package/dist/carbon-vue-3.css +1 -1
- package/dist/carbon-vue-3.umd.js +235 -49
- package/dist/carbon-vue-3.umd.js.map +1 -1
- package/dist/carbon-vue-3.umd.min.js +4 -4
- package/dist/carbon-vue-3.umd.min.js.map +1 -1
- package/dist/web-types.json +18 -2
- package/package.json +1 -1
- package/src/components/CvDropdown/CvDropdown.vue +51 -7
- package/src/components/CvOverflowMenu/CvOverflowMenu.vue +2 -1
- package/src/components/CvTabs/CvTabs.vue +22 -7
- package/src/components/CvTabs/__tests__/__snapshots__/CvTabs.spec.js.snap +3 -3
- package/src/components/CvUIShell/CvContent.vue +7 -1
- package/src/components/CvUIShell/CvHeaderMenu.vue +2 -1
- package/src/components/CvUIShell/CvHeaderMenuItem.vue +8 -3
- package/src/components/CvUIShell/CvHeaderNav.vue +10 -1
- package/src/components/CvUIShell/CvHeaderPanel.vue +42 -0
- package/src/components/CvUIShell/CvSideNav.vue +36 -0
- package/src/components/CvUIShell/CvUIShell.stories.mdx +19 -15
package/dist/web-types.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"framework": "vue",
|
|
3
3
|
"name": "@carbon/vue",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.29",
|
|
5
5
|
"contributions": {
|
|
6
6
|
"html": {
|
|
7
7
|
"description-markup": "markdown",
|
|
@@ -770,6 +770,14 @@
|
|
|
770
770
|
"type": "string"
|
|
771
771
|
},
|
|
772
772
|
"default": "'main'"
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
"name": "id",
|
|
776
|
+
"value": {
|
|
777
|
+
"kind": "expression",
|
|
778
|
+
"type": "string"
|
|
779
|
+
},
|
|
780
|
+
"default": "'main-content'"
|
|
773
781
|
}
|
|
774
782
|
],
|
|
775
783
|
"slots": [
|
|
@@ -2280,7 +2288,7 @@
|
|
|
2280
2288
|
"kind": "expression",
|
|
2281
2289
|
"type": "string"
|
|
2282
2290
|
},
|
|
2283
|
-
"default": "
|
|
2291
|
+
"default": "'menuitem'"
|
|
2284
2292
|
}
|
|
2285
2293
|
],
|
|
2286
2294
|
"slots": [
|
|
@@ -2320,6 +2328,14 @@
|
|
|
2320
2328
|
"name": "CvHeaderNav",
|
|
2321
2329
|
"description": "",
|
|
2322
2330
|
"attributes": [
|
|
2331
|
+
{
|
|
2332
|
+
"name": "ariaLabel",
|
|
2333
|
+
"value": {
|
|
2334
|
+
"kind": "expression",
|
|
2335
|
+
"type": "string"
|
|
2336
|
+
},
|
|
2337
|
+
"default": "undefined"
|
|
2338
|
+
},
|
|
2323
2339
|
{
|
|
2324
2340
|
"name": "ariaLabelledBy",
|
|
2325
2341
|
"value": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carbon/vue",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.30",
|
|
5
5
|
"description": "A collection of components for the Carbon Design System built using Vue.js",
|
|
6
6
|
"packageManager": "yarn@3.2.0",
|
|
7
7
|
"main": "dist/carbon-vue-3.umd.min.js",
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
`${carbonPrefix}--label`,
|
|
37
37
|
{ [`${carbonPrefix}--label--disabled`]: disabled },
|
|
38
38
|
]"
|
|
39
|
-
>{{ label }}</label
|
|
40
39
|
>
|
|
40
|
+
{{ label }}
|
|
41
|
+
</label>
|
|
41
42
|
|
|
42
43
|
<div
|
|
43
44
|
ref="listBox"
|
|
@@ -110,7 +111,10 @@
|
|
|
110
111
|
ref="dropList"
|
|
111
112
|
:aria-hidden="!open ? 'true' : 'false'"
|
|
112
113
|
:aria-labelledby="`${uid}-label`"
|
|
113
|
-
:class="
|
|
114
|
+
:class="[
|
|
115
|
+
`${carbonPrefix}--list-box__menu`,
|
|
116
|
+
{ 'cv-dropdown__menu--transition-done': transitionDone },
|
|
117
|
+
]"
|
|
114
118
|
role="menu"
|
|
115
119
|
tabindex="-1"
|
|
116
120
|
>
|
|
@@ -268,6 +272,7 @@ provide('dropdown-caption', dataCaption);
|
|
|
268
272
|
const itemsList = ref([]);
|
|
269
273
|
provide('dropdown-items', itemsList);
|
|
270
274
|
const open = ref(false);
|
|
275
|
+
const transitionDone = ref(false);
|
|
271
276
|
const data = reactive({
|
|
272
277
|
isHelper: false,
|
|
273
278
|
isInvalid: undefined,
|
|
@@ -301,6 +306,15 @@ watch(dataValue, () => {
|
|
|
301
306
|
});
|
|
302
307
|
watch(open, () => {
|
|
303
308
|
focusItem.value = '';
|
|
309
|
+
if (open.value) {
|
|
310
|
+
// Wait for the transition to complete before allowing overflow
|
|
311
|
+
transitionDone.value = false;
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
transitionDone.value = true;
|
|
314
|
+
}, 200); // Increase delay to ensure animation completes
|
|
315
|
+
} else {
|
|
316
|
+
transitionDone.value = false;
|
|
317
|
+
}
|
|
304
318
|
});
|
|
305
319
|
const ariaLabeledBy = computed(() => {
|
|
306
320
|
if (props.label) {
|
|
@@ -323,6 +337,13 @@ const wrapperStyleOverride = computed(() => {
|
|
|
323
337
|
});
|
|
324
338
|
const el = ref(null);
|
|
325
339
|
function onClickOut() {
|
|
340
|
+
// Move focus to button if focus is inside dropdown list
|
|
341
|
+
const dropListEl = dropList.value;
|
|
342
|
+
if (dropListEl && dropListEl.contains(document.activeElement)) {
|
|
343
|
+
if (button.value && typeof button.value.focus === 'function') {
|
|
344
|
+
button.value.focus();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
326
347
|
open.value = false;
|
|
327
348
|
}
|
|
328
349
|
onClickOutside(el, onClickOut);
|
|
@@ -372,8 +393,14 @@ function onUp() {
|
|
|
372
393
|
}
|
|
373
394
|
}
|
|
374
395
|
function onEsc() {
|
|
396
|
+
// Move focus to button if focus is inside dropdown list
|
|
397
|
+
const dropListEl = dropList.value;
|
|
398
|
+
if (dropListEl && dropListEl.contains(document.activeElement)) {
|
|
399
|
+
if (button.value && typeof button.value.focus === 'function') {
|
|
400
|
+
button.value.focus();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
375
403
|
open.value = false;
|
|
376
|
-
doFocus();
|
|
377
404
|
}
|
|
378
405
|
const button = ref(null);
|
|
379
406
|
const listBox = ref(null);
|
|
@@ -384,8 +411,10 @@ function onTab(ev) {
|
|
|
384
411
|
open.value = false;
|
|
385
412
|
} else if (ev.target === null || listBox.value?.contains(ev.target)) {
|
|
386
413
|
// list has focus, close and return focus to dropdown
|
|
414
|
+
if (button.value && typeof button.value.focus === 'function') {
|
|
415
|
+
button.value.focus();
|
|
416
|
+
}
|
|
387
417
|
open.value = false;
|
|
388
|
-
doFocus();
|
|
389
418
|
ev.preventDefault();
|
|
390
419
|
}
|
|
391
420
|
}
|
|
@@ -394,9 +423,6 @@ function onClick(ev) {
|
|
|
394
423
|
if (props.disabled) {
|
|
395
424
|
ev.preventDefault();
|
|
396
425
|
} else {
|
|
397
|
-
open.value = !open.value;
|
|
398
|
-
doFocus(); // open or close (Some browsers do not focus on button when clicked)
|
|
399
|
-
|
|
400
426
|
let target = ev.target;
|
|
401
427
|
while (
|
|
402
428
|
!target.classList.contains(`${carbonPrefix}--dropdown-link`) &&
|
|
@@ -408,7 +434,25 @@ function onClick(ev) {
|
|
|
408
434
|
if (target.classList.contains(`${carbonPrefix}--dropdown-link`)) {
|
|
409
435
|
const targetItemEl = target.parentNode;
|
|
410
436
|
dataValue.value = targetItemEl.getAttribute('data-value');
|
|
437
|
+
// Move focus to button before closing
|
|
438
|
+
if (button.value && typeof button.value.focus === 'function') {
|
|
439
|
+
button.value.focus();
|
|
440
|
+
}
|
|
441
|
+
open.value = false;
|
|
442
|
+
} else {
|
|
443
|
+
open.value = !open.value;
|
|
444
|
+
doFocus(); // open or close (Some browsers do not focus on button when clicked)
|
|
411
445
|
}
|
|
412
446
|
}
|
|
413
447
|
}
|
|
414
448
|
</script>
|
|
449
|
+
|
|
450
|
+
<style lang="scss">
|
|
451
|
+
.bx--list-box .bx--list-box__menu {
|
|
452
|
+
overflow-y: hidden !important;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.bx--list-box--expanded .bx--list-box__menu.cv-dropdown__menu--transition-done {
|
|
456
|
+
overflow-y: auto !important;
|
|
457
|
+
}
|
|
458
|
+
</style>
|
|
@@ -229,7 +229,8 @@ function doFocus() {
|
|
|
229
229
|
!(
|
|
230
230
|
tryOn.classList.contains('cv-overflow-menu__before-content') ||
|
|
231
231
|
tryOn.classList.contains('cv-overflow-menu__after-content') ||
|
|
232
|
-
tryOn.tabindex < 0
|
|
232
|
+
tryOn.tabindex < 0 ||
|
|
233
|
+
tryOn.disabled
|
|
233
234
|
)
|
|
234
235
|
) {
|
|
235
236
|
focusOn = tryOn;
|
|
@@ -11,14 +11,16 @@
|
|
|
11
11
|
},
|
|
12
12
|
]"
|
|
13
13
|
role="navigation"
|
|
14
|
-
|
|
14
|
+
:aria-label="attrs['aria-label'] || 'Tabs'"
|
|
15
|
+
v-bind="attrsWithoutAriaLabel"
|
|
15
16
|
@keydown.right.prevent.stop="onRight"
|
|
16
17
|
@keydown.left.prevent.stop="onLeft"
|
|
17
18
|
>
|
|
18
19
|
<button
|
|
19
20
|
ref="elLeftOverflow"
|
|
20
|
-
aria-
|
|
21
|
-
|
|
21
|
+
:aria-label="
|
|
22
|
+
leftOverflowNavButtonHidden ? undefined : 'Scroll tabs left'
|
|
23
|
+
"
|
|
22
24
|
:class="[
|
|
23
25
|
{
|
|
24
26
|
[`${carbonPrefix}--tab--overflow-nav-button`]: horizontalOverflow,
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
leftOverflowNavButtonHidden,
|
|
27
29
|
},
|
|
28
30
|
]"
|
|
29
|
-
tabIndex="-1"
|
|
31
|
+
:tabIndex="leftOverflowNavButtonHidden ? -1 : 0"
|
|
30
32
|
type="button"
|
|
31
33
|
@click.stop.prevent="e => onOverflowClick(e, { direction: -1 })"
|
|
32
34
|
@mousedown.stop.prevent="e => onOverflowMouseDown(e, { direction: -1 })"
|
|
@@ -89,8 +91,9 @@
|
|
|
89
91
|
/>
|
|
90
92
|
<button
|
|
91
93
|
ref="elRightOverflow"
|
|
92
|
-
aria-
|
|
93
|
-
|
|
94
|
+
:aria-label="
|
|
95
|
+
rightOverflowNavButtonHidden ? undefined : 'Scroll tabs right'
|
|
96
|
+
"
|
|
94
97
|
:class="[
|
|
95
98
|
{
|
|
96
99
|
[`${carbonPrefix}--tab--overflow-nav-button`]: horizontalOverflow,
|
|
@@ -98,7 +101,7 @@
|
|
|
98
101
|
rightOverflowNavButtonHidden,
|
|
99
102
|
},
|
|
100
103
|
]"
|
|
101
|
-
tabIndex="-1"
|
|
104
|
+
:tabIndex="rightOverflowNavButtonHidden ? -1 : 0"
|
|
102
105
|
type="button"
|
|
103
106
|
@click="e => onOverflowClick(e, { direction: 1 })"
|
|
104
107
|
@mousedown="e => onOverflowMouseDown(e, { direction: 1 })"
|
|
@@ -117,15 +120,21 @@
|
|
|
117
120
|
import { carbonPrefix } from '../../global/settings';
|
|
118
121
|
import { ChevronLeft16, ChevronRight16 } from '@carbon/icons-vue';
|
|
119
122
|
import {
|
|
123
|
+
computed,
|
|
120
124
|
nextTick,
|
|
121
125
|
onBeforeUnmount,
|
|
122
126
|
onMounted,
|
|
123
127
|
onUpdated,
|
|
124
128
|
provide,
|
|
125
129
|
ref,
|
|
130
|
+
useAttrs,
|
|
126
131
|
watch,
|
|
127
132
|
} from 'vue';
|
|
128
133
|
|
|
134
|
+
defineOptions({
|
|
135
|
+
inheritAttrs: false,
|
|
136
|
+
});
|
|
137
|
+
|
|
129
138
|
const OVERFLOW_BUTTON_OFFSET = 40;
|
|
130
139
|
|
|
131
140
|
const props = defineProps({
|
|
@@ -152,6 +161,12 @@ const props = defineProps({
|
|
|
152
161
|
});
|
|
153
162
|
const emit = defineEmits(['tab-selected', 'tab-selected-id']);
|
|
154
163
|
|
|
164
|
+
const attrs = useAttrs();
|
|
165
|
+
const attrsWithoutAriaLabel = computed(() => {
|
|
166
|
+
const { 'aria-label': _, ...rest } = attrs;
|
|
167
|
+
return rest;
|
|
168
|
+
});
|
|
169
|
+
|
|
155
170
|
const selectedId = ref('');
|
|
156
171
|
provide('tab-selected', selectedId);
|
|
157
172
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
3
|
exports[`CvTabs CvTabs - test slotted tab buttons to match snapshot 1`] = `
|
|
4
|
-
<div class="cv-tabs
|
|
5
|
-
<div data-tabs="" class="cv-tab bx--tabs--scrollable ABC-class-123" role="navigation" aria-label="ABC-aria-label-123"><button
|
|
4
|
+
<div class="cv-tabs" style="width: 100%;">
|
|
5
|
+
<div data-tabs="" class="cv-tab bx--tabs--scrollable ABC-class-123" role="navigation" aria-label="ABC-aria-label-123"><button class="bx--tab--overflow-nav-button--hidden" tabindex="-1" type="button"><svg focusable="false" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
|
|
6
6
|
<path d="M5 8L10 3 10.7 3.7 6.4 8 10.7 12.3 10 13z"></path>
|
|
7
7
|
</svg></button>
|
|
8
8
|
<!--v-if-->
|
|
@@ -11,7 +11,7 @@ exports[`CvTabs CvTabs - test slotted tab buttons to match snapshot 1`] = `
|
|
|
11
11
|
<li class="cv-tabs-button bx--tabs--scrollable__nav-item" role="presentation"><button id="tab-2-link" class="bx--tabs--scrollable__nav-link" role="tab" aria-controls="tab-2" aria-selected="false" tabindex="-1" type="button">Origin<strong style="color: orange;">(!)</strong></button></li>
|
|
12
12
|
<li class="cv-tabs-button bx--tabs--scrollable__nav-item" role="presentation"><button id="tab-3-link" class="bx--tabs--scrollable__nav-link" role="tab" aria-controls="tab-3" aria-selected="false" tabindex="-1" type="button">Tab 3 label</button></li>
|
|
13
13
|
</ul>
|
|
14
|
-
<!--v-if--><button
|
|
14
|
+
<!--v-if--><button class="bx--tab--overflow-nav-button--hidden" tabindex="-1" type="button"><svg focusable="false" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
|
|
15
15
|
<path d="M11 8L6 13 5.3 12.3 9.6 8 5.3 3.7 6 3z"></path>
|
|
16
16
|
</svg></button>
|
|
17
17
|
</div>
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
import { carbonPrefix } from '../../global/settings';
|
|
3
3
|
defineProps({
|
|
4
4
|
tagType: { type: String, default: 'main' },
|
|
5
|
+
id: { type: String, default: 'main-content' },
|
|
5
6
|
});
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<template>
|
|
9
|
-
<component
|
|
10
|
+
<component
|
|
11
|
+
:is="tagType"
|
|
12
|
+
:id="id"
|
|
13
|
+
:class="`${carbonPrefix}--content`"
|
|
14
|
+
tabindex="-1"
|
|
15
|
+
>
|
|
10
16
|
<slot />
|
|
11
17
|
</component>
|
|
12
18
|
</template>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<li
|
|
3
3
|
ref="el"
|
|
4
4
|
:class="`cv-header-menu ${carbonPrefix}--header__submenu`"
|
|
5
|
+
role="menuitem"
|
|
5
6
|
@mouseenter="doHoverToggle(true)"
|
|
6
7
|
@mouseleave="doHoverToggle(false)"
|
|
7
8
|
>
|
|
@@ -10,9 +11,9 @@
|
|
|
10
11
|
:aria-expanded="data.expanded ? 'true' : 'false'"
|
|
11
12
|
:class="`${carbonPrefix}--header__menu-item ${carbonPrefix}--header__menu-title`"
|
|
12
13
|
href="javascript:void(0)"
|
|
13
|
-
role="menuitem"
|
|
14
14
|
tabindex="0"
|
|
15
15
|
:aria-label="$attrs.ariaLabel"
|
|
16
|
+
role="button"
|
|
16
17
|
@click="doToggle"
|
|
17
18
|
@keydown.space.prevent
|
|
18
19
|
@keyup.space.prevent="doToggle"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<li class="cv-header-menu-item"
|
|
2
|
+
<li class="cv-header-menu-item" role="none">
|
|
3
3
|
<component
|
|
4
4
|
:is="tagType"
|
|
5
5
|
v-bind="{ ...$attrs, ...linkProps }"
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
`${carbonPrefix}--header__menu-item`,
|
|
8
8
|
{ [`${carbonPrefix}--header__menu-item--current`]: activePage },
|
|
9
9
|
]"
|
|
10
|
-
role="menuitem"
|
|
11
10
|
:aria-current="ariaCurrent"
|
|
11
|
+
:role="role"
|
|
12
|
+
:tabindex="tabindex"
|
|
12
13
|
>
|
|
13
14
|
<span :class="`${carbonPrefix}--text-truncate--end`">
|
|
14
15
|
<slot />
|
|
@@ -25,7 +26,7 @@ import { computed } from 'vue';
|
|
|
25
26
|
const props = defineProps({
|
|
26
27
|
active: Boolean,
|
|
27
28
|
ariaCurrent: { type: String, default: undefined },
|
|
28
|
-
role: { type: String, default:
|
|
29
|
+
role: { type: String, default: 'menuitem' },
|
|
29
30
|
...propsLink,
|
|
30
31
|
});
|
|
31
32
|
|
|
@@ -35,4 +36,8 @@ const linkProps = useLinkProps(props);
|
|
|
35
36
|
const activePage = computed(() => {
|
|
36
37
|
return props.active && props.ariaCurrent !== 'page';
|
|
37
38
|
});
|
|
39
|
+
|
|
40
|
+
const tabindex = computed(() => {
|
|
41
|
+
return props.role === 'menuitem' ? '0' : undefined;
|
|
42
|
+
});
|
|
38
43
|
</script>
|
|
@@ -3,16 +3,25 @@
|
|
|
3
3
|
<ul
|
|
4
4
|
:class="`${carbonPrefix}--header__menu-bar`"
|
|
5
5
|
role="menubar"
|
|
6
|
-
|
|
6
|
+
aria-orientation="horizontal"
|
|
7
|
+
:aria-label="ariaLabel || $attrs['aria-label']"
|
|
8
|
+
:aria-labelledby="ariaLabelledBy || $attrs['aria-labelledby']"
|
|
7
9
|
>
|
|
8
10
|
<slot />
|
|
9
11
|
</ul>
|
|
10
12
|
</nav>
|
|
11
13
|
</template>
|
|
12
14
|
|
|
15
|
+
<script>
|
|
16
|
+
export default {
|
|
17
|
+
inheritAttrs: false,
|
|
18
|
+
};
|
|
19
|
+
</script>
|
|
20
|
+
|
|
13
21
|
<script setup>
|
|
14
22
|
import { carbonPrefix } from '../../global/settings';
|
|
15
23
|
defineProps({
|
|
24
|
+
ariaLabel: { type: String, default: undefined },
|
|
16
25
|
ariaLabelledBy: { type: String, default: undefined },
|
|
17
26
|
});
|
|
18
27
|
</script>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
{ [`${carbonPrefix}--header-panel--expanded`]: panelExpanded },
|
|
9
9
|
]"
|
|
10
10
|
:aria-hidden="!panelExpanded ? 'true' : 'false'"
|
|
11
|
+
:inert="!panelExpanded"
|
|
11
12
|
@focusout="onFocusout"
|
|
12
13
|
@mousedown="onMouseDown"
|
|
13
14
|
>
|
|
@@ -20,6 +21,7 @@ import { carbonPrefix } from '../../global/settings';
|
|
|
20
21
|
import {
|
|
21
22
|
computed,
|
|
22
23
|
inject,
|
|
24
|
+
nextTick,
|
|
23
25
|
onBeforeUnmount,
|
|
24
26
|
onMounted,
|
|
25
27
|
reactive,
|
|
@@ -99,9 +101,49 @@ watch(
|
|
|
99
101
|
}
|
|
100
102
|
);
|
|
101
103
|
|
|
104
|
+
function manageFocusableElements(isExpanded) {
|
|
105
|
+
if (!el.value) return;
|
|
106
|
+
|
|
107
|
+
const focusableElements = el.value.querySelectorAll(
|
|
108
|
+
'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
focusableElements.forEach(element => {
|
|
112
|
+
if (isExpanded) {
|
|
113
|
+
// Restore original tabindex when expanded
|
|
114
|
+
const originalTabindex = element.getAttribute('data-original-tabindex');
|
|
115
|
+
if (originalTabindex !== null) {
|
|
116
|
+
if (originalTabindex === 'null') {
|
|
117
|
+
element.removeAttribute('tabindex');
|
|
118
|
+
} else {
|
|
119
|
+
element.setAttribute('tabindex', originalTabindex);
|
|
120
|
+
}
|
|
121
|
+
element.removeAttribute('data-original-tabindex');
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Store original tabindex and set to -1 when collapsed
|
|
125
|
+
const currentTabindex = element.getAttribute('tabindex');
|
|
126
|
+
element.setAttribute('data-original-tabindex', currentTabindex || 'null');
|
|
127
|
+
element.setAttribute('tabindex', '-1');
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
102
132
|
const emit = defineEmits(['update:expanded', 'panel-resize']);
|
|
103
133
|
watch(panelExpanded, current => {
|
|
104
134
|
emit('update:expanded', current);
|
|
105
135
|
emit('panel-resize', { id: props.id, expanded: current });
|
|
136
|
+
|
|
137
|
+
// Manage focusable elements
|
|
138
|
+
nextTick(() => {
|
|
139
|
+
manageFocusableElements(current);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Initial setup of focusable elements
|
|
144
|
+
onMounted(() => {
|
|
145
|
+
nextTick(() => {
|
|
146
|
+
manageFocusableElements(panelExpanded.value);
|
|
147
|
+
});
|
|
106
148
|
});
|
|
107
149
|
</script>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
]"
|
|
16
16
|
:aria-hidden="!panelExpanded && !fixed ? 'true' : 'false'"
|
|
17
|
+
:inert="!panelExpanded && !fixed"
|
|
17
18
|
@focusout="onFocusout"
|
|
18
19
|
@mousedown="onMouseDown"
|
|
19
20
|
@mouseenter="onHoverToggle(true)"
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
import {
|
|
34
35
|
computed,
|
|
35
36
|
inject,
|
|
37
|
+
nextTick,
|
|
36
38
|
onBeforeUnmount,
|
|
37
39
|
onMounted,
|
|
38
40
|
reactive,
|
|
@@ -110,6 +112,35 @@ watch(
|
|
|
110
112
|
panelExpanded.value = props.expanded;
|
|
111
113
|
}
|
|
112
114
|
);
|
|
115
|
+
// Manage focusable elements when panel visibility changes
|
|
116
|
+
function manageFocusableElements(isExpanded, isFixed) {
|
|
117
|
+
if (!el.value || isFixed) return;
|
|
118
|
+
|
|
119
|
+
const focusableElements = el.value.querySelectorAll(
|
|
120
|
+
'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
focusableElements.forEach(element => {
|
|
124
|
+
if (isExpanded) {
|
|
125
|
+
// Restore original tabindex when expanded
|
|
126
|
+
const originalTabindex = element.getAttribute('data-original-tabindex');
|
|
127
|
+
if (originalTabindex !== null) {
|
|
128
|
+
if (originalTabindex === 'null') {
|
|
129
|
+
element.removeAttribute('tabindex');
|
|
130
|
+
} else {
|
|
131
|
+
element.setAttribute('tabindex', originalTabindex);
|
|
132
|
+
}
|
|
133
|
+
element.removeAttribute('data-original-tabindex');
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
// Store original tabindex and set to -1 when collapsed
|
|
137
|
+
const currentTabindex = element.getAttribute('tabindex');
|
|
138
|
+
element.setAttribute('data-original-tabindex', currentTabindex || 'null');
|
|
139
|
+
element.setAttribute('tabindex', '-1');
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
113
144
|
const emit = defineEmits(['update:expanded', 'panel-resize']);
|
|
114
145
|
const isPanelExpanded = computed(
|
|
115
146
|
() => panelExpanded.value || expandedViaHoverState.value
|
|
@@ -117,6 +148,11 @@ const isPanelExpanded = computed(
|
|
|
117
148
|
watch(isPanelExpanded, current => {
|
|
118
149
|
emit('update:expanded', current);
|
|
119
150
|
emit('panel-resize', { id: props.id, expanded: current });
|
|
151
|
+
|
|
152
|
+
// Manage focusable elements for non-fixed nav
|
|
153
|
+
nextTick(() => {
|
|
154
|
+
manageFocusableElements(current, props.fixed);
|
|
155
|
+
});
|
|
120
156
|
});
|
|
121
157
|
|
|
122
158
|
const isChildOfHeader = computed(() => {
|
|
@@ -19,6 +19,7 @@ import CvSideNavLink from './CvSideNavLink.vue';
|
|
|
19
19
|
import CvSideNavMenu from './CvSideNavMenu.vue';
|
|
20
20
|
import CvSideNavMenuDivider from './CvSideNavMenuDivider.vue';
|
|
21
21
|
import CvSideNavMenuItem from './CvSideNavMenuItem.vue';
|
|
22
|
+
import CvContent from './CvContent.vue';
|
|
22
23
|
import {
|
|
23
24
|
Notification20,
|
|
24
25
|
UserAvatar20,
|
|
@@ -55,6 +56,7 @@ export const Template = args => ({
|
|
|
55
56
|
CvSideNavMenu,
|
|
56
57
|
CvSideNavMenuDivider,
|
|
57
58
|
CvSideNavMenuItem,
|
|
59
|
+
CvContent,
|
|
58
60
|
Notification20,
|
|
59
61
|
UserAvatar20,
|
|
60
62
|
Login20,
|
|
@@ -89,22 +91,12 @@ const defaultTemplate = `
|
|
|
89
91
|
[Platform]
|
|
90
92
|
</cv-header-name>
|
|
91
93
|
<cv-header-nav aria-label="Carbon nav">
|
|
92
|
-
<cv-header-menu-item href="javascript:void(0)">
|
|
93
|
-
|
|
94
|
-
</cv-header-menu-item>
|
|
95
|
-
<cv-header-menu-item href="javascript:void(0)">
|
|
96
|
-
Link 2
|
|
97
|
-
</cv-header-menu-item>
|
|
94
|
+
<cv-header-menu-item href="javascript:void(0)">Link 1</cv-header-menu-item>
|
|
95
|
+
<cv-header-menu-item href="javascript:void(0)">Link 2</cv-header-menu-item>
|
|
98
96
|
<cv-header-menu aria-label="Link 3" title="Link 3">
|
|
99
|
-
<cv-header-menu-item href="javascript:void(0)">
|
|
100
|
-
|
|
101
|
-
</cv-header-menu-item>
|
|
102
|
-
<cv-header-menu-item href="javascript:void(0)">
|
|
103
|
-
Submenu Link 2
|
|
104
|
-
</cv-header-menu-item>
|
|
105
|
-
<cv-header-menu-item href="javascript:void(0)">
|
|
106
|
-
Submenu Link 3
|
|
107
|
-
</cv-header-menu-item>
|
|
97
|
+
<cv-header-menu-item href="javascript:void(0)">Submenu Link 1</cv-header-menu-item>
|
|
98
|
+
<cv-header-menu-item href="javascript:void(0)">Submenu Link 2</cv-header-menu-item>
|
|
99
|
+
<cv-header-menu-item href="javascript:void(0)">Submenu Link 3</cv-header-menu-item>
|
|
108
100
|
</cv-header-menu>
|
|
109
101
|
</cv-header-nav>
|
|
110
102
|
<template v-slot:header-global>
|
|
@@ -162,6 +154,10 @@ const defaultTemplate = `
|
|
|
162
154
|
</cv-header-panel>
|
|
163
155
|
</template>
|
|
164
156
|
</cv-header>
|
|
157
|
+
<cv-content>
|
|
158
|
+
<h1>Main Content</h1>
|
|
159
|
+
<p>This is the main content area that the skip link targets.</p>
|
|
160
|
+
</cv-content>
|
|
165
161
|
`;
|
|
166
162
|
const railTemplate = `
|
|
167
163
|
<cv-header aria-label="Carbon header">
|
|
@@ -196,6 +192,10 @@ const railTemplate = `
|
|
|
196
192
|
</cv-side-nav>
|
|
197
193
|
</template>
|
|
198
194
|
</cv-header>
|
|
195
|
+
<cv-content>
|
|
196
|
+
<h1>Main Content</h1>
|
|
197
|
+
<p>This is the main content area that the skip link targets.</p>
|
|
198
|
+
</cv-content>
|
|
199
199
|
`;
|
|
200
200
|
const fixedSideNavTemplate = `
|
|
201
201
|
<cv-header aria-label="Carbon header">
|
|
@@ -231,6 +231,10 @@ const fixedSideNavTemplate = `
|
|
|
231
231
|
</cv-side-nav-link>
|
|
232
232
|
</cv-side-nav-items>
|
|
233
233
|
</cv-side-nav>
|
|
234
|
+
<cv-content>
|
|
235
|
+
<h1>Main Content</h1>
|
|
236
|
+
<p>This is the main content area that the skip link targets.</p>
|
|
237
|
+
</cv-content>
|
|
234
238
|
`;
|
|
235
239
|
|
|
236
240
|
# UI Shell components
|