@rancher/shell 3.0.2-rc.3 → 3.0.2-rc.4

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 (49) hide show
  1. package/assets/styles/base/_basic.scss +2 -1
  2. package/assets/styles/global/_form.scss +2 -1
  3. package/assets/styles/themes/_dark.scss +1 -1
  4. package/assets/translations/en-us.yaml +22 -4
  5. package/assets/translations/zh-hans.yaml +2 -3
  6. package/components/AppModal.vue +50 -0
  7. package/components/Carousel.vue +54 -47
  8. package/components/CopyToClipboardText.vue +3 -0
  9. package/components/Dialog.vue +20 -1
  10. package/components/PromptChangePassword.vue +3 -0
  11. package/components/ResourceDetail/Masthead.vue +1 -1
  12. package/components/Tabbed/index.vue +4 -7
  13. package/components/__tests__/Carousel.test.ts +56 -27
  14. package/components/form/LabeledSelect.vue +1 -1
  15. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +192 -0
  16. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +104 -0
  17. package/components/form/SSHKnownHosts/index.vue +101 -0
  18. package/components/form/Select.vue +1 -1
  19. package/components/form/SelectOrCreateAuthSecret.vue +43 -11
  20. package/components/form/__tests__/SSHKnownHosts.test.ts +59 -0
  21. package/composables/focusTrap.ts +68 -0
  22. package/detail/secret.vue +25 -0
  23. package/edit/fleet.cattle.io.gitrepo.vue +27 -22
  24. package/edit/provisioning.cattle.io.cluster/index.vue +26 -19
  25. package/edit/secret/index.vue +1 -1
  26. package/edit/secret/ssh.vue +21 -3
  27. package/list/provisioning.cattle.io.cluster.vue +1 -0
  28. package/models/fleet.cattle.io.gitrepo.js +2 -2
  29. package/models/provisioning.cattle.io.cluster.js +2 -12
  30. package/models/secret.js +5 -0
  31. package/package.json +1 -1
  32. package/pages/account/index.vue +4 -0
  33. package/pages/c/_cluster/explorer/ConfigBadge.vue +5 -4
  34. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +3 -1
  35. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +3 -0
  36. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +7 -1
  37. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +3 -1
  38. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +10 -7
  39. package/pages/c/_cluster/uiplugins/InstallDialog.vue +7 -0
  40. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +181 -106
  41. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +2 -0
  42. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +9 -1
  43. package/pages/c/_cluster/uiplugins/index.vue +50 -12
  44. package/rancher-components/Card/Card.vue +7 -21
  45. package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -0
  46. package/rancher-components/RcDropdown/RcDropdown.vue +11 -0
  47. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +2 -3
  48. package/rancher-components/RcDropdown/useDropdownCollection.ts +1 -0
  49. package/rancher-components/RcDropdown/useDropdownContext.ts +28 -1
@@ -5,6 +5,7 @@ import { Banner } from '@components/Banner';
5
5
  import LazyImage from '@shell/components/LazyImage';
6
6
  import { MANAGEMENT } from '@shell/config/types';
7
7
  import { SETTING } from '@shell/config/settings';
8
+ import { useWatcherBasedSetupFocusTrapWithDestroyIncluded } from '@shell/composables/focusTrap';
8
9
 
