diff --git a/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js b/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js index e7de0a22559..0f0548fccc9 100644 --- a/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js +++ b/app/assets/javascripts/discourse/app/widgets/widget-dropdown.js @@ -140,6 +140,66 @@ export const WidgetDropdownItemClass = { createWidget("widget-dropdown-item", WidgetDropdownItemClass); +export const WidgetDropdownBodyClass = { + tagName: "div", + + buildClasses(attrs) { + return `widget-dropdown-body ${attrs.class || ""}`; + }, + + init(attrs) { + schedule("afterRender", () => { + const dropdownHeader = document.querySelector( + `#${attrs.id} .widget-dropdown-header` + ); + const dropdownBody = document.querySelector( + `#${attrs.id} .widget-dropdown-body` + ); + + if (dropdownHeader && dropdownBody) { + /* global Popper:true */ + this._popper = Popper.createPopper(dropdownHeader, dropdownBody, { + strategy: "fixed", + placement: "bottom-start", + modifiers: [ + { + name: "preventOverflow" + }, + { + name: "offset", + options: { + offset: [0, 5] + } + } + ] + }); + } + }); + }, + + destroy() { + if (this._popper) { + this._popper.destroy(); + this._popper = null; + } + }, + + clickOutside() { + this.sendWidgetAction("hideBody"); + }, + + template: hbs` + {{#each attrs.content as |item|}} + {{attach + widget="widget-dropdown-item" + attrs=(hash item=item) + }} + {{/each}} + ` +}; + +createWidget("widget-dropdown-body", WidgetDropdownBodyClass); + export const WidgetDropdownClass = { tagName: "div", @@ -178,21 +238,18 @@ export const WidgetDropdownClass = { }, transform(attrs) { - const options = attrs.options || {}; - return { - options, - bodyClass: `widget-dropdown-body ${options.bodyClass || ""}` + options: attrs.options || {} }; }, - clickOutside() { + hideBody() { this.state.opened = false; - this.scheduleRerender(); }, _onChange(params) { this.state.opened = false; + if (this.attrs.onChange) { if (typeof this.attrs.onChange === "string") { this.sendWidgetAction(this.attrs.onChange, params); @@ -203,22 +260,7 @@ export const WidgetDropdownClass = { }, _onTrigger() { - if (this.state.opened) { - this.state.opened = false; - this._closeDropdown(this.attrs.id); - } else { - this.state.opened = true; - this._openDropdown(this.attrs.id); - } - - this._popper && this._popper.update(); - }, - - destroy() { - if (this._popper) { - this._popper.destroy(); - this._popper = null; - } + this.state.opened = !this.state.opened; }, template: hbs` @@ -234,50 +276,18 @@ export const WidgetDropdownClass = { ) }} -
- {{#each attrs.content as |item|}} - {{attach - widget="widget-dropdown-item" - attrs=(hash item=item) - }} - {{/each}} -
+ {{#if this.state.opened}} + {{attach + widget="widget-dropdown-body" + attrs=(hash + id=attrs.id + class=this.transformed.options.bodyClass + content=attrs.content + ) + }} + {{/if}} {{/if}} - `, - - _closeDropdown() { - this._popper && this._popper.destroy(); - }, - - _openDropdown(id) { - const dropdownHeader = document.querySelector( - `#${id} .widget-dropdown-header` - ); - const dropdownBody = document.querySelector(`#${id} .widget-dropdown-body`); - - if (dropdownHeader && dropdownBody) { - /* global Popper:true */ - this._popper = Popper.createPopper(dropdownHeader, dropdownBody, { - strategy: "fixed", - placement: "bottom-start", - modifiers: [ - { - name: "preventOverflow" - }, - { - name: "offset", - options: { - offset: [0, 5] - } - } - ] - }); - } - - schedule("afterRender", () => { - this._popper && this._popper.update(); - }); - } + ` }; export default createWidget("widget-dropdown", WidgetDropdownClass); diff --git a/test/javascripts/widgets/widget-dropdown-test.js b/test/javascripts/widgets/widget-dropdown-test.js index 1d60a84615e..9811484ba42 100644 --- a/test/javascripts/widgets/widget-dropdown-test.js +++ b/test/javascripts/widgets/widget-dropdown-test.js @@ -112,7 +112,8 @@ widgetTest("content", { this.setProperties(DEFAULT_CONTENT); }, - test(assert) { + async test(assert) { + await toggle(); assert.equal(rowById(1).dataset.id, 1, "it creates rows"); assert.equal(rowById(2).dataset.id, 2, "it creates rows"); assert.equal(rowById(3).dataset.id, 3, "it creates rows"); @@ -143,6 +144,7 @@ widgetTest("onChange action", { }, async test(assert) { + await toggle(); await clickRowById(2); assert.equal(find("#test").text(), 2, "it calls the onChange actions"); } @@ -157,10 +159,14 @@ widgetTest("can be opened and closed", { async test(assert) { assert.ok(exists("#my-dropdown.closed")); + assert.ok(!exists("#my-dropdown .widget-dropdown-body")); await toggle(); + assert.equal(rowById(2).innerText.trim(), "FooBar"); assert.ok(exists("#my-dropdown.opened")); + assert.ok(exists("#my-dropdown .widget-dropdown-body")); await toggle(); assert.ok(exists("#my-dropdown.closed")); + assert.ok(!exists("#my-dropdown .widget-dropdown-body")); } }); @@ -197,7 +203,8 @@ widgetTest("content with translatedLabel", { this.setProperties(DEFAULT_CONTENT); }, - test(assert) { + async test(assert) { + await toggle(); assert.equal(rowById(2).innerText.trim(), "FooBar"); } }); @@ -216,7 +223,8 @@ widgetTest("content with label", { I18n.translations = this._translations; }, - test(assert) { + async test(assert) { + await toggle(); assert.equal(rowById(1).innerText.trim(), "FooBaz"); } }); @@ -228,7 +236,8 @@ widgetTest("content with icon", { this.setProperties(DEFAULT_CONTENT); }, - test(assert) { + async test(assert) { + await toggle(); assert.ok(exists(rowById(3).querySelector(".d-icon-times"))); } }); @@ -240,7 +249,8 @@ widgetTest("content with html", { this.setProperties(DEFAULT_CONTENT); }, - test(assert) { + async test(assert) { + await toggle(); assert.equal(rowById(4).innerHTML.trim(), "baz"); } }); @@ -252,7 +262,8 @@ widgetTest("separator", { this.setProperties(DEFAULT_CONTENT); }, - test(assert) { + async test(assert) { + await toggle(); assert.ok( find( "#my-dropdown .widget-dropdown-item:nth-child(3)" @@ -297,7 +308,8 @@ widgetTest("bodyClass option", { this.set("options", { bodyClass: "gigantic and-yet-small" }); }, - test(assert) { + async test(assert) { + await toggle(); assert.ok(body().classList.contains("widget-dropdown-body")); assert.ok(body().classList.contains("gigantic")); assert.ok(body().classList.contains("and-yet-small"));