@rancher/shell 3.0.1 → 3.0.2-rc.1

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 (96) hide show
  1. package/assets/styles/base/_basic.scss +17 -5
  2. package/assets/styles/base/_mixins.scss +2 -1
  3. package/assets/styles/global/_button.scss +10 -0
  4. package/assets/styles/global/_form.scss +2 -2
  5. package/assets/translations/en-us.yaml +33 -5
  6. package/assets/translations/zh-hans.yaml +1 -1
  7. package/components/ActionMenu.vue +8 -0
  8. package/components/AsyncButton.vue +9 -3
  9. package/components/BannerGraphic.vue +10 -0
  10. package/components/ButtonGroup.vue +2 -0
  11. package/components/ButtonMultiAction.vue +6 -0
  12. package/components/ClusterIconMenu.vue +1 -1
  13. package/components/CodeMirror.vue +28 -1
  14. package/components/CommunityLinks.vue +13 -0
  15. package/components/CruResource.vue +6 -0
  16. package/components/GrowlManager.vue +14 -4
  17. package/components/LocaleSelector.vue +49 -5
  18. package/components/PaginatedResourceTable.vue +4 -3
  19. package/components/ResourceDetail/Masthead.vue +11 -4
  20. package/components/ResourceList/index.vue +5 -3
  21. package/components/ResourceTable.vue +1 -1
  22. package/components/SortableTable/THead.vue +19 -4
  23. package/components/SortableTable/index.vue +13 -9
  24. package/components/SortableTable/selection.js +19 -5
  25. package/components/YamlEditor.vue +2 -1
  26. package/components/auth/SelectPrincipal.vue +1 -1
  27. package/components/fleet/FleetBundles.vue +2 -1
  28. package/components/form/LabeledSelect.vue +20 -7
  29. package/components/form/NodeScheduling.vue +5 -1
  30. package/components/form/Password.vue +23 -13
  31. package/components/form/ResourceLabeledSelect.vue +1 -1
  32. package/components/form/Select.vue +28 -6
  33. package/components/form/SelectOrCreateAuthSecret.vue +39 -11
  34. package/components/form/__tests__/NodeScheduling.test.ts +44 -0
  35. package/components/formatter/Endpoints.vue +1 -1
  36. package/components/formatter/LiveExpiryDate.vue +5 -1
  37. package/components/formatter/ServiceTargets.vue +1 -1
  38. package/components/formatter/ServiceType.vue +19 -17
  39. package/components/nav/Pinned.vue +6 -1
  40. package/components/nav/TopLevelMenu.helper.ts +17 -1
  41. package/components/nav/TopLevelMenu.vue +154 -19
  42. package/config/pagination-table-headers.js +9 -1
  43. package/config/product/apps.js +63 -30
  44. package/config/product/explorer.js +182 -17
  45. package/config/product/settings.js +9 -1
  46. package/config/router/routes.js +0 -1
  47. package/config/settings.ts +20 -2
  48. package/config/table-headers.js +23 -15
  49. package/config/types.js +2 -1
  50. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +12 -3
  51. package/edit/fleet.cattle.io.gitrepo.vue +40 -33
  52. package/edit/provisioning.cattle.io.cluster/rke2.vue +13 -2
  53. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +10 -2
  54. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
  55. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +6 -3
  56. package/edit/workload/mixins/workload.js +15 -7
  57. package/list/catalog.cattle.io.app.vue +4 -11
  58. package/list/catalog.cattle.io.clusterrepo.vue +59 -25
  59. package/list/fleet.cattle.io.bundle.vue +2 -2
  60. package/list/management.cattle.io.feature.vue +12 -5
  61. package/list/management.cattle.io.setting.vue +30 -19
  62. package/list/namespace.vue +4 -1
  63. package/list/networking.k8s.io.ingress.vue +14 -11
  64. package/list/node.vue +65 -63
  65. package/list/persistentvolume.vue +55 -20
  66. package/list/persistentvolumeclaim.vue +3 -15
  67. package/list/service.vue +16 -21
  68. package/list/workload.vue +35 -49
  69. package/mixins/resource-fetch.js +8 -1
  70. package/mixins/vue-select-overrides.js +10 -16
  71. package/models/management.cattle.io.cluster.js +6 -1
  72. package/models/persistentvolume.js +1 -3
  73. package/models/storage.k8s.io.storageclass.js +4 -0
  74. package/package.json +28 -29
  75. package/pages/c/_cluster/explorer/EventsTable.vue +58 -16
  76. package/pages/c/_cluster/explorer/index.vue +3 -16
  77. package/pages/c/_cluster/settings/performance.vue +49 -23
  78. package/pages/home.vue +24 -3
  79. package/pages/support/index.vue +1 -1
  80. package/plugins/floating-vue.js +1 -1
  81. package/plugins/steve/steve-pagination-utils.ts +85 -15
  82. package/rancher-components/Banner/Banner.vue +12 -0
  83. package/rancher-components/Form/Checkbox/Checkbox.vue +27 -5
  84. package/rancher-components/Form/Radio/RadioButton.vue +0 -6
  85. package/rancher-components/Form/Radio/RadioGroup.vue +5 -1
  86. package/scripts/.gitlab/workflows/build-extension-catalog.gitlab-ci.yml +2 -2
  87. package/scripts/test-plugins-build.sh +21 -6
  88. package/scripts/typegen.sh +1 -0
  89. package/store/index.js +16 -0
  90. package/store/type-map.utils.ts +14 -1
  91. package/types/shell/index.d.ts +467 -418
  92. package/types/store/vuex.d.ts +1 -1
  93. package/types/vue-shim.d.ts +2 -8
  94. package/utils/cluster.js +2 -2
  95. package/utils/string.js +6 -0
  96. package/vue.config.js +3 -4