9
10
  export default {
10
11
  async fetch() {
@@ -22,7 +23,6 @@ export default {
22
23
  ChartReadme,
23
24
  LazyImage
24
25
  },
25
-
26
26
  data() {
27
27
  return {
28
28
  showSlideIn: false,
@@ -32,9 +32,12 @@ export default {
32
32
  versionError: undefined,
33
33
  defaultIcon: require('~shell/assets/images/generic-plugin.svg'),
34
34
  headerBannerSize: 0,
35
+ isActive: false
35
36
  };
36
37
  },
37
-
38
+ created() {
39
+ useWatcherBasedSetupFocusTrapWithDestroyIncluded(() => this.showSlideIn, '#slide-in-content-element');
40
+ },
38
41
  computed: {
39
42
  ...mapGetters({ theme: 'prefs/theme' }),
40
43
 
@@ -46,7 +49,20 @@ export default {
46
49
  return {};
47
50
  },
48
51
  },
49
-
52
+ watch: {
53
+ showSlideIn: {
54
+ handler(neu) {
55
+ // we register the global event on slidein visibility
56
+ // so that it doesn't collide with other global events
57
+ if (neu) {
58
+ document.addEventListener('keyup', this.handleEscapeKey);
59
+ } else {
60
+ document.removeEventListener('keyup', this.handleEscapeKey);
61
+ }
62
+ },
63
+ immediate: true
64
+ }
65
+ },
50
66
  methods: {
51
67
  show(info) {
52
68
  this.info = info;
@@ -115,6 +131,22 @@ export default {
115
131
 
116
132
  handleVersionBtnClass(version) {
117
133
  return { 'version-active': version.version === this.infoVersion, disabled: !version.isVersionCompatible };
134
+ },
135
+
136
+ onEnter() {
137
+ this.isActive = true; // Set active state after the transition
138
+ },
139
+
140
+ onLeave() {
141
+ this.isActive = false; // Remove active state when fully closed
142
+ },
143
+
144
+ handleEscapeKey(event) {
145
+ event.stopPropagation();
146
+
147
+ if (event.key === 'Escape') {
148
+ this.hide();
149
+ }
118
150
  }
119
151
  }
120
152
  };
@@ -130,123 +162,139 @@ export default {
130
162
  data-testid="extension-details-bg"
131
163
  @click="hide()"
132
164
  />
133
- <div
134
- class="slideIn"
135
- data-testid="extension-details"
136
- :class="{'hide': false, 'slideIn__show': showSlideIn}"
165
+ <transition
166
+ name="slide"
167
+ @after-enter="onEnter"
168
+ @after-leave="onLeave"
137
169
  >
138
170
  <div
139
- v-if="info"
140
- class="plugin-info-content"
171
+ v-if="showSlideIn"
172
+ id="slide-in-content-element"
173
+ class="slideIn"
174
+ data-testid="extension-details"
175
+ :class="{'active': isActive}"
141
176
  >
142
- <div class="plugin-header">
143
- <div
144
- class="plugin-icon"
145
- :class="applyDarkModeBg"
146
- >
147
- <LazyImage
148
- v-if="info.icon"
149
- :initial-src="defaultIcon"
150
- :error-src="defaultIcon"
151
- :src="info.icon"
152
- class="icon plugin-icon-img"
153
- />
154
- <img
155
- v-else
156
- :src="defaultIcon"
157
- class="icon plugin-icon-img"
158
- >
159
- </div>
160
- <div class="plugin-title">
161
- <h2
162
- class="slideIn__header"
163
- data-testid="extension-details-title"
177
+ <div
178
+ v-if="info"
179
+ class="plugin-info-content"
180
+ >
181
+ <div class="plugin-header">
182
+ <div
183
+ class="plugin-icon"
184
+ :class="applyDarkModeBg"
164
185
  >
165
- {{ info.label }}
166
- </h2>
167
- <p class="plugin-description">
168
- {{ info.description }}
169
- </p>
170
- </div>
171
- <div class="plugin-close">
172
- <div class="slideIn__header__buttons">
173
- <div
174
- class="slideIn__header__button"
175
- data-testid="extension-details-close"
176
- @click="showSlideIn = false"
186
+ <LazyImage
187
+ v-if="info.icon"
188
+ :initial-src="defaultIcon"
189
+ :error-src="defaultIcon"
190
+ :src="info.icon"
191
+ class="icon plugin-icon-img"
192
+ />
193
+ <img
194
+ v-else
195
+ :src="defaultIcon"
196
+ class="icon plugin-icon-img"
177
197
  >
178
- <i class="icon icon-close" />
198
+ </div>
199
+ <div class="plugin-title">
200
+ <h2
201
+ class="slideIn__header"
202
+ data-testid="extension-details-title"
203
+ >
204
+ {{ info.label }}
205
+ </h2>
206
+ <p class="plugin-description">
207
+ {{ info.description }}
208
+ </p>
209
+ </div>
210
+ <div class="plugin-close">
211
+ <div class="slideIn__header__buttons">
212
+ <div
213
+ class="slideIn__header__button"
214
+ data-testid="extension-details-close"
215
+ role="button"
216
+ :aria-label="t('plugins.closePluginPanel')"
217
+ tabindex="0"
218
+ @click="hide()"
219
+ @keyup.enter.space="hide()"
220
+ >
221
+ <i class="icon icon-close" />
222
+ </div>
179
223
  </div>
180
224
  </div>
181
225
  </div>
182
- </div>
183
- <div>
184
- <Banner
185
- v-if="info.builtin"
186
- color="warning"
187
- :label="t('plugins.descriptions.built-in')"
188
- class="mt-10"
189
- />
190
- <template v-else>
226
+ <div>
191
227
  <Banner
192
- v-if="!info.certified"
228
+ v-if="info.builtin"
193
229
  color="warning"
194
- :label="t('plugins.descriptions.third-party')"
230
+ :label="t('plugins.descriptions.built-in')"
195
231
  class="mt-10"
196
232
  />
197
- <Banner
198
- v-if="info.experimental"
199
- color="warning"
200
- :label="t('plugins.descriptions.experimental')"
201
- class="mt-10"
202
- />
203
- </template>
204
- </div>
233
+ <template v-else>
234
+ <Banner
235
+ v-if="!info.certified"
236
+ color="warning"
237
+ :label="t('plugins.descriptions.third-party')"
238
+ class="mt-10"
239
+ />
240
+ <Banner
241
+ v-if="info.experimental"
242
+ color="warning"
243
+ :label="t('plugins.descriptions.experimental')"
244
+ class="mt-10"
245
+ />
246
+ </template>
247
+ </div>
205
248
 
206
- <h3 v-if="info.versions.length">
207
- {{ t('plugins.info.versions') }}
208
- </h3>
209
- <div class="plugin-versions mb-10">
210
- <div
211
- v-for="v in info.versions"
212
- :key="`${v.name}-${v.version}`"
213
- >
214
- <a
215
- v-clean-tooltip="handleVersionBtnTooltip(v)"
216
- class="version-link"
217
- :class="handleVersionBtnClass(v)"
218
- @click="loadPluginVersionInfo(v.version)"
249
+ <h3 v-if="info.versions.length">
250
+ {{ t('plugins.info.versions') }}
251
+ </h3>
252
+ <div class="plugin-versions mb-10">
253
+ <div
254
+ v-for="v in info.versions"
255
+ :key="`${v.name}-${v.version}`"
219
256
  >
220
- {{ v.version }}
221
- </a>
257
+ <a
258
+ v-clean-tooltip="handleVersionBtnTooltip(v)"
259
+ class="version-link"
260
+ :class="handleVersionBtnClass(v)"
261
+ :tabindex="!v.isVersionCompatible ? -1 : 0"
262
+ role="button"
263
+ :aria-label="t('plugins.viewVersionDetails', {name: v.name, version: v.version})"
264
+ @click="loadPluginVersionInfo(v.version)"
265
+ @keyup.enter.space="loadPluginVersionInfo(v.version)"
266
+ >
267
+ {{ v.version }}
268
+ </a>
269
+ </div>
222
270
  </div>
223
- </div>
224
271
 
225
- <div v-if="versionError">
226
- {{ t('plugins.info.versionError') }}
227
- </div>
228
- <h3 v-if="versionInfo">
229
- {{ t('plugins.info.detail') }}
230
- </h3>
231
- <div
232
- v-if="versionInfo"
233
- class="plugin-info-detail"
234
- >
235
- <ChartReadme
236
- v-if="versionInfo"
237
- :version-info="versionInfo"
238
- />
239
- </div>
240
- <div v-if="!info.versions.length">
241
- <h3>
242
- {{ t('plugins.info.versions') }}
272
+ <div v-if="versionError">
273
+ {{ t('plugins.info.versionError') }}
274
+ </div>
275
+ <h3 v-if="versionInfo">
276
+ {{ t('plugins.info.detail') }}
243
277
  </h3>
244
- <div class="version-link version-active version-builtin">
245
- {{ info.displayVersion }}
278
+ <div
279
+ v-if="versionInfo"
280
+ class="plugin-info-detail"
281
+ >
282
+ <ChartReadme
283
+ v-if="versionInfo"
284
+ :version-info="versionInfo"
285
+ />
286
+ </div>
287
+ <div v-if="!info.versions.length">
288
+ <h3>
289
+ {{ t('plugins.info.versions') }}
290
+ </h3>
291
+ <div class="version-link version-active version-builtin">
292
+ {{ info.displayVersion }}
293
+ </div>
246
294
  </div>
247
295
  </div>
248
296
  </div>
249
- </div>
297
+ </transition>
250
298
  </div>
251
299
  </template>
252
300
  <style lang="scss" scoped>
@@ -284,10 +332,30 @@ export default {
284
332
  z-index: 10;
285
333
  display: flex;
286
334
  flex-direction: column;
287
-
288
335
  padding: 10px;
289
336
 
290
- transition: right .5s ease;
337
+ &.active {
338
+ right: 0;
339
+ }
340
+
341
+ /* Enter animation */
342
+ &.slide-enter-active {
343
+ transition: right 0.5s ease; /* Animates both enter and leave */
344
+ }
345
+
346
+ &.slide-leave-active {
347
+ transition: right 0.5s ease; /* Animates both enter and leave */
348
+ }
349
+
350
+ &.slide-enter-from,
351
+ &.slide-leave-to {
352
+ right: -$slideout-width; /* Off-screen position */
353
+ }
354
+
355
+ &.slide-enter-to,
356
+ &.slide-leave-from {
357
+ right: 0; /* Fully visible position */
358
+ }
291
359
 
292
360
  &__header {
293
361
  text-transform: capitalize;
@@ -378,6 +446,10 @@ export default {
378
446
  &.version-builtin {
379
447
  display: inline-block;
380
448
  }
449
+
450
+ &:focus-visible {
451
+ @include focus-outline;
452
+ }
381
453
  }
382
454
 
383
455
  &__header {
@@ -395,13 +467,20 @@ export default {
395
467
  align-items: center;
396
468
  justify-content: center;
397
469
  padding: 2px;
470
+
398
471
  > i {
399
472
  font-size: 20px;
400
473
  opacity: 0.5;
401
474
  }
475
+
402
476
  &:hover {
403
477
  background-color: var(--wm-closer-hover-bg);
404
478
  }
479
+
480
+ &:focus-visible {
481
+ @include focus-outline;
482
+ outline-offset: -2px;
483
+ }
405
484
  }
406
485
  }
407
486
 
@@ -419,10 +498,6 @@ export default {
419
498
  overflow: auto;
420
499
  }
421
500
  }
422
-
423
- &__show {
424
- right: 0;
425
- }
426
501
  }
427
502
  }
428
503
  </style>
@@ -62,6 +62,8 @@ export default {
62
62
  v-if="showFeaturesButton"
63
63
  class="btn role-primary enable-plugin-support"
64
64
  data-testid="extension-feature-button"
65
+ role="button"
66
+ :aria-label="t('plugins.setup.install.featuresButton')"
65
67
  @click="redirectToFeatureFlags"
66
68
  >
67
69
  {{ t('plugins.setup.install.featuresButton') }}
@@ -20,7 +20,12 @@ export default {
20
20
  };
21
21
  },
22
22
 
23
- computed: { ...mapGetters({ allCharts: 'catalog/charts' }) },
23
+ computed: {
24
+ ...mapGetters({ allCharts: 'catalog/charts' }),
25
+ returnFocusSelector() {
26
+ return `[data-testid="extension-card-uninstall-btn-${ this.plugin?.name }"]`;
27
+ }
28
+ },
24
29
 
25
30
  methods: {
26
31
  showDialog(plugin) {
@@ -78,6 +83,9 @@ export default {
78
83
  name="uninstallPluginDialog"
79
84
  height="auto"
80
85
  :scrollable="true"
86
+ :trigger-focus-trap="true"
87
+ :return-focus-selector="returnFocusSelector"
88
+ :return-focus-first-iterable-node-selector="'#extensions-main-page'"
81
89
  @close="closeDialog(false)"
82
90
  >
83
91
  <div
@@ -627,8 +627,12 @@ export default {
627
627
  </script>
628
628
 
629
629
  <template>
630
- <div class="plugins">
630
+ <div
631
+ id="extensions-main-page"
632
+ class="plugins"
633
+ >
631
634
  <div class="plugin-header">
635
+ <!-- catalog view header -->
632
636
  <template v-if="showCatalogList">
633
637
  <div class="catalog-title">
634
638
  <h2
@@ -637,6 +641,9 @@ export default {
637
641
  >
638
642
  <a
639
643
  class="link"
644
+ role="link"
645
+ tabindex="0"
646
+ :aria-label="t('plugins.manageCatalog.title')"
640
647
  @click="manageExtensionView()"
641
648
  >
642
649
  {{ t('plugins.manageCatalog.title') }}:
@@ -650,6 +657,7 @@ export default {
650
657
  />
651
658
  </div>
652
659
  </template>
660
+ <!-- normal extensions view header -->
653
661
  <template v-else>
654
662
  <h2 data-testid="extensions-page-title">
655
663
  <TabTitle breadcrumb="vendor-only">
@@ -658,6 +666,7 @@ export default {
658
666
  </h2>
659
667
  </template>
660
668
  <div class="actions-container">
669
+ <!-- extensions reload toast/notification -->
661
670
  <div
662
671
  v-if="reloadRequired"
663
672
  class="plugin-reload-banner mr-20"
@@ -670,11 +679,14 @@ export default {
670
679
  <button
671
680
  class="ml-10 btn btn-sm role-primary"
672
681
  data-testid="extension-reload-banner-reload-btn"
682
+ role="button"
683
+ :aria-label="t('plugins.labels.reloadRancher')"
673
684
  @click="reload()"
674
685
  >
675
686
  {{ t('generic.reload') }}
676
687
  </button>
677
688
  </div>
689
+ <!-- extensions menu -->
678
690
  <div v-if="hasFeatureFlag && hasMenuActions">
679
691
  <button
680
692
  ref="actions"
@@ -682,6 +694,8 @@ export default {
682
694
  type="button"
683
695
  class="btn role-multi-action actions"
684
696
  data-testid="extensions-page-menu"
697
+ role="button"
698
+ :aria-label="t('plugins.labels.menu')"
685
699
  @click="setMenu"
686
700
  >
687
701
  <i class="icon icon-actions" />
@@ -702,8 +716,10 @@ export default {
702
716
  </div>
703
717
  </div>
704
718
 
719
+ <!-- extensions slide-in panel -->
705
720
  <PluginInfoPanel ref="infoPanel" />
706
721
 
722
+ <!-- extensions not enabled by feature flag -->
707
723
  <div v-if="!hasFeatureFlag">
708
724
  <div
709
725
  v-if="loading"
@@ -724,6 +740,7 @@ export default {
724
740
  />
725
741
  </div>
726
742
  <div v-else>
743
+ <!-- Extension Catalog list view -->
727
744
  <template v-if="showCatalogList">
728
745
  <CatalogList
729
746
  @showCatalogLoadDialog="showCatalogLoadDialog"
@@ -741,6 +758,8 @@ export default {
741
758
  <button
742
759
  class="ml-10 btn btn-sm role-primary"
743
760
  data-testid="extensions-new-repos-banner-action-btn"
761
+ role="button"
762
+ :aria-label="t('plugins.addRepos.bannerBtn')"
744
763
  @click="showAddExtensionReposDialog()"
745
764
  >
746
765
  {{ t('plugins.addRepos.bannerBtn') }}
@@ -800,14 +819,20 @@ export default {
800
819
  :message="emptyMessage"
801
820
  />
802
821
  <template v-else>
822
+ <!-- extension card! -->
803
823
  <div
804
824
  v-for="(plugin, i) in list"
825
+ :id="`list-item-${i}`"
805
826
  :key="i"
806
827
  class="plugin"
807
828
  :data-testid="`extension-card-${plugin.name}`"
829
+ role="button"
830
+ tabindex="0"
831
+ :aria-label="plugin.name || ''"
808
832
  @click="showPluginDetail(plugin)"
833
+ @keyup.enter.space="showPluginDetail(plugin)"
809
834
  >
810
- <!-- plugin icon -->
835
+ <!-- extension icon -->
811
836
  <div
812
837
  class="plugin-icon"
813
838
  :class="applyDarkModeBg"
@@ -825,13 +850,14 @@ export default {
825
850
  class="icon plugin-icon-img"
826
851
  >
827
852
  </div>
828
- <!-- plugin card -->
853
+ <!-- extension card -->
829
854
  <div class="plugin-metadata">
830
- <!-- plugin basic info -->
855
+ <!-- extension basic info -->
831
856
  <div class="plugin-name">
832
857
  {{ plugin.label }}
833
858
  </div>
834
859
  <div>{{ plugin.description }}</div>
860
+ <!-- extension version info and error display -->
835
861
  <div class="plugin-version">
836
862
  <span
837
863
  v-if="plugin.installing"
@@ -858,7 +884,7 @@ export default {
858
884
  >{{ plugin.incompatibilityMessage }}</p>
859
885
  </span>
860
886
  </div>
861
- <!-- plugin badges -->
887
+ <!-- extension badges -->
862
888
  <div
863
889
  v-if="plugin.builtin"
864
890
  class="plugin-badges"
@@ -885,9 +911,9 @@ export default {
885
911
  </div>
886
912
  </div>
887
913
  <div class="plugin-spacer" />
888
- <!-- plugin badges -->
914
+ <!-- extension actions -->
889
915
  <div class="plugin-actions">
890
- <!-- plugin status -->
916
+ <!-- extension status -->
891
917
  <div
892
918
  v-if="plugin.helmError"
893
919
  v-clean-tooltip="t('plugins.helmError')"
@@ -910,7 +936,7 @@ export default {
910
936
  {{ t('plugins.labels.uninstalling') }}
911
937
  </div>
912
938
  </div>
913
- <!-- plugin buttons -->
939
+ <!-- extension buttons -->
914
940
  <div
915
941
  v-else-if="plugin.installed"
916
942
  class="plugin-buttons"
@@ -919,7 +945,10 @@ export default {
919
945
  v-if="!plugin.builtin"
920
946
  class="btn role-secondary"
921
947
  :data-testid="`extension-card-uninstall-btn-${plugin.name}`"
922
- @click="showUninstallDialog(plugin, $event)"
948
+ role="button"
949
+ :aria-label="t('plugins.uninstall.label')"
950
+ @click.stop="showUninstallDialog(plugin, $event)"
951
+ @keyup.space.stop="showUninstallDialog(plugin, $event)"
923
952
  >
924
953
  {{ t('plugins.uninstall.label') }}
925
954
  </button>
@@ -927,7 +956,10 @@ export default {
927
956
  v-if="plugin.upgrade"
928
957
  class="btn role-secondary"
929
958
  :data-testid="`extension-card-update-btn-${plugin.name}`"
930
- @click="showInstallDialog(plugin, 'update', $event)"
959
+ role="button"
960
+ :aria-label="t('plugins.update.label')"
961
+ @click.stop="showInstallDialog(plugin, 'update', $event)"
962
+ @keyup.space.stop="showInstallDialog(plugin, 'update', $event)"
931
963
  >
932
964
  {{ t('plugins.update.label') }}
933
965
  </button>
@@ -935,7 +967,10 @@ export default {
935
967
  v-if="!plugin.upgrade && plugin.installableVersions && plugin.installableVersions.length > 1"
936
968
  class="btn role-secondary"
937
969
  :data-testid="`extension-card-rollback-btn-${plugin.name}`"
938
- @click="showInstallDialog(plugin, 'rollback', $event)"
970
+ role="button"
971
+ :aria-label="t('plugins.rollback.label')"
972
+ @click.stop="showInstallDialog(plugin, 'rollback', $event)"
973
+ @keyup.space.stop="showInstallDialog(plugin, 'rollback', $event)"
939
974
  >
940
975
  {{ t('plugins.rollback.label') }}
941
976
  </button>
@@ -947,7 +982,10 @@ export default {
947
982
  <button
948
983
  class="btn role-secondary"
949
984
  :data-testid="`extension-card-install-btn-${plugin.name}`"
950
- @click="showInstallDialog(plugin, 'install', $event)"
985
+ role="button"
986
+ :aria-label="t('plugins.install.label')"
987
+ @click.stop="showInstallDialog(plugin, 'install', $event)"
988
+ @keyup.space.stop="showInstallDialog(plugin, 'install', $event)"
951
989
  >
952
990
  {{ t('plugins.install.label') }}
953
991
  </button>
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent, PropType } from 'vue';
3
- import { createFocusTrap, FocusTrap } from 'focus-trap';
3
+ import { useBasicSetupFocusTrap } from '@shell/composables/focusTrap';
4
4
 
5
5
  export default defineComponent({
6
+
6
7
  name: 'Card',
7
8
  props: {
8
9
  /**
@@ -56,32 +57,17 @@ export default defineComponent({
56
57
  default: false,
57
58
  },
58
59
  },
59
- data() {
60
- return { focusTrapInstance: {} as FocusTrap };
61
- },
62
- mounted() {
63
- if (this.triggerFocusTrap) {
64
- this.focusTrapInstance = createFocusTrap(this.$refs.cardContainer as HTMLElement, {
65
- escapeDeactivates: true,
66
- allowOutsideClick: true,
67
- });
68
-
69
- this.$nextTick(() => {
70
- this.focusTrapInstance.activate();
71
- });
60
+ setup(props) {
61
+ if (props.triggerFocusTrap) {
62
+ useBasicSetupFocusTrap('#focus-trap-card-container-element');
72
63
  }
73
- },
74
- beforeUnmount() {
75
- if (this.focusTrapInstance && this.triggerFocusTrap) {
76
- this.focusTrapInstance.deactivate();
77
- }
78
- },
64
+ }
79
65
  });
80
66
  </script>
81
67
 
82
68
  <template>
83
69
  <div
84
- ref="cardContainer"
70
+ id="focus-trap-card-container-element"
85
71
  class="card-container"
86
72
  :class="{'highlight-border': showHighlightBorder, 'card-sticky': sticky}"
87
73
  data-testid="card"
@@ -364,6 +364,7 @@ export default defineComponent({
364
364
  <input
365
365
  v-else
366
366
  ref="value"
367
+ role="textbox"
367
368
  :class="{ 'no-label': !hasLabel }"
368
369
  v-bind="$attrs"
369
370
  :maxlength="_maxlength"