@rancher/shell 3.0.12-rc.6 → 3.0.12-rc.7
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/apis/intf/shell-api/slide-in.ts +46 -17
- package/apis/shell/__tests__/slide-in.test.ts +83 -2
- package/apis/shell/slide-in.ts +20 -0
- package/components/ExplorerProjectsNamespaces.vue +2 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +9 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -1
- package/components/SlideInPanelManager.vue +103 -25
- package/components/__tests__/SlideInPanelManager.spec.ts +153 -10
- package/components/auth/AuthBanner.vue +1 -1
- package/core/plugins-loader.js +2 -0
- package/models/__tests__/namespace.test.ts +133 -8
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +57 -1
- package/models/namespace.js +34 -2
- package/models/provisioning.cattle.io.cluster.js +20 -13
- package/package.json +1 -1
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +136 -1
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +69 -1
- package/pages/c/_cluster/fleet/index.vue +5 -9
- package/pkg/vue.config.js +4 -3
- package/plugins/codemirror.js +2 -0
- package/plugins/dashboard-store/resource-class.js +3 -6
- package/store/__tests__/action-menu.test.ts +622 -0
- package/store/__tests__/slideInPanel.test.ts +143 -43
- package/store/__tests__/wm.test.ts +503 -0
- package/store/aws.js +19 -4
- package/store/slideInPanel.ts +15 -3
- package/types/shell/index.d.ts +26 -2
- package/utils/__tests__/fleet-appco.test.ts +23 -0
- package/utils/fleet-appco.ts +6 -1
- package/utils/sort.js +1 -1
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { Component } from 'vue';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Standard width presets for the Slide-In panel.
|
|
5
|
+
*
|
|
6
|
+
* - `'default'` — 33% of the viewport width
|
|
7
|
+
* - `'wide'` — 73% of the viewport width
|
|
8
|
+
*/
|
|
9
|
+
export type SlideInWidth = 'default' | 'wide';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard height presets for the Slide-In panel.
|
|
13
|
+
*
|
|
14
|
+
* - `'default'` — positioned below the header bar, filling the remaining viewport height
|
|
15
|
+
* - `'full'` — full viewport height (100vh), covering the header and side menu
|
|
16
|
+
*/
|
|
17
|
+
export type SlideInHeight = 'default' | 'full';
|
|
18
|
+
|
|
3
19
|
/**
|
|
4
20
|
*
|
|
5
21
|
* Configuration object for opening a Slide-In panel. Here's what a Slide-In looks like in Rancher UI:
|
|
@@ -8,45 +24,50 @@ import { Component } from 'vue';
|
|
|
8
24
|
export interface SlideInConfig {
|
|
9
25
|
/**
|
|
10
26
|
*
|
|
11
|
-
*
|
|
27
|
+
* title for the Slide-In panel.
|
|
28
|
+
* When set, a header bar with the title and a close button is displayed.
|
|
29
|
+
* When omitted, no header is shown.
|
|
12
30
|
*
|
|
13
31
|
*/
|
|
14
|
-
|
|
32
|
+
title?: string;
|
|
15
33
|
/**
|
|
16
34
|
*
|
|
17
|
-
*
|
|
18
|
-
* Can be set as `33%` or `80vh`
|
|
19
|
-
*
|
|
20
|
-
*/
|
|
21
|
-
height?: string;
|
|
22
|
-
/**
|
|
35
|
+
* Width preset for the Slide-In panel. Defaults to `'default'` (33%).
|
|
23
36
|
*
|
|
24
|
-
*
|
|
37
|
+
* - `'default'` — 33% of the viewport width
|
|
38
|
+
* - `'wide'` — 73% of the viewport width
|
|
25
39
|
*
|
|
26
40
|
*/
|
|
27
|
-
|
|
41
|
+
width?: SlideInWidth;
|
|
28
42
|
/**
|
|
29
43
|
*
|
|
30
|
-
*
|
|
44
|
+
* Height preset for the Slide-In panel. Defaults to `'default'` (below header).
|
|
45
|
+
* When set to `'full'`, the panel covers the full viewport height and the z-index
|
|
46
|
+
* is automatically elevated above the side menu.
|
|
47
|
+
*
|
|
48
|
+
* - `'default'` — positioned below the header bar, filling the remaining viewport height
|
|
49
|
+
* - `'full'` — full viewport height (100vh), covering the header and side menu
|
|
31
50
|
*
|
|
32
51
|
*/
|
|
33
|
-
|
|
52
|
+
height?: SlideInHeight;
|
|
34
53
|
/**
|
|
35
54
|
*
|
|
36
|
-
*
|
|
55
|
+
* When `true`, disables the focus trap on the Slide-In panel.
|
|
56
|
+
* Useful for panels with no focusable elements or custom focus management.
|
|
37
57
|
*
|
|
38
58
|
*/
|
|
39
|
-
|
|
59
|
+
disableFocusTrap?: boolean;
|
|
40
60
|
/**
|
|
41
61
|
*
|
|
42
|
-
* Array of
|
|
62
|
+
* Array of route properties to watch. When any of them change, the Slide-In closes.
|
|
63
|
+
* Defaults to `['name', 'params', 'hash', 'query']`.
|
|
43
64
|
* @ignore
|
|
44
65
|
*
|
|
45
66
|
*/
|
|
46
|
-
closeOnRouteChange?: [
|
|
67
|
+
closeOnRouteChange?: string[];
|
|
47
68
|
/**
|
|
48
69
|
*
|
|
49
|
-
*
|
|
70
|
+
* CSS selector for the element that should receive focus when the Slide-In closes.
|
|
50
71
|
* @ignore
|
|
51
72
|
*
|
|
52
73
|
*/
|
|
@@ -68,6 +89,14 @@ export interface SlideInConfig {
|
|
|
68
89
|
props?: {
|
|
69
90
|
[key: string]: any;
|
|
70
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* @deprecated No longer needed — automatically managed based on `panelHeight`. Full deprecation expected in Rancher 2.17.
|
|
94
|
+
*/
|
|
95
|
+
top?: string;
|
|
96
|
+
/**
|
|
97
|
+
* @deprecated No longer needed — header visibility is inferred from the presence of `title`. Full deprecation expected in Rancher 2.17.
|
|
98
|
+
*/
|
|
99
|
+
showHeader?: boolean;
|
|
71
100
|
}
|
|
72
101
|
|
|
73
102
|
/**
|
|
@@ -38,7 +38,7 @@ describe('slideInApiImpl', () => {
|
|
|
38
38
|
it('should open a slide-in panel with a config', () => {
|
|
39
39
|
const config = {
|
|
40
40
|
title: 'Test Panel',
|
|
41
|
-
width: '
|
|
41
|
+
width: 'default' as const,
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// 3. Act
|
|
@@ -48,7 +48,7 @@ describe('slideInApiImpl', () => {
|
|
|
48
48
|
expect(mockCommit).toHaveBeenCalledTimes(1);
|
|
49
49
|
expect(mockCommit).toHaveBeenCalledWith('slideInPanel/open', {
|
|
50
50
|
component: MockComponent,
|
|
51
|
-
componentProps: {
|
|
51
|
+
componentProps: { title: 'Test Panel', width: 'default' },
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -87,4 +87,85 @@ describe('slideInApiImpl', () => {
|
|
|
87
87
|
},
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
it('should open with preset properties', () => {
|
|
92
|
+
const config = {
|
|
93
|
+
title: 'Preset Panel',
|
|
94
|
+
width: 'wide' as const,
|
|
95
|
+
height: 'full' as const,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
slideInApi.open(MockComponent, config);
|
|
99
|
+
|
|
100
|
+
expect(mockCommit).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(mockCommit).toHaveBeenCalledWith('slideInPanel/open', {
|
|
102
|
+
component: MockComponent,
|
|
103
|
+
componentProps: {
|
|
104
|
+
title: 'Preset Panel',
|
|
105
|
+
width: 'wide',
|
|
106
|
+
height: 'full',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should open with disableFocusTrap option', () => {
|
|
112
|
+
const config = {
|
|
113
|
+
width: 'wide' as const,
|
|
114
|
+
height: 'full' as const,
|
|
115
|
+
disableFocusTrap: true,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
slideInApi.open(MockComponent, config);
|
|
119
|
+
|
|
120
|
+
expect(mockCommit).toHaveBeenCalledTimes(1);
|
|
121
|
+
expect(mockCommit).toHaveBeenCalledWith('slideInPanel/open', {
|
|
122
|
+
component: MockComponent,
|
|
123
|
+
componentProps: {
|
|
124
|
+
width: 'wide',
|
|
125
|
+
height: 'full',
|
|
126
|
+
disableFocusTrap: true,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('deprecation warnings', () => {
|
|
132
|
+
const originalEnv = process.env.dev;
|
|
133
|
+
let warnSpy: jest.SpyInstance;
|
|
134
|
+
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
process.env.dev = 'true';
|
|
137
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
afterEach(() => {
|
|
141
|
+
process.env.dev = originalEnv;
|
|
142
|
+
warnSpy.mockRestore();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('warns when deprecated "top" is used', () => {
|
|
146
|
+
slideInApi.open(MockComponent, { top: '0' });
|
|
147
|
+
|
|
148
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
149
|
+
expect.stringContaining('"top" is deprecated')
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('warns when deprecated "showHeader" is used', () => {
|
|
154
|
+
slideInApi.open(MockComponent, { showHeader: false });
|
|
155
|
+
|
|
156
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
157
|
+
expect.stringContaining('"showHeader" is deprecated')
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('does not warn when using preset properties', () => {
|
|
162
|
+
slideInApi.open(MockComponent, {
|
|
163
|
+
title: 'Test',
|
|
164
|
+
width: 'wide' as const,
|
|
165
|
+
height: 'full' as const,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
90
171
|
});
|
package/apis/shell/slide-in.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { Component } from 'vue';
|
|
|
2
2
|
import { SlideInApi, SlideInConfig } from '@shell/apis/intf/shell';
|
|
3
3
|
import { Store } from 'vuex';
|
|
4
4
|
|
|
5
|
+
const DEPRECATION_PREFIX = '[SlideIn API]';
|
|
6
|
+
|
|
5
7
|
export class SlideInApiImpl implements SlideInApi {
|
|
6
8
|
private store: Store<any>;
|
|
7
9
|
|
|
@@ -9,6 +11,20 @@ export class SlideInApiImpl implements SlideInApi {
|
|
|
9
11
|
this.store = store;
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
private warnDeprecated(config: SlideInConfig): void {
|
|
15
|
+
if (!process.env.dev) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (config.top !== undefined) {
|
|
20
|
+
console.warn(`${ DEPRECATION_PREFIX } "top" is deprecated. It is now automatically managed based on "panelHeight". Full deprecation expected in Rancher 2.17.`); // eslint-disable-line no-console
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (config.showHeader !== undefined) {
|
|
24
|
+
console.warn(`${ DEPRECATION_PREFIX } "showHeader" is deprecated. Header visibility is now inferred from the presence of "title". Full deprecation expected in Rancher 2.17.`); // eslint-disable-line no-console
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
/**
|
|
13
29
|
* Opens a slide in panel in Rancher UI
|
|
14
30
|
*
|
|
@@ -30,6 +46,10 @@ export class SlideInApiImpl implements SlideInApi {
|
|
|
30
46
|
public open(component: Component, config?: SlideInConfig): void {
|
|
31
47
|
const props = config?.props || {};
|
|
32
48
|
|
|
49
|
+
if (config) {
|
|
50
|
+
this.warnDeprecated(config);
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
delete config?.props;
|
|
34
54
|
|
|
35
55
|
this.store.commit('slideInPanel/open', {
|
|
@@ -157,7 +157,7 @@ export default {
|
|
|
157
157
|
return uniq(ids);
|
|
158
158
|
},
|
|
159
159
|
clusterProjects() {
|
|
160
|
-
const clusterId = this.currentCluster
|
|
160
|
+
const clusterId = this.currentCluster?.id;
|
|
161
161
|
|
|
162
162
|
// Get the list of projects from the store so that the list
|
|
163
163
|
// is updated if a new project is created or removed.
|
|
@@ -276,7 +276,7 @@ export default {
|
|
|
276
276
|
},
|
|
277
277
|
|
|
278
278
|
canSeeProjectlessNamespaces() {
|
|
279
|
-
return this.currentCluster
|
|
279
|
+
return this.currentCluster?.canUpdate;
|
|
280
280
|
},
|
|
281
281
|
|
|
282
282
|
showMockNotInProjectGroup() {
|
|
@@ -59,6 +59,15 @@ describe('composables: IdentifyingFields', () => {
|
|
|
59
59
|
expect(result?.value.label).toStrictEqual('component.resource.detail.metadata.identifyingInformation.namespace');
|
|
60
60
|
expect(result?.value.valueDataTestid).toStrictEqual('masthead-subheader-namespace');
|
|
61
61
|
});
|
|
62
|
+
|
|
63
|
+
it('should return a plain text namespace row when the resource signals namespaceLocation is unreachable', () => {
|
|
64
|
+
mockStore.getters['cluster/canList'] = () => true;
|
|
65
|
+
const resource = { namespace: 'NAMESPACE', namespaceLocation: null };
|
|
66
|
+
const result = useNamespace(resource);
|
|
67
|
+
|
|
68
|
+
expect(result?.value.valueOverride).toBeUndefined();
|
|
69
|
+
expect(result?.value.value).toStrictEqual(resource.namespace);
|
|
70
|
+
});
|
|
62
71
|
});
|
|
63
72
|
|
|
64
73
|
describe('useWorkspace', () => {
|
|
@@ -27,10 +27,14 @@ export const useNamespace = (resource: any): ComputedRef<Row> | undefined => {
|
|
|
27
27
|
const currentStore = store.getters['currentStore'](NAMESPACE);
|
|
28
28
|
const canList = store.getters[`${ currentStore }/canList`](NAMESPACE);
|
|
29
29
|
|
|
30
|
+
// A resource that explicitly returns null from namespaceLocation is signaling that
|
|
31
|
+
// the namespace lives in a cluster the user can't reach (mirrors legacy.vue's hideNamespaceLocation)
|
|
32
|
+
const hasReachableLocation = resourceValue.namespaceLocation !== null;
|
|
33
|
+
|
|
30
34
|
const label = i18n.t('component.resource.detail.metadata.identifyingInformation.namespace');
|
|
31
35
|
const value = resourceValue.namespace;
|
|
32
36
|
const valueDataTestid = 'masthead-subheader-namespace';
|
|
33
|
-
const valueOverride = canList ? {
|
|
37
|
+
const valueOverride = canList && hasReachableLocation ? {
|
|
34
38
|
component: markRaw(defineAsyncComponent(() => import('@shell/components/Resource/Detail/ResourcePopover/index.vue'))),
|
|
35
39
|
props: {
|
|
36
40
|
type: NAMESPACE,
|
|
@@ -10,6 +10,13 @@ import { useRouter } from 'vue-router';
|
|
|
10
10
|
|
|
11
11
|
const HEADER_HEIGHT = 55;
|
|
12
12
|
|
|
13
|
+
const WIDTH_MAP = {
|
|
14
|
+
default: '33%',
|
|
15
|
+
wide: '73%'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const HEIGHT_FULL = 'full';
|
|
19
|
+
|
|
13
20
|
const slideInPanelManager = useTemplateRef('SlideInPanelManager');
|
|
14
21
|
const slideInPanelManagerClose = useTemplateRef('SlideInPanelManagerClose');
|
|
15
22
|
|
|
@@ -19,12 +26,22 @@ const isClosing = computed(() => store.getters['slideInPanel/isClosing']);
|
|
|
19
26
|
const currentComponent = computed(() => store.getters['slideInPanel/component']);
|
|
20
27
|
const currentProps = computed(() => store.getters['slideInPanel/componentProps']);
|
|
21
28
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
const resolvedHeightMode = computed(() => {
|
|
30
|
+
if (currentProps.value?.height) {
|
|
31
|
+
return currentProps.value.height;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Deprecated: infer from raw top value
|
|
35
|
+
if (currentProps.value?.top === '0') {
|
|
36
|
+
return 'full';
|
|
26
37
|
}
|
|
27
38
|
|
|
39
|
+
return 'default';
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const isFullHeight = computed(() => resolvedHeightMode.value === HEIGHT_FULL);
|
|
43
|
+
|
|
44
|
+
const defaultTop = computed(() => {
|
|
28
45
|
const banner = document.getElementById('banner-header');
|
|
29
46
|
let height = HEADER_HEIGHT;
|
|
30
47
|
|
|
@@ -35,15 +52,51 @@ const panelTop = computed(() => {
|
|
|
35
52
|
return `${ height }px`;
|
|
36
53
|
});
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
const panelTop = computed(() => {
|
|
56
|
+
if (isFullHeight.value) {
|
|
57
|
+
return '0';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Deprecated: explicit top value
|
|
61
|
+
if (currentProps.value?.top) {
|
|
62
|
+
return currentProps.value.top;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return defaultTop.value;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const panelHeight = computed(() => {
|
|
69
|
+
if (isFullHeight.value) {
|
|
70
|
+
return '100vh';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return `calc(100vh - ${ panelTop.value })`;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const panelWidth = computed(() => {
|
|
77
|
+
const width = currentProps.value?.width as keyof typeof WIDTH_MAP | undefined;
|
|
78
|
+
|
|
79
|
+
return (width && WIDTH_MAP[width]) || WIDTH_MAP.default;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const panelRight = computed(() => (isOpen.value ? '0' : `-${ panelWidth.value }`));
|
|
83
|
+
|
|
84
|
+
const glassZIndex = computed(() => (isFullHeight.value ? 101 : undefined));
|
|
85
|
+
const panelZIndex = computed(() => (isFullHeight.value ? 102 : undefined));
|
|
86
|
+
|
|
87
|
+
const showHeader = computed(() => {
|
|
88
|
+
// Deprecated: explicit showHeader takes precedence for backwards compat
|
|
89
|
+
if (currentProps.value?.showHeader !== undefined) {
|
|
90
|
+
return currentProps.value.showHeader;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return !!currentProps.value?.title;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const panelTitle = computed(() => currentProps.value?.title || (showHeader.value ? 'Details' : ''));
|
|
42
97
|
|
|
43
|
-
const showHeader = computed(() => currentProps?.value?.showHeader ?? true);
|
|
44
|
-
const panelTitle = showHeader.value ? computed(() => currentProps?.value?.title || 'Details') : null;
|
|
45
98
|
const closeOnRouteChange = computed(() => {
|
|
46
|
-
const propsCloseOnRouteChange = currentProps?.
|
|
99
|
+
const propsCloseOnRouteChange = currentProps.value?.closeOnRouteChange;
|
|
47
100
|
|
|
48
101
|
if (!propsCloseOnRouteChange) {
|
|
49
102
|
return ['name', 'params', 'hash', 'query'];
|
|
@@ -51,27 +104,32 @@ const closeOnRouteChange = computed(() => {
|
|
|
51
104
|
|
|
52
105
|
return propsCloseOnRouteChange;
|
|
53
106
|
});
|
|
107
|
+
|
|
54
108
|
const router = useRouter();
|
|
55
109
|
|
|
56
110
|
watch(
|
|
57
111
|
/**
|
|
58
|
-
*
|
|
112
|
+
* Focus trap logic
|
|
59
113
|
*/
|
|
60
|
-
() => isOpen
|
|
114
|
+
() => isOpen.value,
|
|
61
115
|
(neu, old) => {
|
|
62
116
|
if (neu && neu !== old) {
|
|
63
|
-
|
|
117
|
+
if (currentProps.value?.disableFocusTrap) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const panelEl = slideInPanelManager.value as HTMLElement;
|
|
122
|
+
const closeEl = slideInPanelManagerClose.value;
|
|
123
|
+
|
|
124
|
+
const opts: any = {
|
|
64
125
|
...DEFAULT_FOCUS_TRAP_OPTS,
|
|
65
|
-
|
|
66
|
-
|
|
126
|
+
initialFocus: closeEl || panelEl,
|
|
127
|
+
fallbackFocus: panelEl
|
|
67
128
|
};
|
|
68
129
|
|
|
69
|
-
const returnFocusSelector = currentProps
|
|
130
|
+
const returnFocusSelector = currentProps.value?.returnFocusSelector;
|
|
70
131
|
|
|
71
132
|
if (returnFocusSelector) {
|
|
72
|
-
/**
|
|
73
|
-
* will return focus to the first iterable node of this container select
|
|
74
|
-
*/
|
|
75
133
|
opts.setReturnFocus = () => {
|
|
76
134
|
if (returnFocusSelector && !document.querySelector(returnFocusSelector)) {
|
|
77
135
|
console.warn('SlideInPanelManager: cannot find elem with "returnFocusSelector", returning focus to main view'); // eslint-disable-line no-console
|
|
@@ -85,13 +143,13 @@ watch(
|
|
|
85
143
|
|
|
86
144
|
useWatcherBasedSetupFocusTrapWithDestroyIncluded(
|
|
87
145
|
() => {
|
|
88
|
-
if (currentProps
|
|
146
|
+
if (currentProps.value?.focusTrapWatcherBasedVariable) {
|
|
89
147
|
return currentProps.value.focusTrapWatcherBasedVariable;
|
|
90
148
|
}
|
|
91
149
|
|
|
92
|
-
return isOpen
|
|
150
|
+
return isOpen.value && !isClosing.value;
|
|
93
151
|
},
|
|
94
|
-
|
|
152
|
+
panelEl,
|
|
95
153
|
opts,
|
|
96
154
|
false
|
|
97
155
|
);
|
|
@@ -102,7 +160,7 @@ watch(
|
|
|
102
160
|
watch(
|
|
103
161
|
() => router?.currentRoute?.value,
|
|
104
162
|
(newValue, oldValue) => {
|
|
105
|
-
if (!isOpen
|
|
163
|
+
if (!isOpen.value) {
|
|
106
164
|
return;
|
|
107
165
|
}
|
|
108
166
|
|
|
@@ -137,6 +195,7 @@ function closePanel() {
|
|
|
137
195
|
<div
|
|
138
196
|
id="slide-in-panel-manager"
|
|
139
197
|
ref="SlideInPanelManager"
|
|
198
|
+
tabindex="-1"
|
|
140
199
|
@keydown.escape="closePanel"
|
|
141
200
|
>
|
|
142
201
|
<div
|
|
@@ -144,6 +203,7 @@ function closePanel() {
|
|
|
144
203
|
data-testid="slide-in-glass"
|
|
145
204
|
class="slide-in-glass"
|
|
146
205
|
:class="{ 'slide-in-glass-open': isOpen }"
|
|
206
|
+
:style="{ zIndex: glassZIndex }"
|
|
147
207
|
@click="closePanel"
|
|
148
208
|
/>
|
|
149
209
|
<aside
|
|
@@ -154,6 +214,7 @@ function closePanel() {
|
|
|
154
214
|
right: panelRight,
|
|
155
215
|
top: panelTop,
|
|
156
216
|
height: panelHeight,
|
|
217
|
+
zIndex: panelZIndex,
|
|
157
218
|
}"
|
|
158
219
|
>
|
|
159
220
|
<div
|
|
@@ -167,6 +228,8 @@ function closePanel() {
|
|
|
167
228
|
ref="SlideInPanelManagerClose"
|
|
168
229
|
class="icon icon-close"
|
|
169
230
|
data-testid="slide-in-close"
|
|
231
|
+
role="button"
|
|
232
|
+
:aria-label="t('generic.close')"
|
|
170
233
|
:tabindex="isOpen ? 0 : -1"
|
|
171
234
|
@click="closePanel"
|
|
172
235
|
@keypress.enter="closePanel"
|
|
@@ -220,7 +283,7 @@ function closePanel() {
|
|
|
220
283
|
.header {
|
|
221
284
|
display: flex;
|
|
222
285
|
align-items: center;
|
|
223
|
-
padding: 4px;
|
|
286
|
+
padding: 4px 10px;
|
|
224
287
|
border-bottom: 1px solid var(--border);
|
|
225
288
|
|
|
226
289
|
.title {
|
|
@@ -229,11 +292,26 @@ function closePanel() {
|
|
|
229
292
|
}
|
|
230
293
|
|
|
231
294
|
.icon-close {
|
|
295
|
+
padding: 8px;
|
|
296
|
+
border-radius: 4px;
|
|
297
|
+
opacity: 0.7;
|
|
232
298
|
cursor: pointer;
|
|
299
|
+
|
|
300
|
+
&:hover {
|
|
301
|
+
background-color: var(--primary);
|
|
302
|
+
color: var(--primary-text);
|
|
303
|
+
opacity: 1;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
&:focus-visible {
|
|
307
|
+
@include focus-outline;
|
|
308
|
+
outline-offset: 2px;
|
|
309
|
+
}
|
|
233
310
|
}
|
|
234
311
|
}
|
|
235
312
|
|
|
236
313
|
.main-panel {
|
|
314
|
+
flex: 1;
|
|
237
315
|
padding: 10px;
|
|
238
316
|
overflow: auto;
|
|
239
317
|
}
|