From d7305b365101555452a719cafaa5591d8691da41 Mon Sep 17 00:00:00 2001
From: Markus Sanin
Date: Sun, 1 Feb 2026 17:07:09 +0100
Subject: [PATCH 1/5] Refactor disabled getter (remove ember/runloop & fix
no-side-effects)
---
src/components/basic-dropdown-content.gts | 3 +-
src/components/basic-dropdown.gts | 48 +++++++++----------
src/modifiers/basic-dropdown-trigger.ts | 13 +++++
src/types.ts | 1 +
tests/integration/components/trigger-test.gts | 1 +
5 files changed, 40 insertions(+), 26 deletions(-)
diff --git a/src/components/basic-dropdown-content.gts b/src/components/basic-dropdown-content.gts
index aa4269cc..2729bae1 100644
--- a/src/components/basic-dropdown-content.gts
+++ b/src/components/basic-dropdown-content.gts
@@ -175,7 +175,8 @@ export default class BasicDropdownContent<
if (!triggerElement) {
triggerElement = document.querySelector(selector) as HTMLElement;
}
- this.handleRootMouseDown = (e: MouseEvent | TouchEvent) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ this.handleRootMouseDown = (e: MouseEvent | TouchEvent): any => {
const target = (e.composedPath?.()[0] || e.target) as Element;
if (target === null) return;
if (
diff --git a/src/components/basic-dropdown.gts b/src/components/basic-dropdown.gts
index 35e7f917..cfc6915e 100644
--- a/src/components/basic-dropdown.gts
+++ b/src/components/basic-dropdown.gts
@@ -3,7 +3,6 @@ import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';
import calculatePosition from '../utils/calculate-position.ts';
-import { schedule } from '@ember/runloop';
import { hash } from '@ember/helper';
import BasicDropdownTrigger from './basic-dropdown-trigger.gts';
import BasicDropdownContent from './basic-dropdown-content.gts';
@@ -27,7 +26,6 @@ import type {
TRootEventType,
} from '../types.ts';
-const UNINITIALIZED = {};
const IGNORED_STYLES = ['top', 'left', 'right', 'width', 'height'];
export interface BasicDropdownDefaultBlock<
@@ -122,9 +120,10 @@ export default class BasicDropdown<
@tracked width: string | undefined;
@tracked height: string | undefined;
@tracked otherStyles: Record = {};
- @tracked isOpen = this.args.initiallyOpened || false;
@tracked renderInPlace =
this.args.renderInPlace !== undefined ? this.args.renderInPlace : false;
+ @tracked private _isOpen = this.args.initiallyOpened || false;
+
private previousVerticalPosition?: VerticalPosition | undefined;
private previousHorizontalPosition?: HorizontalPosition | undefined;
private triggerElement: HTMLElement | null = null;
@@ -132,25 +131,33 @@ export default class BasicDropdown<
private _uid = guidFor(this);
private _dropdownId: string = `ember-basic-dropdown-content-${this._uid}`;
- private _previousDisabled = UNINITIALIZED;
private _actions: DropdownActions = {
open: this.open.bind(this),
close: this.close.bind(this),
toggle: this.toggle.bind(this),
reposition: this.reposition.bind(this),
+ updatePublicApi: this.updatePublicApi.bind(this),
registerTriggerElement: this.registerTriggerElement.bind(this),
registerDropdownElement: this.registerDropdownElement.bind(this),
getTriggerElement: () => this.triggerElement,
};
- private get horizontalPosition() {
+ private get horizontalPosition(): HorizontalPosition {
return this.args.horizontalPosition || 'auto'; // auto-right | right | center | left
}
- private get verticalPosition() {
+ private get verticalPosition(): VerticalPosition {
return this.args.verticalPosition || 'auto'; // above | below
}
+ get isOpen(): boolean {
+ return !this.disabled && this._isOpen
+ }
+
+ set isOpen(v: boolean) {
+ this._isOpen = v;
+ }
+
get destination(): string {
return this.args.destination || this._getDestinationId();
}
@@ -179,25 +186,7 @@ export default class BasicDropdown<
}
get disabled(): boolean {
- const newVal = this.args.disabled || false;
- if (
- this._previousDisabled !== UNINITIALIZED &&
- this._previousDisabled !== newVal
- ) {
- // eslint-disable-next-line ember/no-runloop
- schedule('actions', () => {
- if (newVal && this.publicAPI.isOpen) {
- // eslint-disable-next-line ember/no-side-effects
- this.isOpen = false;
- }
- if (this.args.registerAPI) {
- this.args.registerAPI(this.publicAPI);
- }
- });
- }
- // eslint-disable-next-line ember/no-side-effects
- this._previousDisabled = newVal;
- return newVal;
+ return this.args.disabled || false;
}
get publicAPI(): Dropdown {
@@ -337,6 +326,15 @@ export default class BasicDropdown<
return this.applyReposition(triggerElement, dropdownElement, positionData);
}
+ @action
+ updatePublicApi() {
+ if (!this.args.registerAPI) {
+ return;
+ }
+
+ this.args.registerAPI(this.publicAPI);
+ }
+
@action
registerTriggerElement(element: HTMLElement): void {
this.triggerElement = element;
diff --git a/src/modifiers/basic-dropdown-trigger.ts b/src/modifiers/basic-dropdown-trigger.ts
index 751b5eae..550335d1 100644
--- a/src/modifiers/basic-dropdown-trigger.ts
+++ b/src/modifiers/basic-dropdown-trigger.ts
@@ -32,6 +32,8 @@ export default class DropdownTriggerModifier extends Modifier {
desiredEventType!: string;
stopPropagation: boolean | undefined;
+ private _previousDisabled: boolean | undefined = undefined;
+
constructor(owner: Owner, args: ArgsFor) {
super(owner, args);
registerDestructor(this, cleanup);
@@ -51,6 +53,17 @@ export default class DropdownTriggerModifier extends Modifier {
this.setup(element);
this.didSetup = true;
}
+
+ if (this._previousDisabled === undefined) {
+ this._previousDisabled = this.dropdown.disabled;
+ }
+
+ if (this.dropdown.disabled !== this._previousDisabled) {
+ this._previousDisabled = this.dropdown.disabled;
+
+ this.dropdown.actions.updatePublicApi();
+ }
+
this.update(element, positional, named);
}
diff --git a/src/types.ts b/src/types.ts
index 9615fdad..157c286a 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,6 +3,7 @@ export interface DropdownActions {
close: (e?: Event, skipFocus?: boolean) => void;
open: (e?: Event) => void;
reposition: () => undefined | RepositionChanges;
+ updatePublicApi: () => void;
registerTriggerElement: (e: HTMLElement) => void;
registerDropdownElement: (e: HTMLElement) => void;
getTriggerElement: () => HTMLElement | null;
diff --git a/tests/integration/components/trigger-test.gts b/tests/integration/components/trigger-test.gts
index 5da08690..6374be11 100644
--- a/tests/integration/components/trigger-test.gts
+++ b/tests/integration/components/trigger-test.gts
@@ -63,6 +63,7 @@ const dropdownBase: Dropdown = {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
From 4e45840b2bf83c6991509e26fb07e6ba967b47bc Mon Sep 17 00:00:00 2001
From: Markus Sanin
Date: Sun, 1 Feb 2026 17:36:24 +0100
Subject: [PATCH 2/5] Fix lint
---
src/components/basic-dropdown.gts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/basic-dropdown.gts b/src/components/basic-dropdown.gts
index cfc6915e..0b8e2ec8 100644
--- a/src/components/basic-dropdown.gts
+++ b/src/components/basic-dropdown.gts
@@ -151,7 +151,7 @@ export default class BasicDropdown<
}
get isOpen(): boolean {
- return !this.disabled && this._isOpen
+ return !this.disabled && this._isOpen;
}
set isOpen(v: boolean) {
From 75187f416f6bd3b7931c51096abdb6558f07e53e Mon Sep 17 00:00:00 2001
From: Markus Sanin
Date: Sun, 1 Feb 2026 17:40:09 +0100
Subject: [PATCH 3/5] Fix lint
---
tests/integration/components/content-test.gts | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/tests/integration/components/content-test.gts b/tests/integration/components/content-test.gts
index b5a30575..0b733c5e 100644
--- a/tests/integration/components/content-test.gts
+++ b/tests/integration/components/content-test.gts
@@ -57,6 +57,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -101,6 +102,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -143,6 +145,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -181,6 +184,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -225,6 +229,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -267,6 +272,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -306,6 +312,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -348,6 +355,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -391,6 +399,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -435,6 +444,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -479,6 +489,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -520,6 +531,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -540,6 +552,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -592,6 +605,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -640,6 +654,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -698,6 +713,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -776,6 +792,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -828,6 +845,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -864,6 +882,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
assert.ok(false, 'Reposition is invoked exactly once');
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -899,6 +918,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -997,6 +1017,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
repositions++;
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1041,6 +1062,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
repositions++;
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1085,6 +1107,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
repositions++;
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1129,6 +1152,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
repositions++;
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1175,6 +1199,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
repositions++;
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1221,6 +1246,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
done();
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1279,6 +1305,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
repositions++;
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1323,6 +1350,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
@@ -1370,6 +1398,7 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
reposition: () => {
return undefined;
},
+ updatePublicApi: () => {},
registerTriggerElement: () => {},
registerDropdownElement: () => {},
getTriggerElement: () => {
From 52f3672eb0805994b6d1dfda520c4c1f9d0103bc Mon Sep 17 00:00:00 2001
From: Markus Sanin
Date: Sun, 1 Feb 2026 17:46:06 +0100
Subject: [PATCH 4/5] Add note to migrate
---
.../public-pages/docs/migrate-8-0-to-9-0.gts | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/docs/app/templates/public-pages/docs/migrate-8-0-to-9-0.gts b/docs/app/templates/public-pages/docs/migrate-8-0-to-9-0.gts
index 67f565d0..1612997e 100644
--- a/docs/app/templates/public-pages/docs/migrate-8-0-to-9-0.gts
+++ b/docs/app/templates/public-pages/docs/migrate-8-0-to-9-0.gts
@@ -77,6 +77,18 @@ import { LinkTo } from '@ember/routing';
a regular HTML attribute instead.
+
+
+ If you are using the public API without the trigger modifier and change
+ @disabled
+ from
+ false
+ to
+ true, please follow the guidance
+ in
+ this PR
+
+
If you are using
From 36cebdecb8b4618ecf1f61f25f63225d8c98e70e Mon Sep 17 00:00:00 2001
From: Markus Sanin
Date: Sun, 1 Feb 2026 17:47:34 +0100
Subject: [PATCH 5/5] Remove unwanted change
---
src/components/basic-dropdown-content.gts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/components/basic-dropdown-content.gts b/src/components/basic-dropdown-content.gts
index 2729bae1..4be495c8 100644
--- a/src/components/basic-dropdown-content.gts
+++ b/src/components/basic-dropdown-content.gts
@@ -175,8 +175,7 @@ export default class BasicDropdownContent<
if (!triggerElement) {
triggerElement = document.querySelector(selector) as HTMLElement;
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- this.handleRootMouseDown = (e: MouseEvent | TouchEvent): any => {
+ this.handleRootMouseDown = (e: MouseEvent | TouchEvent): void => {
const target = (e.composedPath?.()[0] || e.target) as Element;
if (target === null) return;
if (