diff --git a/addon/components/attach/popover.js b/addon/components/attach/popover.js index 0bca26de..3ce4fc36 100644 --- a/addon/components/attach/popover.js +++ b/addon/components/attach/popover.js @@ -13,7 +13,6 @@ export default class AttachPopoverComponent extends Component { @tracked hideDuration = 300; @tracked hideOn = 'mouseleave blur escapekey'; @tracked interactive = false; - @tracked isOffset = false; @tracked isShown = false; @tracked lazyRender = false; @tracked modifiers = null; @@ -192,7 +191,11 @@ export default class AttachPopoverComponent extends Component { } // If cursor is not on the attachment or target, hide the popover - if (!target.contains(event.target) && !(this.isOffset && this.isCursorBetweenTargetAndAttachment(event)) && this.floatingElement && !this.floatingElement.contains(event.target)) { + // NOTE: this used to read `!(this.isOffset && this.isCursorBetweenTargetAndAttachment(event))`. + // `isOffset` was never assigned from an argument or anywhere else, so it was permanently + // false — and `isCursorBetweenTargetAndAttachment` does not exist on this component, so + // had anything ever set the flag the popover would have thrown on every mousemove. + if (!target.contains(event.target) && this.floatingElement && !this.floatingElement.contains(event.target)) { // Remove this listener before hiding the attachment delete this.hideListenersOnDocumentByEvent.mousemove; document.removeEventListener('mousemove', this.hideIfMouseOutsideTargetOrAttachment, this.useCapture); diff --git a/addon/components/filter/multi-option.js b/addon/components/filter/multi-option.js index 045f3570..faf4584f 100644 --- a/addon/components/filter/multi-option.js +++ b/addon/components/filter/multi-option.js @@ -38,14 +38,19 @@ export default class FilterMultiOptionComponent extends Component { } } + // power-select expects `@search` to RETURN the matches (or a promise of them). The previous + // version called `this.fetchOptions(...)` — a Task object, not a function, so the remote path + // threw — and on the local path assigned `this.options`, which both raised a backtracking + // assertion (it runs inside a modifier update) and permanently discarded every non-matching + // option, so clearing the query could not bring them back. @action search(query) { const { optionLabel, fetchUri, fetchParams = {} } = this.args; if (typeof fetchUri === 'string') { - return this.fetchOptions(fetchUri, { query, ...fetchParams }); + return this.fetchOptions.perform(fetchUri, { query, ...fetchParams }); } - this.options = this.options.filter((option) => { + return this.options.filter((option) => { const optionText = get(option, optionLabel ?? 'name') ?? option; if (typeof optionText === 'string') { @@ -65,9 +70,15 @@ export default class FilterMultiOptionComponent extends Component { try { const options = yield this.fetch.get(uri, queryParams); this.options = options; + + // Returned as well as stored: `search` hands this task's promise straight to + // power-select, which uses the resolved value as the result list. + return options; } catch (err) { debug('Error loading options: ' + err.message); } + + return []; } parseValue(value) { diff --git a/addon/components/modal.js b/addon/components/modal.js index 26f7d6ed..b482633e 100644 --- a/addon/components/modal.js +++ b/addon/components/modal.js @@ -289,7 +289,9 @@ export default class Modal extends Component { * @readonly * @private */ - @usesTransition('_fade') usesTransition; + // Must name the ARGUMENT: the decorator reads `this.args[prop]`, and `_fade` is a getter on + // the component, so `this.args._fade` was always undefined and `@fade={{false}}` was ignored. + @usesTransition('fade') usesTransition; destinationElement = getDestinationElement(this); diff --git a/addon/components/overlay.js b/addon/components/overlay.js index 8e76dca6..3fefb97e 100644 --- a/addon/components/overlay.js +++ b/addon/components/overlay.js @@ -189,16 +189,25 @@ export default class OverlayComponent extends Component { const height = dy * multiplier + this.overlayHeight; const minResizeWidth = this.args.minResizeWidth ?? 560; const maxResizeWidth = this.args.maxResizeWidth ?? 900; - - // Min resize width - if (width <= minResizeWidth) { - overlayPanelNode.style.width = `${minResizeWidth}px`; + const minResizeHeight = this.args.minResizeHeight ?? 0; + const maxResizeHeight = this.args.maxResizeHeight ?? Number.MAX_SAFE_INTEGER; + + // Clamp the dimension actually being dragged. These guards used to test the WIDTH whatever + // the position and `return` before the fork below, so a top/bottom overlay whose width sat + // outside [min, max] — which a full-width drawer always does — could never be resized at + // all, and each vertical drag silently rewrote its width to the clamp. + const size = isHorizontal ? width : height; + const minSize = isHorizontal ? minResizeWidth : minResizeHeight; + const maxSize = isHorizontal ? maxResizeWidth : maxResizeHeight; + const dimension = isHorizontal ? 'width' : 'height'; + + if (size <= minSize) { + overlayPanelNode.style[dimension] = `${minSize}px`; return; } - // Max resize width - if (width >= maxResizeWidth) { - overlayPanelNode.style.width = `${maxResizeWidth}px`; + if (size >= maxSize) { + overlayPanelNode.style[dimension] = `${maxSize}px`; return; } diff --git a/addon/components/query-builder/conditions.hbs b/addon/components/query-builder/conditions.hbs index 9b967693..3e1d2623 100644 --- a/addon/components/query-builder/conditions.hbs +++ b/addon/components/query-builder/conditions.hbs @@ -1,4 +1,6 @@ -