@@ -62,12 +62,24 @@ INPUT,
62
62
  SELECT,
63
63
  TEXTAREA,
64
64
  .labeled-input,
65
- .labeled-select,
66
- .unlabeled-select,
67
- .checkbox-custom,
68
- .radio-custom {
65
+ .checkbox-custom {
69
66
  &:focus, &.focused {
70
- @include form-focus
67
+ @include form-focus;
68
+ }
69
+ }
70
+
71
+ .radio-custom,
72
+ .labeled-select,
73
+ .unlabeled-select {
74
+ &:focus-visible, &.focused {
75
+ @include focus-outline;
76
+ }
77
+ }
78
+
79
+ .labeled-select,
80
+ .unlabeled-select {
81
+ &.focused {
82
+ border-color: var(--outline);
71
83
  }
72
84
  }
73
85
 
@@ -158,5 +158,6 @@
158
158
 
159
159
  @mixin focus-outline {
160
160
  // Focus for form like elements (not to be confused with basic :focus style)
161
+ // we need to use !important because it needs to superseed other classes that might impact outlines
161
162
  outline: 2px solid var(--primary-keyboard-focus);
162
- }
163
+ }
@@ -106,6 +106,11 @@ button,
106
106
  border: 0;
107
107
  }
108
108
  }
109
+
110
+ &:focus-visible {
111
+ @include focus-outline;
112
+ outline-offset: 2px;
113
+ }
109
114
  }
110
115
 
