mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
A regression introduced in 32c8aa0aad
incorrectly passes label to the trigger component, but also passes
translatedLabel instead of label to the menu.
The existing test was checking for the presence of "label", but it was
actually returning true because the test was showing "en.label". The
test has been modified in consequences.
203 lines
5.5 KiB
Plaintext
203 lines
5.5 KiB
Plaintext
import Component from "@glimmer/component";
|
|
import { concat } from "@ember/helper";
|
|
import { on } from "@ember/modifier";
|
|
import { action } from "@ember/object";
|
|
import { getOwner } from "@ember/owner";
|
|
import { service } from "@ember/service";
|
|
import curryComponent from "ember-curry-component";
|
|
import { modifier } from "ember-modifier";
|
|
import { and } from "truth-helpers";
|
|
import DButton from "discourse/components/d-button";
|
|
import DModal from "discourse/components/d-modal";
|
|
import concatClass from "discourse/helpers/concat-class";
|
|
import { isTesting } from "discourse/lib/environment";
|
|
import DFloatBody from "float-kit/components/d-float-body";
|
|
import { MENU } from "float-kit/lib/constants";
|
|
import DMenuInstance from "float-kit/lib/d-menu-instance";
|
|
|
|
export default class DMenu extends Component {
|
|
@service menu;
|
|
@service site;
|
|
|
|
menuInstance = new DMenuInstance(getOwner(this), {
|
|
...this.allowedProperties,
|
|
autoUpdate: true,
|
|
listeners: true,
|
|
});
|
|
|
|
registerTrigger = modifier((domElement) => {
|
|
this.menuInstance.trigger = domElement;
|
|
this.options.onRegisterApi?.(this.menuInstance);
|
|
|
|
return () => {
|
|
this.menuInstance.destroy();
|
|
};
|
|
});
|
|
|
|
registerFloatBody = modifier((domElement) => {
|
|
this.body = domElement;
|
|
|
|
return () => {
|
|
this.body = null;
|
|
};
|
|
});
|
|
|
|
@action
|
|
teardownFloatBody() {
|
|
this.body = null;
|
|
}
|
|
|
|
@action
|
|
forwardTabToContent(event) {
|
|
if (!this.body) {
|
|
return;
|
|
}
|
|
|
|
if (event.key === "Tab") {
|
|
event.preventDefault();
|
|
|
|
const firstFocusable = this.body.querySelector(
|
|
'button, a, input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
);
|
|
|
|
firstFocusable?.focus() || this.body.focus();
|
|
}
|
|
}
|
|
|
|
get menuId() {
|
|
return `d-menu-${this.menuInstance.id}`;
|
|
}
|
|
|
|
get options() {
|
|
return this.menuInstance?.options ?? {};
|
|
}
|
|
|
|
get componentArgs() {
|
|
return {
|
|
close: this.menuInstance.close,
|
|
data: this.options.data,
|
|
};
|
|
}
|
|
|
|
get triggerComponent() {
|
|
const instance = this;
|
|
const baseArguments = {
|
|
get icon() {
|
|
return instance.args.icon;
|
|
},
|
|
get translatedLabel() {
|
|
return instance.args.label;
|
|
},
|
|
get translatedAriaLabel() {
|
|
return instance.args.ariaLabel;
|
|
},
|
|
get translatedTitle() {
|
|
return instance.args.title;
|
|
},
|
|
get disabled() {
|
|
return instance.args.disabled;
|
|
},
|
|
get isLoading() {
|
|
return instance.args.isLoading;
|
|
},
|
|
};
|
|
|
|
return (
|
|
this.args.triggerComponent ||
|
|
curryComponent(DButton, baseArguments, getOwner(this))
|
|
);
|
|
}
|
|
|
|
get allowedProperties() {
|
|
const properties = {};
|
|
for (const [key, value] of Object.entries(MENU.options)) {
|
|
properties[key] = this.args[key] ?? value;
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
<template>
|
|
<this.triggerComponent
|
|
{{this.registerTrigger}}
|
|
class={{concatClass
|
|
"fk-d-menu__trigger"
|
|
(if this.menuInstance.expanded "-expanded")
|
|
(concat this.options.identifier "-trigger")
|
|
@triggerClass
|
|
@class
|
|
}}
|
|
id={{this.menuInstance.id}}
|
|
data-identifier={{this.options.identifier}}
|
|
data-trigger
|
|
aria-expanded={{if this.menuInstance.expanded "true" "false"}}
|
|
{{on "keydown" this.forwardTabToContent}}
|
|
...attributes
|
|
>
|
|
{{#if (has-block "trigger")}}
|
|
{{yield this.componentArgs to="trigger"}}
|
|
{{/if}}
|
|
</this.triggerComponent>
|
|
|
|
{{#if this.menuInstance.expanded}}
|
|
{{#if (and this.site.mobileView this.options.modalForMobile)}}
|
|
<DModal
|
|
@closeModal={{this.menuInstance.close}}
|
|
@hideHeader={{true}}
|
|
@autofocus={{this.options.autofocus}}
|
|
class={{concatClass
|
|
"fk-d-menu-modal"
|
|
(concat this.options.identifier "-content")
|
|
@contentClass
|
|
@class
|
|
}}
|
|
@inline={{(isTesting)}}
|
|
data-identifier={{this.options.identifier}}
|
|
data-content
|
|
>
|
|
<div class="fk-d-menu-modal__grip" aria-hidden="true"></div>
|
|
{{#if (has-block)}}
|
|
{{yield this.componentArgs}}
|
|
{{else if (has-block "content")}}
|
|
{{yield this.componentArgs to="content"}}
|
|
{{else if this.options.component}}
|
|
<this.options.component
|
|
@data={{this.options.data}}
|
|
@close={{this.menuInstance.close}}
|
|
/>
|
|
{{else if this.options.content}}
|
|
{{this.options.content}}
|
|
{{/if}}
|
|
</DModal>
|
|
{{else}}
|
|
<DFloatBody
|
|
@instance={{this.menuInstance}}
|
|
@trapTab={{this.options.trapTab}}
|
|
@mainClass={{concatClass
|
|
"fk-d-menu"
|
|
(concat this.options.identifier "-content")
|
|
@class
|
|
@contentClass
|
|
}}
|
|
@innerClass="fk-d-menu__inner-content"
|
|
@role="dialog"
|
|
@inline={{this.options.inline}}
|
|
{{this.registerFloatBody}}
|
|
>
|
|
{{#if (has-block)}}
|
|
{{yield this.componentArgs}}
|
|
{{else if (has-block "content")}}
|
|
{{yield this.componentArgs to="content"}}
|
|
{{else if this.options.component}}
|
|
<this.options.component
|
|
@data={{this.options.data}}
|
|
@close={{this.menuInstance.close}}
|
|
/>
|
|
{{else if this.options.content}}
|
|
{{this.options.content}}
|
|
{{/if}}
|
|
</DFloatBody>
|
|
{{/if}}
|
|
{{/if}}
|
|
</template>
|
|
}
|