@rancher/shell 3.0.4 → 3.0.5-rc.2
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/assets/images/providers/sks.svg +1 -0
- package/assets/styles/base/_basic.scss +6 -0
- package/assets/styles/base/_helpers.scss +4 -0
- package/assets/styles/base/_variables.scss +1 -0
- package/assets/styles/global/_button.scss +1 -0
- package/assets/translations/en-us.yaml +65 -15
- package/assets/translations/zh-hans.yaml +4 -3
- package/chart/monitoring/index.vue +3 -1
- package/cloud-credential/aws.vue +2 -0
- package/components/ActionDropdownShell.vue +71 -0
- package/components/AppModal.vue +18 -4
- package/components/AsyncButton.vue +24 -7
- package/components/BannerGraphic.vue +1 -0
- package/components/CommunityLinks.vue +4 -59
- package/components/CopyToClipboardText.vue +2 -1
- package/components/CruResource.vue +6 -1
- package/components/DetailText.vue +5 -0
- package/components/ExplorerMembers.vue +1 -1
- package/components/ExplorerProjectsNamespaces.vue +68 -18
- package/components/GlobalRoleBindings.vue +5 -1
- package/components/GrowlManager.vue +1 -0
- package/components/LandingPagePreference.vue +7 -3
- package/components/LocaleSelector.vue +39 -95
- package/components/ModalManager.vue +55 -0
- package/components/ModalWithCard.vue +1 -0
- package/components/PromptModal.vue +47 -8
- package/components/PromptRemove.vue +1 -0
- package/components/PromptRestore.vue +1 -0
- package/components/ResourceCancelModal.vue +1 -0
- package/components/ResourceDetail/Masthead.vue +38 -12
- package/components/ResourceDetail/__tests__/Masthead.test.ts +5 -1
- package/components/ResourceDetail/index.vue +47 -12
- package/components/ResourceTable.vue +54 -19
- package/components/SideNav.vue +5 -1
- package/components/SlideInPanelManager.vue +126 -0
- package/components/SortableTable/THead.vue +5 -2
- package/components/SortableTable/actions.js +1 -1
- package/components/SortableTable/index.vue +64 -51
- package/components/SortableTable/paging.js +16 -19
- package/components/SortableTable/selection.js +0 -11
- package/components/Wizard.vue +2 -2
- package/components/__tests__/AsyncButton.test.ts +2 -2
- package/components/__tests__/ModalManager.spec.ts +176 -0
- package/components/__tests__/PromptModal.test.ts +148 -0
- package/components/__tests__/SlideInPanelManager.spec.ts +166 -0
- package/components/auth/AuthBanner.vue +13 -11
- package/components/auth/Principal.vue +1 -0
- package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
- package/components/auth/login/ldap.vue +1 -1
- package/components/fleet/FleetResources.vue +21 -6
- package/components/form/ArrayList.vue +76 -60
- package/components/form/BannerSettings.vue +17 -2
- package/components/form/ColorInput.vue +35 -6
- package/components/form/Command.vue +6 -15
- package/components/form/EnvVars.vue +16 -8
- package/components/form/HealthCheck.vue +3 -3
- package/components/form/HookOption.vue +11 -16
- package/components/form/LabeledSelect.vue +18 -22
- package/components/form/LifecycleHooks.vue +3 -3
- package/components/form/MatchExpressions.vue +14 -8
- package/components/form/NameNsDescription.vue +128 -104
- package/components/form/Networking.vue +20 -12
- package/components/form/NodeAffinity.vue +31 -23
- package/components/form/NodeScheduling.vue +13 -3
- package/components/form/NotificationSettings.vue +15 -1
- package/components/form/Password.vue +1 -0
- package/components/form/PodAffinity.vue +43 -43
- package/components/form/Probe.vue +68 -66
- package/components/form/ResourceQuota/Project.vue +5 -1
- package/components/form/ResourceSelector.vue +7 -9
- package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +16 -24
- package/components/form/SSHKnownHosts/index.vue +30 -13
- package/components/form/Security.vue +54 -56
- package/components/form/Select.vue +32 -21
- package/components/form/ShellInput.vue +5 -1
- package/components/form/Tolerations.vue +5 -1
- package/components/form/ValueFromResource.vue +134 -121
- package/components/form/WorkloadPorts.vue +18 -18
- package/components/form/__tests__/ArrayList.test.ts +5 -2
- package/components/form/__tests__/ColorInput.test.ts +35 -0
- package/components/form/__tests__/LabeledSelect.test.ts +40 -0
- package/components/form/__tests__/MatchExpressions.test.ts +12 -12
- package/components/form/__tests__/NameNsDescription.test.ts +115 -14
- package/components/form/__tests__/Probe.test.ts +12 -8
- package/components/form/__tests__/SSHKnownHosts.test.ts +22 -2
- package/components/form/__tests__/Select.test.ts +37 -0
- package/components/formatter/InternalExternalIP.vue +2 -0
- package/components/formatter/SecretData.vue +20 -7
- package/components/nav/Group.vue +27 -5
- package/components/nav/Header.vue +17 -43
- package/components/nav/NamespaceFilter.vue +134 -86
- package/components/nav/TopLevelMenu.vue +4 -5
- package/components/nav/Type.vue +12 -1
- package/components/nav/WindowManager/ContainerLogs.vue +87 -61
- package/components/nav/WindowManager/ContainerLogsActions.vue +76 -0
- package/components/templates/blank.vue +4 -1
- package/components/templates/default.vue +8 -3
- package/components/templates/home.vue +10 -1
- package/components/templates/plain.vue +10 -4
- package/composables/focusTrap.ts +12 -4
- package/composables/useRuntimeFlag.ts +29 -0
- package/config/router/routes.js +20 -13
- package/config/store.js +4 -0
- package/config/uiplugins.js +5 -1
- package/core/types.ts +12 -6
- package/detail/catalog.cattle.io.app.vue +6 -1
- package/detail/fleet.cattle.io.bundle.vue +70 -6
- package/detail/fleet.cattle.io.gitrepo.vue +1 -1
- package/detail/namespace.vue +0 -3
- package/detail/node.vue +17 -13
- package/detail/provisioning.cattle.io.cluster.vue +72 -6
- package/dialog/AddCustomBadgeDialog.vue +1 -1
- package/{pages/c/_cluster/uiplugins/AddExtensionRepos.vue → dialog/AddExtensionReposDialog.vue} +72 -42
- package/{components/AssignTo.vue → dialog/AssignToDialog.vue} +71 -80
- package/dialog/ChangePasswordDialog.vue +106 -0
- package/dialog/DeactivateDriverDialog.vue +1 -0
- package/{pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue → dialog/DeveloperLoadExtensionDialog.vue} +74 -71
- package/dialog/DisableAuthProviderDialog.vue +101 -0
- package/dialog/DrainNode.vue +1 -1
- package/{pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue → dialog/ExtensionCatalogInstallDialog.vue} +100 -88
- package/{pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue → dialog/ExtensionCatalogUninstallDialog.vue} +69 -57
- package/dialog/FeatureFlagListDialog.vue +288 -0
- package/dialog/ForceMachineRemoveDialog.vue +5 -2
- package/{components/Import.vue → dialog/ImportDialog.vue} +0 -5
- package/{pages/c/_cluster/uiplugins/InstallDialog.vue → dialog/InstallExtensionDialog.vue} +124 -106
- package/{components/form/SSHKnownHosts → dialog}/KnownHostsEditDialog.vue +52 -59
- package/dialog/MoveNamespaceDialog.vue +157 -0
- package/dialog/ScalePoolDownDialog.vue +1 -1
- package/{components/nav/Jump.vue → dialog/SearchDialog.vue} +34 -14
- package/{pages/c/_cluster/uiplugins/UninstallDialog.vue → dialog/UninstallExtensionDialog.vue} +67 -58
- package/dialog/WechatDialog.vue +57 -0
- package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
- package/edit/auth/__tests__/oidc.test.ts +152 -109
- package/edit/auth/azuread.vue +2 -1
- package/edit/auth/github.vue +1 -1
- package/edit/auth/googleoauth.vue +5 -1
- package/edit/auth/ldap/index.vue +1 -1
- package/edit/auth/oidc.vue +38 -5
- package/edit/auth/saml.vue +1 -1
- package/edit/cloudcredential.vue +24 -9
- package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
- package/edit/management.cattle.io.user.vue +28 -3
- package/edit/namespace.vue +1 -4
- package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
- package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
- package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +26 -9
- package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +8 -8
- package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +26 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +66 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/utils/rke2-test-data.ts +58 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +49 -41
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +5 -3
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +33 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
- package/edit/token.vue +2 -0
- package/edit/workload/index.vue +1 -0
- package/edit/workload/mixins/workload.js +0 -2
- package/initialize/install-plugins.js +2 -1
- package/list/harvesterhci.io.management.cluster.vue +4 -1
- package/list/management.cattle.io.feature.vue +4 -287
- package/list/provisioning.cattle.io.cluster.vue +20 -12
- package/machine-config/azure.vue +16 -4
- package/mixins/vue-select-overrides.js +0 -4
- package/models/__tests__/namespace.test.ts +25 -1
- package/models/cloudcredential.js +5 -0
- package/models/fleet.cattle.io.cluster.js +8 -2
- package/models/fleet.cattle.io.gitrepo.js +8 -34
- package/models/kontainerdriver.js +6 -3
- package/models/management.cattle.io.feature.js +7 -1
- package/models/management.cattle.io.node.js +3 -3
- package/models/namespace.js +11 -6
- package/models/nodedriver.js +6 -3
- package/models/workload.js +4 -1
- package/package.json +3 -3
- package/pages/about.vue +13 -3
- package/pages/account/index.vue +16 -6
- package/pages/auth/login.vue +18 -7
- package/pages/auth/logout.vue +4 -1
- package/pages/auth/setup.vue +2 -0
- package/pages/auth/verify.vue +13 -8
- package/pages/c/_cluster/apps/charts/chart.vue +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +26 -26
- package/pages/c/_cluster/auth/config/index.vue +10 -12
- package/pages/c/_cluster/explorer/EventsTable.vue +38 -33
- package/pages/c/_cluster/explorer/index.vue +17 -15
- package/pages/c/_cluster/istio/index.vue +2 -2
- package/pages/c/_cluster/longhorn/index.vue +1 -1
- package/pages/c/_cluster/monitoring/index.vue +1 -1
- package/pages/c/_cluster/monitoring/monitor/_namespace/_id.vue +4 -2
- package/pages/c/_cluster/monitoring/monitor/create.vue +4 -2
- package/pages/c/_cluster/monitoring/route-receiver/_id.vue +4 -2
- package/pages/c/_cluster/monitoring/route-receiver/create.vue +5 -2
- package/pages/c/_cluster/neuvector/index.vue +1 -1
- package/pages/c/_cluster/settings/banners.vue +4 -3
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +8 -10
- package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +4 -7
- package/pages/c/_cluster/uiplugins/index.vue +98 -55
- package/pages/diagnostic.vue +59 -11
- package/pages/fail-whale.vue +14 -8
- package/pages/home.vue +24 -18
- package/pages/prefs.vue +7 -6
- package/pages/support/index.vue +4 -1
- package/plugins/internal-api/index.ts +37 -0
- package/plugins/internal-api/shared/base-api.ts +13 -0
- package/plugins/internal-api/shell/shell.api.ts +108 -0
- package/plugins/steve/actions.js +0 -12
- package/public/index.html +1 -0
- package/rancher-components/Card/Card.vue +1 -1
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +59 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +27 -3
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +47 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +20 -2
- package/rancher-components/Form/Radio/RadioButton.test.ts +36 -1
- package/rancher-components/Form/Radio/RadioButton.vue +20 -4
- package/rancher-components/Form/Radio/RadioGroup.test.ts +60 -0
- package/rancher-components/Form/Radio/RadioGroup.vue +52 -10
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +17 -0
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +5 -0
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +10 -1
- package/rancher-components/RcButton/RcButton.vue +2 -1
- package/rancher-components/RcButton/types.ts +1 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +18 -6
- package/rancher-components/RcDropdown/RcDropdownItem.vue +3 -56
- package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +68 -0
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +92 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -0
- package/rancher-components/RcDropdown/index.ts +2 -0
- package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
- package/rancher-components/RcDropdown/useDropdownItem.ts +63 -0
- package/scripts/extension/bundle +20 -0
- package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +2 -1
- package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +2 -0
- package/scripts/extension/helmpatch +44 -31
- package/scripts/extension/publish +12 -12
- package/scripts/typegen.sh +2 -4
- package/server/har-file.js +25 -3
- package/store/action-menu.js +26 -56
- package/store/features.js +2 -1
- package/store/index.js +5 -0
- package/store/modal.ts +71 -0
- package/store/slideInPanel.ts +47 -0
- package/store/type-map.js +12 -1
- package/store/type-map.utils.ts +4 -4
- package/types/global-vue.d.ts +5 -0
- package/types/internal-api/shell/growl.d.ts +25 -0
- package/types/internal-api/shell/modal.d.ts +77 -0
- package/types/internal-api/shell/slideIn.d.ts +15 -0
- package/types/resources/fleet.d.ts +0 -14
- package/types/shell/index.d.ts +43 -24
- package/types/vue-shim.d.ts +4 -1
- package/utils/__mocks__/tabbable.js +13 -0
- package/utils/__tests__/object.test.ts +38 -4
- package/utils/cluster.js +35 -0
- package/utils/fleet.ts +15 -73
- package/utils/object.js +48 -5
- package/utils/validators/formRules/__tests__/index.test.ts +10 -1
- package/utils/validators/formRules/index.ts +27 -3
- package/utils/validators/machine-pool.ts +20 -0
- package/components/DisableAuthProviderModal.vue +0 -114
- package/components/MoveModal.vue +0 -166
- package/components/PromptChangePassword.vue +0 -123
- package/components/fleet/FleetBundleResources.vue +0 -86
- package/components/formatter/ExtensionCache.vue +0 -74
- package/components/formatter/Port.vue +0 -24
- package/components/formatter/SecretType.vue +0 -41
- package/types/vue-shim.d +0 -20
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { defineComponent } from 'vue';
|
|
3
3
|
import { _VIEW } from '@shell/config/query-params';
|
|
4
|
-
import {
|
|
4
|
+
import { generateRandomAlphaString } from '@shell/utils/string';
|
|
5
5
|
|
|
6
6
|
export default defineComponent({
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
inheritAttrs: false,
|
|
9
|
+
props: {
|
|
8
10
|
/**
|
|
9
11
|
* The name of the input, for grouping.
|
|
10
12
|
*/
|
|
@@ -76,7 +78,16 @@ export default defineComponent({
|
|
|
76
78
|
preventFocusOnRadioGroups: {
|
|
77
79
|
type: Boolean,
|
|
78
80
|
default: false
|
|
79
|
-
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Radio option Id - used to link to aria-activedescendant
|
|
85
|
+
* when using inside of the context of a Radio Group
|
|
86
|
+
*/
|
|
87
|
+
radioOptionId: {
|
|
88
|
+
type: String,
|
|
89
|
+
default: undefined
|
|
90
|
+
},
|
|
80
91
|
},
|
|
81
92
|
|
|
82
93
|
emits: ['update:value'],
|
|
@@ -84,7 +95,8 @@ export default defineComponent({
|
|
|
84
95
|
data() {
|
|
85
96
|
return {
|
|
86
97
|
isChecked: this.value === this.val,
|
|
87
|
-
randomString: `${
|
|
98
|
+
randomString: `${ generateRandomAlphaString(12) }-radio`,
|
|
99
|
+
describeById: `${ generateRandomAlphaString(12) }-radio-described-id`,
|
|
88
100
|
};
|
|
89
101
|
},
|
|
90
102
|
|
|
@@ -165,11 +177,14 @@ export default defineComponent({
|
|
|
165
177
|
@click.stop.prevent
|
|
166
178
|
>
|
|
167
179
|
<span
|
|
180
|
+
:id="radioOptionId"
|
|
168
181
|
ref="custom"
|
|
169
182
|
:class="[ isDisabled ? 'text-muted' : '', 'radio-custom']"
|
|
170
183
|
:tabindex="isDisabled || preventFocusOnRadioGroups ? -1 : 0"
|
|
171
184
|
:aria-label="label"
|
|
172
185
|
:aria-checked="isChecked"
|
|
186
|
+
:aria-disabled="isDisabled"
|
|
187
|
+
:aria-describedby="descriptionKey || description ? describeById : undefined"
|
|
173
188
|
role="radio"
|
|
174
189
|
/>
|
|
175
190
|
<div class="labeling">
|
|
@@ -190,6 +205,7 @@ export default defineComponent({
|
|
|
190
205
|
</label>
|
|
191
206
|
<div
|
|
192
207
|
v-if="descriptionKey || description"
|
|
208
|
+
:id="describeById"
|
|
193
209
|
class="radio-button-outer-container-description"
|
|
194
210
|
>
|
|
195
211
|
<t
|
|
@@ -24,4 +24,64 @@ describe('component: RadioGroup', () => {
|
|
|
24
24
|
expect(slot.disabled).toBe(disabled);
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
|
|
29
|
+
const inputLabel = 'some-label';
|
|
30
|
+
const ariaDescribedById = 'some-external-id';
|
|
31
|
+
const currValue = 'whatever';
|
|
32
|
+
|
|
33
|
+
const wrapper = mount(RadioGroup, {
|
|
34
|
+
propsData: {
|
|
35
|
+
name: 'some-name',
|
|
36
|
+
label: inputLabel,
|
|
37
|
+
value: currValue,
|
|
38
|
+
options: [{ label: currValue, value: currValue }]
|
|
39
|
+
},
|
|
40
|
+
attrs: { 'aria-describedby': ariaDescribedById }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const field = wrapper.find('[role="radiogroup"]');
|
|
44
|
+
const role = field.attributes('role');
|
|
45
|
+
const ariaLabel = field.attributes('aria-label');
|
|
46
|
+
const ariaDescribedBy = field.attributes('aria-describedby');
|
|
47
|
+
const ariaActiveDescendant = field.attributes('aria-activedescendant');
|
|
48
|
+
|
|
49
|
+
expect(ariaLabel).toBe(inputLabel);
|
|
50
|
+
expect(role).toBe('radiogroup');
|
|
51
|
+
expect(ariaActiveDescendant).toBe(`${ wrapper.vm.radioOptionsIdPrefix }0`);
|
|
52
|
+
expect(ariaDescribedBy).toBe(ariaDescribedById);
|
|
53
|
+
|
|
54
|
+
const radioOption = wrapper.find(`.radio-custom`);
|
|
55
|
+
|
|
56
|
+
// make sure we validate when using RadioGroup without custom slot data
|
|
57
|
+
// we do assign an ID that is important to get 'aria-activedescendant' working
|
|
58
|
+
expect(radioOption.attributes('id')).toBe(`${ wrapper.vm.radioOptionsIdPrefix }0`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('a11y: adding aria-label ($attrs) from parent should override label-based aria-label', async() => {
|
|
62
|
+
const inputLabel = 'some-label';
|
|
63
|
+
const overrideLabel = 'some-override-label';
|
|
64
|
+
const currValue = 'whatever';
|
|
65
|
+
|
|
66
|
+
const wrapper = mount(RadioGroup, {
|
|
67
|
+
propsData: {
|
|
68
|
+
name: 'some-name',
|
|
69
|
+
label: inputLabel,
|
|
70
|
+
value: currValue,
|
|
71
|
+
disabled: true,
|
|
72
|
+
options: [{ label: currValue, value: currValue }]
|
|
73
|
+
},
|
|
74
|
+
attrs: { 'aria-label': overrideLabel }
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const field = wrapper.find('[role="radiogroup"]');
|
|
78
|
+
const ariaLabel = field.attributes('aria-label');
|
|
79
|
+
const ariaDisabled = field.attributes('aria-disabled');
|
|
80
|
+
const tabIndex = field.attributes('tabindex');
|
|
81
|
+
|
|
82
|
+
expect(ariaLabel).toBe(overrideLabel);
|
|
83
|
+
expect(ariaLabel).not.toBe(inputLabel);
|
|
84
|
+
expect(ariaDisabled).toBe('true');
|
|
85
|
+
expect(tabIndex).toBe('-1');
|
|
86
|
+
});
|
|
27
87
|
});
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
import { PropType, defineComponent } from 'vue';
|
|
3
3
|
import { _VIEW } from '@shell/config/query-params';
|
|
4
4
|
import RadioButton from '@components/Form/Radio/RadioButton.vue';
|
|
5
|
+
import { generateRandomAlphaString } from '@shell/utils/string';
|
|
5
6
|
|
|
6
7
|
interface Option {
|
|
7
8
|
value: unknown,
|
|
8
9
|
label: string,
|
|
9
10
|
description?: string,
|
|
11
|
+
radioOptionId?: string,
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export default defineComponent({
|
|
@@ -106,7 +108,10 @@ export default defineComponent({
|
|
|
106
108
|
emits: ['update:value'],
|
|
107
109
|
|
|
108
110
|
data() {
|
|
109
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
currFocusedElem: undefined as undefined | EventTarget | null,
|
|
113
|
+
radioOptionsIdPrefix: `radio-option-${ generateRandomAlphaString(12) }-`
|
|
114
|
+
};
|
|
110
115
|
},
|
|
111
116
|
|
|
112
117
|
computed: {
|
|
@@ -120,16 +125,21 @@ export default defineComponent({
|
|
|
120
125
|
const opt = this.options[i];
|
|
121
126
|
|
|
122
127
|
if (typeof opt === 'object' && opt) {
|
|
123
|
-
out.push(
|
|
128
|
+
out.push({
|
|
129
|
+
...opt,
|
|
130
|
+
radioOptionId: `${ this.radioOptionsIdPrefix }${ i }`
|
|
131
|
+
});
|
|
124
132
|
} else if (this.labels) {
|
|
125
133
|
out.push({
|
|
126
|
-
label:
|
|
127
|
-
value:
|
|
134
|
+
label: this.labels[i],
|
|
135
|
+
value: opt,
|
|
136
|
+
radioOptionId: `${ this.radioOptionsIdPrefix }${ i }`
|
|
128
137
|
});
|
|
129
138
|
} else {
|
|
130
139
|
out.push({
|
|
131
|
-
label:
|
|
132
|
-
value:
|
|
140
|
+
label: opt,
|
|
141
|
+
value: opt,
|
|
142
|
+
radioOptionId: `${ this.radioOptionsIdPrefix }${ i }`
|
|
133
143
|
});
|
|
134
144
|
}
|
|
135
145
|
}
|
|
@@ -150,8 +160,36 @@ export default defineComponent({
|
|
|
150
160
|
isDisabled(): boolean {
|
|
151
161
|
return (this.disabled || this.isView);
|
|
152
162
|
},
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Radio Group Aria Label based on the label present on this input
|
|
165
|
+
*/
|
|
166
|
+
radioGroupAriaLabel(): string | undefined {
|
|
167
|
+
// seems like VoiceOver screen reader isn't really picking up aria-labelledby
|
|
168
|
+
// let's just gather the label that comes in and assign it.
|
|
169
|
+
// We allow override with $attrs['aria-label'] for more control
|
|
170
|
+
if (this.$attrs['aria-label']) {
|
|
171
|
+
return this.$attrs['aria-label'] as string || undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return this.labelKey ? this.t(this.labelKey) : this.label ? this.label : undefined;
|
|
175
|
+
},
|
|
176
|
+
/**
|
|
177
|
+
* Radio Group Aria DescribedBy parent attribute for extendability
|
|
178
|
+
*/
|
|
179
|
+
radioGroupAriaDescribedBy(): string | undefined {
|
|
180
|
+
return this.$attrs['aria-describedby'] as string || undefined;
|
|
181
|
+
},
|
|
182
|
+
/**
|
|
183
|
+
* Radio Group value for aria-activedescendant HTML prop
|
|
184
|
+
*/
|
|
185
|
+
ariaActiveDescendant(): string | undefined {
|
|
186
|
+
const activeOpt = this.normalizedOptions.find((opt) => opt.value === this.value);
|
|
187
|
+
|
|
188
|
+
if (this.value && activeOpt) {
|
|
189
|
+
return activeOpt.radioOptionId;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return '';
|
|
155
193
|
}
|
|
156
194
|
},
|
|
157
195
|
|
|
@@ -232,10 +270,13 @@ export default defineComponent({
|
|
|
232
270
|
<div
|
|
233
271
|
ref="radioGroup"
|
|
234
272
|
role="radiogroup"
|
|
235
|
-
:aria-label="
|
|
273
|
+
:aria-label="radioGroupAriaLabel"
|
|
274
|
+
:aria-describedby="radioGroupAriaDescribedBy"
|
|
275
|
+
:aria-activedescendant="ariaActiveDescendant"
|
|
236
276
|
class="radio-group"
|
|
237
277
|
:class="{'row':row}"
|
|
238
|
-
tabindex="0"
|
|
278
|
+
:tabindex="isDisabled ? -1 : 0"
|
|
279
|
+
:aria-disabled="isDisabled"
|
|
239
280
|
@keydown.down.prevent.stop="clickNext(1)"
|
|
240
281
|
@keydown.up.prevent.stop="clickNext(-1)"
|
|
241
282
|
@keydown.space.enter.stop.prevent
|
|
@@ -255,6 +296,7 @@ export default defineComponent({
|
|
|
255
296
|
:name="name"
|
|
256
297
|
:value="value"
|
|
257
298
|
:label="option.label"
|
|
299
|
+
:radio-option-id="option.radioOptionId"
|
|
258
300
|
:description="option.description"
|
|
259
301
|
:val="option.value"
|
|
260
302
|
:disabled="isDisabled"
|
|
@@ -91,4 +91,21 @@ describe('toggleSwitch.vue', () => {
|
|
|
91
91
|
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
|
92
92
|
expect(wrapper.emitted('update:value')[0][0]).toBe(offValue);
|
|
93
93
|
});
|
|
94
|
+
|
|
95
|
+
it('adds focus class when input is focused', async() => {
|
|
96
|
+
const wrapper = shallowMount(ToggleSwitch);
|
|
97
|
+
|
|
98
|
+
await wrapper.find('input').trigger('focus');
|
|
99
|
+
|
|
100
|
+
expect(wrapper.find('.slider').classes()).toContain('focus');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('removes focus class when input is blurred', async() => {
|
|
104
|
+
const wrapper = shallowMount(ToggleSwitch);
|
|
105
|
+
|
|
106
|
+
await wrapper.find('input').trigger('focus');
|
|
107
|
+
await wrapper.find('input').trigger('blur');
|
|
108
|
+
|
|
109
|
+
expect(wrapper.find('.slider').classes()).not.toContain('focus');
|
|
110
|
+
});
|
|
94
111
|
});
|
|
@@ -26,7 +26,15 @@ export default defineComponent({
|
|
|
26
26
|
hover: {
|
|
27
27
|
type: Boolean,
|
|
28
28
|
default: true
|
|
29
|
-
}
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Inherited global identifier prefix for tests
|
|
32
|
+
* Define a term based on the parent component to avoid conflicts on multiple components
|
|
33
|
+
*/
|
|
34
|
+
componentTestid: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: 'labeledTooltip-info-icon'
|
|
37
|
+
},
|
|
30
38
|
},
|
|
31
39
|
computed: {
|
|
32
40
|
iconClass(): string {
|
|
@@ -64,6 +72,7 @@ export default defineComponent({
|
|
|
64
72
|
:class="{'hover':!value, [iconClass]: true}"
|
|
65
73
|
class="icon status-icon"
|
|
66
74
|
tabindex="0"
|
|
75
|
+
:data-testid="componentTestid"
|
|
67
76
|
/>
|
|
68
77
|
</template>
|
|
69
78
|
<template v-else>
|
|
@@ -15,6 +15,7 @@ const buttonRoles: { role: keyof ButtonRoleProps, className: string }[] = [
|
|
|
15
15
|
{ role: 'secondary', className: 'role-secondary' },
|
|
16
16
|
{ role: 'tertiary', className: 'role-tertiary' },
|
|
17
17
|
{ role: 'link', className: 'role-link' },
|
|
18
|
+
{ role: 'multiAction', className: 'role-multi-action' },
|
|
18
19
|
{ role: 'ghost', className: 'role-ghost' },
|
|
19
20
|
];
|
|
20
21
|
|
|
@@ -50,7 +51,7 @@ defineExpose({ focus });
|
|
|
50
51
|
<button
|
|
51
52
|
ref="RcFocusTarget"
|
|
52
53
|
role="button"
|
|
53
|
-
:class="{ ...buttonClass
|
|
54
|
+
:class="{ ...buttonClass }"
|
|
54
55
|
>
|
|
55
56
|
<slot name="before">
|
|
56
57
|
<!-- Empty Content -->
|
|
@@ -24,9 +24,18 @@ import { ref } from 'vue';
|
|
|
24
24
|
import { useClickOutside } from '@shell/composables/useClickOutside';
|
|
25
25
|
import { useDropdownContext } from '@components/RcDropdown/useDropdownContext';
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
import type { Placement } from 'floating-vue';
|
|
28
|
+
|
|
29
|
+
withDefaults(
|
|
30
|
+
defineProps<{
|
|
31
|
+
// eslint-disable-next-line vue/require-default-prop
|
|
32
|
+
ariaLabel?: string;
|
|
33
|
+
// eslint-disable-next-line vue/require-default-prop
|
|
34
|
+
distance?: number;
|
|
35
|
+
placement?: Placement;
|
|
36
|
+
}>(),
|
|
37
|
+
{ placement: 'bottom-end' }
|
|
38
|
+
);
|
|
30
39
|
|
|
31
40
|
const emit = defineEmits(['update:open']);
|
|
32
41
|
|
|
@@ -49,7 +58,7 @@ useClickOutside(dropdownTarget, () => showMenu(false));
|
|
|
49
58
|
|
|
50
59
|
const applyShow = () => {
|
|
51
60
|
registerDropdownCollection(dropdownTarget.value);
|
|
52
|
-
setFocus();
|
|
61
|
+
setFocus('down');
|
|
53
62
|
};
|
|
54
63
|
|
|
55
64
|
</script>
|
|
@@ -61,7 +70,8 @@ const applyShow = () => {
|
|
|
61
70
|
:shown="isMenuOpen"
|
|
62
71
|
:auto-hide="false"
|
|
63
72
|
:container="popperContainer"
|
|
64
|
-
:placement="
|
|
73
|
+
:placement="placement"
|
|
74
|
+
:distance="distance"
|
|
65
75
|
@apply-show="applyShow"
|
|
66
76
|
>
|
|
67
77
|
<slot name="default">
|
|
@@ -78,7 +88,8 @@ const applyShow = () => {
|
|
|
78
88
|
dropdown-menu-collection
|
|
79
89
|
:aria-label="ariaLabel || 'Dropdown Menu'"
|
|
80
90
|
@keydown="handleKeydown"
|
|
81
|
-
@keydown.down="setFocus()"
|
|
91
|
+
@keydown.down.prevent="setFocus('down')"
|
|
92
|
+
@keydown.up.prevent="setFocus('up')"
|
|
82
93
|
>
|
|
83
94
|
<slot name="dropdownCollection">
|
|
84
95
|
<!--Empty slot content-->
|
|
@@ -110,6 +121,7 @@ const applyShow = () => {
|
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
.v-popper__inner {
|
|
124
|
+
overflow: unset;
|
|
113
125
|
padding: 10px 0 10px 0;
|
|
114
126
|
}
|
|
115
127
|
}
|
|
@@ -2,56 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* An item for a dropdown menu. Used in conjunction with RcDropdown.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { DropdownContext, defaultContext } from './types';
|
|
5
|
+
import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
|
|
7
6
|
|
|
8
7
|
const props = defineProps({ disabled: Boolean });
|
|
9
8
|
const emits = defineEmits(['click']);
|
|
10
9
|
|
|
11
|
-
const { close,
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Handles keydown events to navigate between dropdown items.
|
|
15
|
-
* @param {KeyboardEvent} e - The keydown event.
|
|
16
|
-
*/
|
|
17
|
-
const handleKeydown = (e: KeyboardEvent) => {
|
|
18
|
-
const activeItem = document.activeElement;
|
|
19
|
-
|
|
20
|
-
const activeIndex = dropdownItems.value.indexOf(activeItem || new HTMLElement());
|
|
21
|
-
|
|
22
|
-
if (activeIndex < 0) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const shouldAdvance = e.key === 'ArrowDown';
|
|
27
|
-
|
|
28
|
-
const newIndex = findNewIndex(shouldAdvance, activeIndex, dropdownItems.value);
|
|
29
|
-
|
|
30
|
-
if (dropdownItems.value[newIndex] instanceof HTMLElement) {
|
|
31
|
-
dropdownItems.value[newIndex].focus();
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Finds the new index for the dropdown item based on the key pressed.
|
|
37
|
-
* @param shouldAdvance - Whether to advance to the next or previous item.
|
|
38
|
-
* @param activeIndex - Current active index.
|
|
39
|
-
* @param itemsArr - Array of dropdown items.
|
|
40
|
-
* @returns The new index.
|
|
41
|
-
*/
|
|
42
|
-
const findNewIndex = (shouldAdvance: boolean, activeIndex: number, itemsArr: Element[]) => {
|
|
43
|
-
const newIndex = shouldAdvance ? activeIndex + 1 : activeIndex - 1;
|
|
44
|
-
|
|
45
|
-
if (newIndex > itemsArr.length - 1) {
|
|
46
|
-
return 0;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (newIndex < 0) {
|
|
50
|
-
return itemsArr.length - 1;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return newIndex;
|
|
54
|
-
};
|
|
10
|
+
const { handleKeydown, close, handleActivate } = useDropdownItem();
|
|
55
11
|
|
|
56
12
|
const handleClick = (e: MouseEvent) => {
|
|
57
13
|
if (props.disabled) {
|
|
@@ -62,15 +18,6 @@ const handleClick = (e: MouseEvent) => {
|
|
|
62
18
|
close();
|
|
63
19
|
};
|
|
64
20
|
|
|
65
|
-
/**
|
|
66
|
-
* Handles keydown events to activate the dropdown item.
|
|
67
|
-
* @param e - The keydown event.
|
|
68
|
-
*/
|
|
69
|
-
const handleActivate = (e: KeyboardEvent) => {
|
|
70
|
-
if (e?.target instanceof HTMLElement) {
|
|
71
|
-
e?.target?.click();
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
21
|
</script>
|
|
75
22
|
|
|
76
23
|
<template>
|
|
@@ -83,7 +30,7 @@ const handleActivate = (e: KeyboardEvent) => {
|
|
|
83
30
|
:aria-disabled="disabled || false"
|
|
84
31
|
@click.stop="handleClick"
|
|
85
32
|
@keydown.enter.space="handleActivate"
|
|
86
|
-
@keydown.up.down.stop="handleKeydown"
|
|
33
|
+
@keydown.up.down.prevent.stop="handleKeydown"
|
|
87
34
|
>
|
|
88
35
|
<slot name="before">
|
|
89
36
|
<!--Empty slot content-->
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* An item for a dropdown menu. Used in conjunction with RcDropdown.
|
|
4
|
+
*/
|
|
5
|
+
import { Checkbox as RcCheckbox } from '@components/Form/Checkbox';
|
|
6
|
+
import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
|
|
7
|
+
|
|
8
|
+
const props = defineProps({ modelValue: Boolean, disabled: Boolean });
|
|
9
|
+
const emits = defineEmits(['click']);
|
|
10
|
+
|
|
11
|
+
const { handleKeydown, handleActivate } = useDropdownItem();
|
|
12
|
+
|
|
13
|
+
const handleClick = () => {
|
|
14
|
+
if (props.disabled) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
emits('click', !props.modelValue);
|
|
19
|
+
};
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div
|
|
24
|
+
ref="dropdownMenuItem"
|
|
25
|
+
dropdown-menu-item
|
|
26
|
+
tabindex="-1"
|
|
27
|
+
role="menuitemcheckbox"
|
|
28
|
+
:disabled="disabled || null"
|
|
29
|
+
:aria-disabled="disabled || false"
|
|
30
|
+
@click.stop="handleClick"
|
|
31
|
+
@keydown.enter.space="handleActivate"
|
|
32
|
+
@keydown.up.down.prevent.stop="handleKeydown"
|
|
33
|
+
>
|
|
34
|
+
<rc-checkbox :value="modelValue">
|
|
35
|
+
<template #label>
|
|
36
|
+
<slot name="default">
|
|
37
|
+
<!--Empty slot content-->
|
|
38
|
+
</slot>
|
|
39
|
+
</template>
|
|
40
|
+
</rc-checkbox>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<style lang="scss" scoped>
|
|
45
|
+
[dropdown-menu-item] {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 8px;
|
|
48
|
+
align-items: center;
|
|
49
|
+
padding: 9px 8px;
|
|
50
|
+
margin: 0 9px;
|
|
51
|
+
border-radius: 4px;
|
|
52
|
+
|
|
53
|
+
&:hover {
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
background-color: var(--dropdown-hover-bg);
|
|
56
|
+
}
|
|
57
|
+
&:focus-visible, &:focus {
|
|
58
|
+
@include focus-outline;
|
|
59
|
+
outline-offset: 0;
|
|
60
|
+
}
|
|
61
|
+
&[disabled] {
|
|
62
|
+
color: var(--disabled-text);
|
|
63
|
+
&:hover {
|
|
64
|
+
cursor: not-allowed;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* An item for a dropdown menu. Used in conjunction with RcDropdown.
|
|
4
|
+
*/
|
|
5
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
6
|
+
import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
|
|
7
|
+
import { ref } from 'vue';
|
|
8
|
+
|
|
9
|
+
type LabeledSelectComponent = {
|
|
10
|
+
focusSearch: () => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
defineProps({
|
|
14
|
+
modelValue: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: ''
|
|
17
|
+
},
|
|
18
|
+
disabled: Boolean,
|
|
19
|
+
options: {
|
|
20
|
+
type: Array,
|
|
21
|
+
default() {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const emits = defineEmits(['click', 'select']);
|
|
27
|
+
|
|
28
|
+
const { handleKeydown, handleActivate } = useDropdownItem();
|
|
29
|
+
|
|
30
|
+
const dropdownMenuItem = ref<HTMLDivElement | null>(null);
|
|
31
|
+
const menuItemSelect = ref<LabeledSelectComponent | null>(null);
|
|
32
|
+
|
|
33
|
+
const handleClick = () => {
|
|
34
|
+
menuItemSelect?.value?.focusSearch();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const focusMenuItem = () => {
|
|
38
|
+
dropdownMenuItem?.value?.focus();
|
|
39
|
+
};
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<div
|
|
44
|
+
ref="dropdownMenuItem"
|
|
45
|
+
dropdown-menu-item
|
|
46
|
+
tabindex="-1"
|
|
47
|
+
role="menuitem"
|
|
48
|
+
:disabled="disabled || null"
|
|
49
|
+
:aria-disabled="disabled || false"
|
|
50
|
+
@click.stop="handleClick"
|
|
51
|
+
@keydown.enter.space="handleActivate"
|
|
52
|
+
@keydown.up.down.prevent.stop="handleKeydown"
|
|
53
|
+
>
|
|
54
|
+
<LabeledSelect
|
|
55
|
+
ref="menuItemSelect"
|
|
56
|
+
:value="modelValue"
|
|
57
|
+
:label="t('wm.containerLogs.range.label')"
|
|
58
|
+
:options="options"
|
|
59
|
+
:clearable="false"
|
|
60
|
+
placement="top"
|
|
61
|
+
@keydown.enter.stop
|
|
62
|
+
@update:value="emits('select', $event)"
|
|
63
|
+
@on-close="focusMenuItem"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<style lang="scss" scoped>
|
|
69
|
+
[dropdown-menu-item] {
|
|
70
|
+
display: flex;
|
|
71
|
+
gap: 8px;
|
|
72
|
+
align-items: center;
|
|
73
|
+
padding: 9px 8px;
|
|
74
|
+
margin: 0 9px;
|
|
75
|
+
border-radius: 4px;
|
|
76
|
+
|
|
77
|
+
&:hover {
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
background-color: var(--dropdown-hover-bg);
|
|
80
|
+
}
|
|
81
|
+
&:focus-visible, &:focus {
|
|
82
|
+
@include focus-outline;
|
|
83
|
+
outline-offset: 0;
|
|
84
|
+
}
|
|
85
|
+
&[disabled] {
|
|
86
|
+
color: var(--disabled-text);
|
|
87
|
+
&:hover {
|
|
88
|
+
cursor: not-allowed;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -35,8 +35,18 @@ defineExpose({ focus });
|
|
|
35
35
|
@keydown.enter.space="handleKeydown"
|
|
36
36
|
@click="showMenu(true)"
|
|
37
37
|
>
|
|
38
|
+
<template #before>
|
|
39
|
+
<slot name="before">
|
|
40
|
+
<!-- Empty Content -->
|
|
41
|
+
</slot>
|
|
42
|
+
</template>
|
|
38
43
|
<slot name="default">
|
|
39
44
|
<!--Empty slot content-->
|
|
40
45
|
</slot>
|
|
46
|
+
<template #after>
|
|
47
|
+
<slot name="after">
|
|
48
|
+
<!-- Empty Content -->
|
|
49
|
+
</slot>
|
|
50
|
+
</template>
|
|
41
51
|
</RcButton>
|
|
42
52
|
</template>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { default as RcDropdown } from './RcDropdown.vue';
|
|
2
2
|
export { default as RcDropdownItem } from './RcDropdownItem.vue';
|
|
3
|
+
export { default as RcDropdownItemCheckbox } from './RcDropdownItemCheckbox.vue';
|
|
4
|
+
export { default as RcDropdownItemSelect } from './RcDropdownItemSelect.vue';
|
|
3
5
|
export { default as RcDropdownSeparator } from './RcDropdownSeparator.vue';
|
|
4
6
|
export { default as RcDropdownTrigger } from './RcDropdownTrigger.vue';
|
|
5
7
|
export { default as RcDropdownMenu } from './RcDropdownMenu.vue';
|
|
@@ -10,6 +10,7 @@ export const useDropdownCollection = () => {
|
|
|
10
10
|
const dropdownItems = ref<Element[]>([]);
|
|
11
11
|
const dropdownContainer = ref<HTMLElement | null>(null);
|
|
12
12
|
const firstDropdownItem = ref<HTMLElement | null>(null);
|
|
13
|
+
const lastDropdownItem = ref<HTMLElement | null>(null);
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Registers the dropdown container and initializes dropdown items.
|
|
@@ -22,6 +23,12 @@ export const useDropdownCollection = () => {
|
|
|
22
23
|
if (dropdownItems.value[0] instanceof HTMLElement) {
|
|
23
24
|
firstDropdownItem.value = dropdownItems.value[0];
|
|
24
25
|
}
|
|
26
|
+
|
|
27
|
+
const lastItem = dropdownItems.value[dropdownItems.value.length - 1];
|
|
28
|
+
|
|
29
|
+
if (lastItem instanceof HTMLElement) {
|
|
30
|
+
lastDropdownItem.value = lastItem;
|
|
31
|
+
}
|
|
25
32
|
}
|
|
26
33
|
};
|
|
27
34
|
|
|
@@ -40,6 +47,7 @@ export const useDropdownCollection = () => {
|
|
|
40
47
|
return {
|
|
41
48
|
dropdownItems,
|
|
42
49
|
firstDropdownItem,
|
|
50
|
+
lastDropdownItem,
|
|
43
51
|
dropdownContainer,
|
|
44
52
|
registerDropdownCollection,
|
|
45
53
|
};
|