111
116
  .role-tertiary {
@@ -180,6 +185,11 @@ fieldset[disabled] .btn {
180
185
  z-index: 1;
181
186
  }
182
187
 
188
+ &:focus-visible {
189
+ z-index: 1;
190
+ @include focus-outline;
191
+ }
192
+
183
193
  &.active {
184
194
  @extend .bg-primary;
185
195
  }
@@ -27,8 +27,8 @@ TEXTAREA,
27
27
 
28
28
  @include input-status-color;
29
29
 
30
- &:focus, &.focused {
31
- @include form-focus
30
+ &:focus:not(.unlabeled-select):not(.labeled-select), &.focused:not(.unlabeled-select):not(.labeled-select) {
31
+ @include form-focus;
32
32
  }
33
33
 
34
34
  LABEL {
@@ -4,6 +4,7 @@
4
4
  generic:
5
5
  add: Add
6
6
  all: All
7
+ ascending: ascending
7
8
  and: ' and '
8
9
  back: Back
9
10
  cancel: Cancel
@@ -19,6 +20,7 @@ generic:
19
20
  customize: Customize
20
21
  dashboard: Dashboard
21
22
  default: Default
23
+ descending: descending
22
24
  disabled: Disabled
23
25
  done: Done
24
26
  enabled: Enabled
@@ -115,11 +117,26 @@ generic:
115
117
  basic: Basic
116
118
 
117
119
  locale:
120
+ menu: Locale selector menu
118
121
  en-us: English
119
122
  zh-hans: 简体中文
120
123
  none: (None)
121
124
 
122
125
  nav:
126
+ ariaLabel:
127
+ topLevelMenu: Main menu
128
+ homePage: Home page navigation menu
129
+ cluster: Cluster menu item
130
+ harvesterCluster: Harvester cluster menu item
131
+ seeAll: See all clusters menu item
132
+ multiClusterApps: Main menu multi cluster app menu item
133
+ configurationApps: Main menu configuration app menu item
134
+ support: Support page link
135
+ about: About page link
136
+ pinCluster: Pin/Unpin cluster
137
+ alt:
138
+ mainMenuIcon: Main menu icon
139
+ mainMenuRancherLogo: Main menu Rancher logo
123
140
  expandCollapseAppBar: Expand/Collapse the Application Bar
124
141
  harvesterDashboard: Harvester Dashboard
125
142
  backToRancher: Cluster Manager
@@ -196,6 +213,7 @@ nav:
196
213
  placeholder: Filter clusters by...
197
214
  noResults: No matching clusters
198
215
  clusters: clusters
216
+ ariaLabel: Filter clusters on main menu
199
217
  resourceSearch:
200
218
  label: Resource Search
201
219
  toolTip: Resource Search {key}
@@ -5169,11 +5187,14 @@ selectOrCreateAuthSecret:
5169
5187
  basic:
5170
5188
  username: Username
5171
5189
  password: Password
5190
+ rke:
5191
+ info: "An RKE Auth Config secret contains the username and password concatenated and base64 encoded into the secret's 'auth' key"
5172
5192
  namespaceGroup: "Namespace: {name}"
5173
5193
  chooseExisting: "Choose an existing secret:"
5174
- createSsh: Create a SSH Key Secret
5175
- createBasic: Create a HTTP Basic Auth Secret
5176
- createS3: Create a S3-Compatible Auth Secret
5194
+ createSsh: Create an SSH Key Secret
5195
+ createBasic: Create an HTTP Basic Auth Secret
5196
+ createS3: Create an S3-Compatible Auth Secret
5197
+ createRKE: Create an RKE Auth Config Secret
5177
5198
 
5178
5199
  serviceAccount:
5179
5200
  automount: Automount Service Account Token
@@ -5743,6 +5764,7 @@ tableHeaders:
5743
5764
  lastSchedule: Last Schedule
5744
5765
  lastSeen: Last Seen
5745
5766
  lastSeenTooltip: The time at which the most recent occurrence of this event was recorded
5767
+ lastUsed: Last Used
5746
5768
  loggingOutputProviders: Provider
5747
5769
  machines: Machines
5748
5770
  machineNodeName: Node
@@ -5818,6 +5840,7 @@ tableHeaders:
5818
5840
  resourcesReady: Resources Ready
5819
5841
  restarts: Restarts
5820
5842
  restart: Restart Required
5843
+ restartSystem: Restart { vendor }
5821
5844
  restore: Restore
5822
5845
  role: Role
5823
5846
  roles: Roles
@@ -7409,6 +7432,8 @@ registryConfig:
7409
7432
  ##############################
7410
7433
 
7411
7434
  advancedSettings:
7435
+ setEnv: Set by Environment Variable
7436
+ hideShow: Hide/show setting
7412
7437
  label: Settings
7413
7438
  subtext: Typical users will not need to change these. Proceed with caution, incorrect values can break your {appName} installation. Settings which have been customized from default settings are tagged 'Modified'.
7414
7439
  show: Show
@@ -7480,6 +7505,7 @@ advancedSettings:
7480
7505
 
7481
7506
  featureFlags:
7482
7507
  label: Feature Flags
7508
+ title: "Are you sure?"
7483
7509
  warning: |-
7484
7510
  Feature flags allow {vendor} to gate certain features behind flags.
7485
7511
  Features that are off by default should be considered experimental functionality.
@@ -7487,7 +7513,7 @@ featureFlags:
7487
7513
  This will result in a short outage of the API and UI, but not affect running clusters or workloads.
7488
7514
  promptActivate: Please confirm that you want to activate the feature flag "{flag}"
7489
7515
  promptDeactivate: Please confirm that you want to deactivate the feature flag "{flag}"
7490
- restartRequired: "Note: Updating this feature flag requires a restart"
7516
+ restartRequired: "Note: Updating this feature flag will restart {vendor}"
7491
7517
  restart:
7492
7518
  title: Waiting for Restart
7493
7519
  wait: This may take a few moments
@@ -7496,6 +7522,7 @@ performance:
7496
7522
  label: UI Performance Settings
7497
7523
  settingName: Performance
7498
7524
  experimental: This setting is experimental and may be removed or updated in future versions.
7525
+ deprecatedForSSP: The <i class="mr-5">"{setting}"</i> setting is now deprecated and will be removed in a future release. Please use the <a href="#ssp-setting" class="ml-5 mr-5">Server-side Pagination</a> setting instead.
7499
7526
  incrementalLoad:
7500
7527
  label: Incremental Loading
7501
7528
  setting: You can configure the threshold above which incremental loading will be used.
@@ -7565,6 +7592,7 @@ performance:
7565
7592
  resources:
7566
7593
  generic: most resources in the cluster's 'More Resources' section
7567
7594
  all: All Resources
7595
+ populateDefaults: Populate with latest pagination defaults
7568
7596
  banner:
7569
7597
  label: Fixed Banners
7570
7598
  settingName: Banners
@@ -7759,7 +7787,7 @@ support:
7759
7787
  text: Run SUSE Rancher products with confidence, knowing that the developers who built them are available to quickly resolve issues.
7760
7788
  three:
7761
7789
  title: Troubleshooting
7762
- text: We focus on uncovering the root cause of any issue, whether it is related to Rancher Labs products, Kubernetes, Docker or your underlying infrastructure.
7790
+ text: We focus on uncovering the root cause of any issue, whether it is related to SUSE products, Kubernetes, Docker or your underlying infrastructure.
7763
7791
  four:
7764
7792
  title: Innovate with Freedom
7765
7793
  text: Take advantage of our certified compatibility with a wide range of Kubernetes providers, operating systems, and open source software.
@@ -7310,7 +7310,7 @@ support:
7310
7310
  text: 我们的开发人员会快速解决问题,因此你可以放心使用 SUSE Rancher 的产品。
7311
7311
  three:
7312
7312
  title: 故障排除
7313
- text: 无论你使用的 Rancher Labs 产品、Kubernetes、Docker 还是底层基础架构出现问题,我们都会努力找到问题的根本原因。
7313
+ text: 无论你使用的 SUSE 产品、Kubernetes、Docker 还是底层基础架构出现问题,我们都会努力找到问题的根本原因。
7314
7314
  four:
7315
7315
  title: 自由创新
7316
7316
  text: 基于我们与众多 Kubernetes 供应商、操作系统和开源软件认证的兼容性,实现自主创新。
@@ -264,7 +264,10 @@ export default {
264
264
  :disabled="opt.disabled ? true : null"
265
265
  :class="{divider: opt.divider}"
266
266
  :data-testid="componentTestid + '-' + i + '-item'"
267
+ :tabindex="opt.divider ? -1 : 0"
267
268
  @click="execute(opt, $event)"
269
+ @keyup.enter="execute(opt, $event)"
270
+ @keyup.space="execute(opt, $event)"
268
271
  >
269
272
  <IconOrSvg
270
273
  v-if="opt.icon || opt.svg"
@@ -311,6 +314,11 @@ export default {
311
314
  padding: 8px 10px;
312
315
  margin: 0;
313
316
 
317
+ &:focus-visible {
318
+ @include focus-outline;
319
+ outline-offset: -2px;
320
+ }
321
+
314
322
  &[disabled] {
315
323
  cursor: not-allowed !important;
316
324
  color: var(--disabled-text);
@@ -208,6 +208,10 @@ export default defineComponent({
208
208
  return this.disabled || this.phase === ASYNC_BUTTON_STATES.WAITING;
209
209
  },
210
210
 
211
+ isManualRefresh() {
212
+ return this.mode === 'manual-refresh';
213
+ },
214
+
211
215
  tooltip(): { content: string, hideOnTargetClick: boolean} | null {
212
216
  if ( this.labelAs === TOOLTIP ) {
213
217
  return {
@@ -283,12 +287,14 @@ export default defineComponent({
283
287
  :data-testid="componentTestid + '-async-button'"
284
288
  @click="clicked"
285
289
  >
286
- <span v-if="mode === 'manual-refresh'">{{ t('action.refresh') }}</span>
290
+ <span
291
+ v-if="isManualRefresh"
292
+ :class="{'mr-10': displayIcon && size !== 'sm', 'mr-5': displayIcon && size === 'sm'}"
293
+ >{{ t('action.refresh') }}</span>
287
294
  <i
288
295
  v-if="displayIcon"
289
296
  v-clean-tooltip="tooltip"
290
- :class="{icon: true, 'icon-lg': true, [displayIcon]: true}"
291
- class="ml-5 mr-0"
297
+ :class="{icon: true, 'icon-lg': true, [displayIcon]: true, 'mr-0': isManualRefresh}"
292
298
  />
293
299
  <span
294
300
  v-if="labelAs === 'text' && displayLabel"
@@ -55,7 +55,12 @@ export default {
55
55
  v-if="pref"
56
56
  class="close-button"
57
57
  data-testid="graphic-banner-close"
58
+ tabindex="0"
59
+ :aria-label="t('generic.close')"
60
+ role="button"
58
61
  @click="hide()"
62
+ @keyup.enter="hide()"
63
+ @keyup.space="hide()"
59
64
  >
60
65
  <i class="icon icon-close" />
61
66
  </div>
@@ -72,6 +77,11 @@ export default {
72
77
  .close-button {
73
78
  position: absolute;
74
79
  visibility: hidden;
80
+
81
+ &:focus-visible {
82
+ @include focus-outline;
83
+ outline-offset: 2px;
84
+ }
75
85
  }
76
86
 
77
87
  &:hover .close-button {
@@ -82,6 +82,8 @@ export default {
82
82
  type="button"
83
83
  :class="opt.class"
84
84
  :disabled="disabled || opt.disabled"
85
+ role="button"
86
+ :aria-label="opt.labelKey ? t(opt.labelKey) : opt.label"
85
87
  @click="change(opt.value)"
86
88
  >
87
89
  <slot
@@ -33,6 +33,12 @@ const buttonClass = computed(() => {
33
33
  .borderless {
34
34
  background-color: transparent;
35
35
  border: none;
36
+
37
+ &:focus-visible {
38
+ @include focus-outline;
39
+ outline-offset: -2px;
40
+ }
41
+
36
42
  &:hover, &:focus {
37
43
  background-color: var(--accent-btn);
38
44
  box-shadow: none;
@@ -25,7 +25,7 @@ export default {
25
25
  },
26
26
 
27
27
  customColor() {
28
- return !this.cluster.removePreviewColor && this.cluster.badge?.iconText ? this.cluster.badge?.color : '';
28
+ return this.cluster.iconColor || '';
29
29
  },
30
30
  },
31
31
 
@@ -5,7 +5,7 @@ import { _EDIT, _VIEW } from '@shell/config/query-params';
5
5
  export default {
6
6
  name: 'CodeMirror',
7
7
 
8
- emits: ['onReady', 'onInput', 'onChanges', 'onFocus'],
8
+ emits: ['onReady', 'onInput', 'onChanges', 'onFocus', 'validationChanged'],
9
9
 
10
10
  props: {
11
11
  /**
@@ -39,6 +39,7 @@ export default {
39
39
  codeMirrorRef: null,
40
40
  loaded: false,
41
41
  removeKeyMapBox: false,
42
+ hasLintErrors: false,
42
43
  };
43
44
  },
44
45
 
@@ -65,6 +66,7 @@ export default {
65
66
  foldGutter: true,
66
67
  styleSelectedText: true,
67
68
  showCursorWhenSelecting: true,
69
+ autocorrect: false,
68
70
  };
69
71
 
70
72
  if (this.asTextArea) {
@@ -76,6 +78,11 @@ export default {
76
78
 
77
79
  Object.assign(out, this.options);
78
80
 
81
+ // parent components control lint with a boolean; if linting is enabled, we need to override that boolean with a custom error handler to wire lint errors into dashboard validation
82
+ if (this.options?.lint) {
83
+ out.lint = { onUpdateLinting: this.handleLintErrors };
84
+ }
85
+
79
86
  return out;
80
87
  },
81
88
 
@@ -104,7 +111,25 @@ export default {
104
111
  }
105
112
  },
106
113
 
114
+ watch: {
115
+ hasLintErrors(neu) {
116
+ this.$emit('validationChanged', !neu);
117
+ }
118
+ },
119
+
107
120
  methods: {
121
+ /**
122
+ * Codemirror yaml linting uses js-yaml parse
123
+ * it does not distinguish between warnings and errors so we will treat all yaml lint messages as errors
124
+ * other codemirror linters (eg json) will report from, to, severity where severity may be 'warning' or 'error'
125
+ * only 'error' level linting will trigger a validation event from this component
126
+ */
127
+ handleLintErrors(diagnostics = []) {
128
+ const hasLintErrors = diagnostics.filter((d) => !d.severity || d.severity === 'error').length > 0;
129
+
130
+ this.hasLintErrors = hasLintErrors;
131
+ },
132
+
108
133
  focus() {
109
134
  if ( this.$refs.codeMirrorRef ) {
110
135
  this.$refs.codeMirrorRef.cminstance.focus();
@@ -118,6 +143,8 @@ export default {
118
143
  },
119
144
 
120
145
  onReady(codeMirrorRef) {
146
+ this.$emit('validationChanged', true);
147
+
121
148
  this.$nextTick(() => {
122
149
  codeMirrorRef.refresh();
123
150
  this.codeMirrorRef = codeMirrorRef;
@@ -110,6 +110,8 @@ export default {
110
110
  <router-link
111
111
  v-if="link.value.startsWith('/') "
112
112
  :to="link.value"
113
+ role="link"
114
+ :aria-label="link.label"
113
115
  >
114
116
  {{ link.label }}
115
117
  </router-link>
@@ -118,6 +120,8 @@ export default {
118
120
  :href="link.value"
119
121
  rel="noopener noreferrer nofollow"
120
122
  target="_blank"
123
+ role="link"
124
+ :aria-label="link.label"
121
125
  > {{ link.label }} </a>
122
126
  </div>
123
127
  <slot />
@@ -127,7 +131,11 @@ export default {
127
131
  >
128
132
  <a
129
133
  class="link"
134
+ tabindex="0"
135
+ :aria-label="t('footer.wechat.title')"
136
+ role="link"
130
137
  @click="show"
138
+ @keyup.enter="show"
131
139
  >
132
140
  {{ t('footer.wechat.title') }}
133
141
  </a>
@@ -147,7 +155,12 @@ export default {
147
155
  <div>
148
156
  <button
149
157
  class="btn role-primary"
158
+ tabindex="0"
159
+ :aria-label="t('generic.close')"
160
+ role="button"
150
161
  @click="close"
162
+ @keyup.enter="close"
163
+ @keyup.space="close"
151
164
  >
152
165
  {{ t('generic.close') }}
153
166
  </button>
@@ -120,6 +120,11 @@ export default {
120
120
  default: () => []
121
121
  },
122
122
 
123
+ stepsOptions: {
124
+ type: Object,
125
+ default: () => ({ editFirstStep: true })
126
+ },
127
+
123
128
  // The set of labels to display for the finish AsyncButton
124
129
  finishMode: {
125
130
  type: String,
@@ -562,6 +567,7 @@ export default {
562
567
  ref="Wizard"
563
568
  :header-mode="mode"
564
569
  :steps="steps"
570
+ :edit-first-step="stepsOptions.editFirstStep"
565
571
  :errors="errors"
566
572
  :finish-mode="finishMode"
567
573
  class="wizard"
@@ -106,7 +106,10 @@ export default {
106
106
  <div class="growl-text-title">
107
107
  {{ growl.title }}
108
108
  </div>
109
- <p v-if="growl.message">
109
+ <p
110
+ v-if="growl.message"
111
+ :class="{ 'has-title': !!growl.title }"
112
+ >
110
113
  {{ growl.message }}
111
114
  </p>
112
115
  </div>
@@ -153,12 +156,16 @@ export default {
153
156
  word-break: break-all;
154
157
  box-shadow: 0 3px 5px 0px var(--shadow);
155
158
 
159
+ $growl-icon-size: 20px;
160
+
156
161
  .icon-container {
157
162
  align-self: center;
158
163
  flex-basis: 10%;
159
164
  padding: 10px 20px 10px 10px;
160
165
  i {
161
- font-size: 24px;
166
+ font-size: $growl-icon-size;
167
+ width: $growl-icon-size;
168
+ height: $growl-icon-size;
162
169
  }
163
170
  }
164
171
 
@@ -183,11 +190,14 @@ export default {
183
190
  }
184
191
  .growl-text-title {
185
192
  font-size: 16px;
186
- margin-bottom: 20px;
187
193
  }
188
194
 
189
195
  > P {
190
- margin-top: 5px;
196
+ padding-top: 2px;
197
+
198
+ &.has-title {
199
+ margin-top: 5px;
200
+ }
191
201
  }
192
202
  }
193
203
  }
@@ -11,7 +11,11 @@ export default {
11
11
  mode: {
12
12
  type: String,
13
13
  default: ''
14
- },
14
+ }
15
+ },
16
+
17
+ data() {
18
+ return { isLocaleSelectorOpen: false };
15
19
  },
16
20
 
17
21
  computed: {
@@ -40,8 +44,15 @@ export default {
40
44
  },
41
45
 
42
46
  methods: {
47
+ openLocaleSelector() {
48
+ this.isLocaleSelectorOpen = true;
49
+ },
50
+ closeLocaleSelector() {
51
+ this.isLocaleSelectorOpen = false;
52
+ },
43
53
  switchLocale($event) {
44
54
  this.$store.dispatch('i18n/switchTo', $event);
55
+ this.closeLocaleSelector();
45
56
  },
46
57
  }
47
58
  };
@@ -50,13 +61,28 @@ export default {
50
61
  <template>
51
62
  <div>
52
63
  <div v-if="mode === 'login'">
53
- <div v-if="showLocale">
64
+ <div
65
+ v-if="showLocale"
66
+ role="menu"
67
+ :aria-label="t('locale.menu')"
68
+ class="locale-login-container"
69
+ tabindex="0"
70
+ @click="openLocaleSelector"
71
+ @blur.capture="closeLocaleSelector"
72
+ @keyup.enter="openLocaleSelector"
73
+ @keyup.space="openLocaleSelector"
74
+ >
54
75
  <v-dropdown
55
76
  popperClass="localeSelector"
77
+ :shown="isLocaleSelectorOpen"
56
78
  placement="top"
57
79
  distance="8"
58
80
  skidding="12"
59
- :triggers="['click']"
81
+ :triggers="[]"
82
+ :autoHide="false"
83
+ :flip="false"
84
+ :container="false"
85
+ @focus.capture="openLocaleSelector"
60
86
  >
61
87
  <a
62
88
  data-testid="locale-selector"
@@ -74,13 +100,21 @@ export default {
74
100
  v-if="showNone"
75
101
  v-t="'locale.none'"
76
102
  class="hand"
77
- @click="switchLocale('none')"
103
+ tabindex="0"
104
+ role="menuitem"
105
+ @click.stop="switchLocale('none')"
106
+ @keyup.enter.stop="switchLocale('none')"
107
+ @keyup.space.stop="switchLocale('none')"
78
108
  />
79
109
  <li
80
110
  v-for="(label, name) in availableLocales"
81
111
  :key="name"
112
+ tabindex="0"
113
+ role="menuitem"
82
114
  class="hand"
83
- @click="switchLocale(name)"
115
+ @click.stop="switchLocale(name)"
116
+ @keyup.enter.stop="switchLocale(name)"
117
+ @keyup.space.stop="switchLocale(name)"
84
118
  >
85
119
  {{ label }}
86
120
  </li>
@@ -114,6 +148,11 @@ export default {
114
148
  border-radius: 4px;
115
149
  }
116
150
 
151
+ .hand:focus-visible {
152
+ @include focus-outline;
153
+ outline-offset: 4px;
154
+ }
155
+
117
156
  .locale-chooser {
118
157
  cursor: pointer;
119
158
 
@@ -121,4 +160,9 @@ export default {
121
160
  text-decoration: none;
122
161
  }
123
162
  }
163
+
164
+ .locale-login-container:focus-visible {
165
+ @include focus-outline;
166
+ outline-offset: 2px;
167
+ }
124
168
  </style>
@@ -93,8 +93,8 @@ export default defineComponent({
93
93
  },
94
94
 
95
95
  computed: {
96
- safeHeaders() {
97
- const customHeaders = this.canPaginate ? this.paginationHeaders : this.headers;
96
+ safeHeaders(): any[] {
97
+ const customHeaders: any[] = this.canPaginate ? this.paginationHeaders : this.headers;
98
98
 
99
99
  return customHeaders || this.$store.getters['type-map/headersFor'](this.schema, this.canPaginate);
100
100
  }
@@ -109,7 +109,7 @@ export default defineComponent({
109
109
  v-bind="$attrs"
110
110
  :schema="schema"
111
111
  :rows="rows"
112
- :alt-loading="canPaginate"
112
+ :alt-loading="canPaginate && !isFirstLoad"
113
113
  :loading="loading"
114
114
  :groupable="groupable"
115
115
 
@@ -124,6 +124,7 @@ export default defineComponent({
124
124
  <template
125
125
  v-for="(_, slot) of $slots"
126
126
  v-slot:[slot]="scope"
127
+ :key="slot"
127
128
  >
128
129
  <slot
129
130
  :name="slot"
@@ -443,6 +443,9 @@ export default {
443
443
  <router-link
444
444
  v-if="location"
445
445
  :to="location"
446
+ role="link"
447
+ class="masthead-resource-list-link"
448
+ :aria-label="parent.displayName"
446
449
  >
447
450
  {{ parent.displayName }}:
448
451
  </router-link>
@@ -584,10 +587,10 @@ export default {
584
587
  }
585
588
 
586
589
  HEADER {
587
- margin: 0;
590
+ margin: 0 0 0 -5px;
588
591
 
589
592
  .title {
590
- overflow: hidden;
593
+ overflow-x: hidden;
591
594
  }
592
595
  }
593
596
 
@@ -598,7 +601,7 @@ export default {
598
601
 
599
602
  h1 {
600
603
  margin: 0;
601
- overflow: hidden;
604
+ overflow-x: hidden;
602
605
  display: flex;
603
606
  flex-direction: row;
604
607
  align-items: center;
@@ -606,9 +609,13 @@ export default {
606
609
  .masthead-resource-title {
607
610
  padding: 0 8px;
608
611
  text-overflow: ellipsis;
609
- overflow: hidden;
612
+ overflow-x: hidden;
610
613
  white-space: nowrap;
611
614
  }
615
+
616
+ .masthead-resource-list-link {
617
+ margin: 5px;
618
+ }
612
619
  }
613
620
  }
614
621