DEV: Convert d-modal and d-modal-body to glimmer components
This commit is contained in:
parent
11e7e949b7
commit
771c4de7f1
|
@ -0,0 +1,10 @@
|
||||||
|
<div
|
||||||
|
id={{@id}}
|
||||||
|
class={{concat-class "modal-body" @class}}
|
||||||
|
tabindex="-1"
|
||||||
|
{{did-insert this.didInsert}}
|
||||||
|
{{will-destroy this.willDestroy}}
|
||||||
|
...attributes
|
||||||
|
>
|
||||||
|
{{yield}}
|
||||||
|
</div>
|
|
@ -1,54 +1,63 @@
|
||||||
import { attributeBindings, classNames } from "@ember-decorators/component";
|
import Component from "@glimmer/component";
|
||||||
import Component from "@ember/component";
|
|
||||||
import { scheduleOnce } from "@ember/runloop";
|
import { scheduleOnce } from "@ember/runloop";
|
||||||
|
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
@classNames("modal-body")
|
function pick(object, keys) {
|
||||||
@attributeBindings("tabindex")
|
const result = {};
|
||||||
|
for (const key of keys) {
|
||||||
|
if (key in object) {
|
||||||
|
result[key] = object[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@disableImplicitInjections
|
||||||
export default class DModalBody extends Component {
|
export default class DModalBody extends Component {
|
||||||
fixed = false;
|
@service appEvents;
|
||||||
submitOnEnter = true;
|
|
||||||
dismissable = true;
|
|
||||||
tabindex = -1;
|
|
||||||
|
|
||||||
didInsertElement() {
|
@tracked fixed = false;
|
||||||
super.didInsertElement(...arguments);
|
|
||||||
|
@action
|
||||||
|
didInsert(element) {
|
||||||
this._modalAlertElement = document.getElementById("modal-alert");
|
this._modalAlertElement = document.getElementById("modal-alert");
|
||||||
if (this._modalAlertElement) {
|
if (this._modalAlertElement) {
|
||||||
this._clearFlash();
|
this._clearFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
let fixedParent = this.element.closest(".d-modal.fixed-modal");
|
const fixedParent = element.closest(".d-modal.fixed-modal");
|
||||||
if (fixedParent) {
|
if (fixedParent) {
|
||||||
this.set("fixed", true);
|
this.fixed = true;
|
||||||
$(fixedParent).modal("show");
|
$(fixedParent).modal("show");
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleOnce("afterRender", this, this._afterFirstRender);
|
scheduleOnce("afterRender", () => this._afterFirstRender(element));
|
||||||
this.appEvents.on("modal-body:flash", this, "_flash");
|
|
||||||
this.appEvents.on("modal-body:clearFlash", this, "_clearFlash");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroyElement() {
|
@action
|
||||||
super.willDestroyElement(...arguments);
|
willDestroy() {
|
||||||
this.appEvents.off("modal-body:flash", this, "_flash");
|
this.appEvents.off("modal-body:flash", this, "_flash");
|
||||||
this.appEvents.off("modal-body:clearFlash", this, "_clearFlash");
|
this.appEvents.off("modal-body:clearFlash", this, "_clearFlash");
|
||||||
this.appEvents.trigger("modal:body-dismissed");
|
this.appEvents.trigger("modal:body-dismissed");
|
||||||
}
|
}
|
||||||
|
|
||||||
_afterFirstRender() {
|
_afterFirstRender(element) {
|
||||||
const maxHeight = this.maxHeight;
|
const maxHeight = this.args.maxHeight;
|
||||||
if (maxHeight) {
|
if (maxHeight) {
|
||||||
const maxHeightFloat = parseFloat(maxHeight) / 100.0;
|
const maxHeightFloat = parseFloat(maxHeight) / 100.0;
|
||||||
if (maxHeightFloat > 0) {
|
if (maxHeightFloat > 0) {
|
||||||
const viewPortHeight = $(window).height();
|
const viewPortHeight = $(window).height();
|
||||||
this.element.style.maxHeight =
|
element.style.maxHeight =
|
||||||
Math.floor(maxHeightFloat * viewPortHeight) + "px";
|
Math.floor(maxHeightFloat * viewPortHeight) + "px";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appEvents.trigger(
|
this.appEvents.trigger(
|
||||||
"modal:body-shown",
|
"modal:body-shown",
|
||||||
this.getProperties(
|
pick(this.args, [
|
||||||
"title",
|
"title",
|
||||||
"rawTitle",
|
"rawTitle",
|
||||||
"fixed",
|
"fixed",
|
||||||
|
@ -56,8 +65,8 @@ export default class DModalBody extends Component {
|
||||||
"rawSubtitle",
|
"rawSubtitle",
|
||||||
"submitOnEnter",
|
"submitOnEnter",
|
||||||
"dismissable",
|
"dismissable",
|
||||||
"headerClass"
|
"headerClass",
|
||||||
)
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,25 @@
|
||||||
<div class="modal-outer-container">
|
{{! template-lint-disable no-down-event-binding }}
|
||||||
|
{{! template-lint-disable no-invalid-interactive }}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={{concat-class
|
||||||
|
"modal"
|
||||||
|
"d-modal"
|
||||||
|
this.modalClass
|
||||||
|
this.modalStyle
|
||||||
|
(if this.hasPanels "has-panels")
|
||||||
|
}}
|
||||||
|
id={{if (not-eq this.modalStyle "inline-modal") "discourse-modal"}}
|
||||||
|
data-keyboard="false"
|
||||||
|
aria-modal="true"
|
||||||
|
role="dialog"
|
||||||
|
aria-labelledby={{this.ariaLabelledby}}
|
||||||
|
...attributes
|
||||||
|
{{did-insert this.setupListeners}}
|
||||||
|
{{will-destroy this.cleanupListeners}}
|
||||||
|
{{on "mousedown" this.handleMouseDown}}
|
||||||
|
>
|
||||||
|
<div class="modal-outer-container">
|
||||||
<div class="modal-middle-container">
|
<div class="modal-middle-container">
|
||||||
<div class="modal-inner-container">
|
<div class="modal-inner-container">
|
||||||
<PluginOutlet @name="above-modal-header" @connectorTagName="div" />
|
<PluginOutlet @name="above-modal-header" @connectorTagName="div" />
|
||||||
|
@ -28,8 +49,8 @@
|
||||||
<ModalTab
|
<ModalTab
|
||||||
@panel={{panel}}
|
@panel={{panel}}
|
||||||
@panelsLength={{this.panels.length}}
|
@panelsLength={{this.panels.length}}
|
||||||
@selectedPanel={{this.selectedPanel}}
|
@selectedPanel={{@selectedPanel}}
|
||||||
@onSelectPanel={{this.onSelectPanel}}
|
@onSelectPanel={{@onSelectPanel}}
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -53,4 +74,5 @@
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,80 +1,102 @@
|
||||||
import {
|
import Component from "@glimmer/component";
|
||||||
attributeBindings,
|
|
||||||
classNameBindings,
|
|
||||||
} from "@ember-decorators/component";
|
|
||||||
import Component from "@ember/component";
|
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import { next, schedule } from "@ember/runloop";
|
import { next, schedule } from "@ember/runloop";
|
||||||
import discourseComputed, { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
@classNameBindings(
|
@disableImplicitInjections
|
||||||
":modal",
|
|
||||||
":d-modal",
|
|
||||||
"modalClass",
|
|
||||||
"modalStyle",
|
|
||||||
"hasPanels"
|
|
||||||
)
|
|
||||||
@attributeBindings(
|
|
||||||
"dataKeyboard:data-keyboard",
|
|
||||||
"ariaModal:aria-modal",
|
|
||||||
"role",
|
|
||||||
"ariaLabelledby:aria-labelledby"
|
|
||||||
)
|
|
||||||
export default class DModal extends Component {
|
export default class DModal extends Component {
|
||||||
submitOnEnter = true;
|
@service appEvents;
|
||||||
dismissable = true;
|
|
||||||
title = null;
|
|
||||||
titleAriaElementId = null;
|
|
||||||
subtitle = null;
|
|
||||||
role = "dialog";
|
|
||||||
headerClass = null;
|
|
||||||
|
|
||||||
// // We handle ESC ourselves
|
@tracked wrapperElement;
|
||||||
dataKeyboard = "false";
|
@tracked modalBodyData = {};
|
||||||
// // Inform screen readers of the modal
|
|
||||||
ariaModal = "true";
|
|
||||||
|
|
||||||
init() {
|
get modalStyle() {
|
||||||
super.init(...arguments);
|
if (this.args.modalStyle === "inline-modal") {
|
||||||
|
return "inline-modal";
|
||||||
// If we need to render a second modal for any reason, we can't
|
} else {
|
||||||
// use `elementId`
|
return "fixed-modal";
|
||||||
if (this.modalStyle !== "inline-modal") {
|
|
||||||
this.set("elementId", "discourse-modal");
|
|
||||||
this.set("modalStyle", "fixed-modal");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
didInsertElement() {
|
get submitOnEnter() {
|
||||||
super.didInsertElement(...arguments);
|
if ("submitOnEnter" in this.modalBodyData) {
|
||||||
|
return this.modalBodyData.submitOnEnter;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.appEvents.on("modal:body-shown", this, "_modalBodyShown");
|
get dismissable() {
|
||||||
|
if ("dismissable" in this.modalBodyData) {
|
||||||
|
return this.modalBodyData.dismissable;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
if (this.modalBodyData.title) {
|
||||||
|
return I18n.t(this.modalBodyData.title);
|
||||||
|
} else if (this.modalBodyData.rawTitle) {
|
||||||
|
return this.modalBodyData.rawTitle;
|
||||||
|
} else {
|
||||||
|
return this.args.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get subtitle() {
|
||||||
|
if (this.modalBodyData.subtitle) {
|
||||||
|
return I18n.t(this.modalBodyData.subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.modalBodyData.rawSubtitle || this.args.subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headerClass() {
|
||||||
|
return this.modalBodyData.headerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
get panels() {
|
||||||
|
return this.args.panels;
|
||||||
|
}
|
||||||
|
|
||||||
|
get errors() {
|
||||||
|
return this.args.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setupListeners(element) {
|
||||||
|
this.appEvents.on("modal:body-shown", this._modalBodyShown);
|
||||||
document.documentElement.addEventListener(
|
document.documentElement.addEventListener(
|
||||||
"keydown",
|
"keydown",
|
||||||
this._handleModalEvents
|
this._handleModalEvents
|
||||||
);
|
);
|
||||||
|
this.wrapperElement = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroyElement() {
|
@action
|
||||||
super.willDestroyElement(...arguments);
|
cleanupListeners() {
|
||||||
|
this.appEvents.off("modal:body-shown", this._modalBodyShown);
|
||||||
this.appEvents.off("modal:body-shown", this, "_modalBodyShown");
|
|
||||||
document.documentElement.removeEventListener(
|
document.documentElement.removeEventListener(
|
||||||
"keydown",
|
"keydown",
|
||||||
this._handleModalEvents
|
this._handleModalEvents
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed("title", "titleAriaElementId")
|
get ariaLabelledby() {
|
||||||
ariaLabelledby(title, titleAriaElementId) {
|
if (this.args.titleAriaElementId) {
|
||||||
if (titleAriaElementId) {
|
return this.args.titleAriaElementId;
|
||||||
return titleAriaElementId;
|
} else if (this.args.title) {
|
||||||
}
|
|
||||||
if (title) {
|
|
||||||
return "discourse-modal-title";
|
return "discourse-modal-title";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
get modalClass() {
|
||||||
|
return this.modalBodyData.modalClass || this.args.modalClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerClickOnEnter(e) {
|
triggerClickOnEnter(e) {
|
||||||
|
@ -93,7 +115,8 @@ export default class DModal extends Component {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseDown(e) {
|
@action
|
||||||
|
handleMouseDown(e) {
|
||||||
if (!this.dismissable) {
|
if (!this.dismissable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -105,46 +128,21 @@ export default class DModal extends Component {
|
||||||
// Send modal close (which bubbles to ApplicationRoute) if clicked outside.
|
// Send modal close (which bubbles to ApplicationRoute) if clicked outside.
|
||||||
// We do this because some CSS of ours seems to cover the backdrop and makes
|
// We do this because some CSS of ours seems to cover the backdrop and makes
|
||||||
// it unclickable.
|
// it unclickable.
|
||||||
return this.attrs.closeModal?.("initiatedByClickOut");
|
return this.args.closeModal?.("initiatedByClickOut");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
_modalBodyShown(data) {
|
_modalBodyShown(data) {
|
||||||
if (this.isDestroying || this.isDestroyed) {
|
if (this.isDestroying || this.isDestroyed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.fixed) {
|
if (data.fixed) {
|
||||||
this.element.classList.remove("hidden");
|
this.wrapperElement.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.title) {
|
this.modalBodyData = data;
|
||||||
this.set("title", I18n.t(data.title));
|
|
||||||
} else if (data.rawTitle) {
|
|
||||||
this.set("title", data.rawTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.subtitle) {
|
|
||||||
this.set("subtitle", I18n.t(data.subtitle));
|
|
||||||
} else if (data.rawSubtitle) {
|
|
||||||
this.set("subtitle", data.rawSubtitle);
|
|
||||||
} else {
|
|
||||||
// if no subtitle provided, makes sure the previous subtitle
|
|
||||||
// of another modal is not used
|
|
||||||
this.set("subtitle", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("submitOnEnter" in data) {
|
|
||||||
this.set("submitOnEnter", data.submitOnEnter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("dismissable" in data) {
|
|
||||||
this.set("dismissable", data.dismissable);
|
|
||||||
} else {
|
|
||||||
this.set("dismissable", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set("headerClass", data.headerClass || null);
|
|
||||||
|
|
||||||
schedule("afterRender", () => {
|
schedule("afterRender", () => {
|
||||||
this._trapTab();
|
this._trapTab();
|
||||||
|
@ -153,16 +151,16 @@ export default class DModal extends Component {
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
_handleModalEvents(event) {
|
_handleModalEvents(event) {
|
||||||
if (this.element.classList.contains("hidden")) {
|
if (this.wrapperElement.classList.contains("hidden")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === "Escape" && this.dismissable) {
|
if (event.key === "Escape" && this.dismissable) {
|
||||||
next(() => this.attrs.closeModal("initiatedByESC"));
|
next(() => this.args.closeModal("initiatedByESC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === "Enter" && this.triggerClickOnEnter(event)) {
|
if (event.key === "Enter" && this.triggerClickOnEnter(event)) {
|
||||||
this.element.querySelector(".modal-footer .btn-primary")?.click();
|
this.wrapperElement.querySelector(".modal-footer .btn-primary")?.click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,11 +170,13 @@ export default class DModal extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_trapTab(event) {
|
_trapTab(event) {
|
||||||
if (this.element.classList.contains("hidden")) {
|
if (this.wrapperElement.classList.contains("hidden")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const innerContainer = this.element.querySelector(".modal-inner-container");
|
const innerContainer = this.wrapperElement.querySelector(
|
||||||
|
".modal-inner-container"
|
||||||
|
);
|
||||||
if (!innerContainer) {
|
if (!innerContainer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue