-
Notifications
You must be signed in to change notification settings - Fork 12
Description
Issue Description
Keyborg uses a global __keyborg property on the window object to store its core instance and references to all Keyborg instances. When multiple Keyborg instances are created on the same site but from different modules, they override each other because they share the same global property but use different id counters. This happens because each module initializes its own private _lastId variable.
Code Reference
The issue occurs with the module-level counter that generates IDs:
Line 24 in 76e5740
| let _lastId = 0; |
Each instance uses this counter to create IDs in the constructor + it will register itself using the id on the global state:
Lines 293 to 302 in 76e5740
| private constructor(win: WindowWithKeyborg, props?: KeyborgProps) { | |
| this._id = "k" + ++_lastId; | |
| this._win = win; | |
| const current = win.__keyborg; | |
| if (current) { | |
| this._core = current.core; | |
| current.refs[this._id] = this; | |
| } else { |
Impact
This issue causes significant problems in applications with multiple bundles that include Keyborg:
- When multiple module instances are loaded, none are functioning properly as the override their Keyborg Instances in two way fashion
- Previously created Keyborg instances stop working correctly
- Keyboard navigation state tracking becomes inconsistent
Real-world impact: This is a critical issue for Microsoft Dynamics 365, where PCFs (Power Apps Component Framework) come with FluentUI v9 bundled. For example, a standard PCF with FluentUI v9 Combobox component will not indicate keyboard navigation mode when users interact with the component using the keyboard, because the Keyborg instance created by the PCF is overridden by the one created by the out-of-the-box script.
Proposed Solution
As there is already a concept for the global __keyborg property, we could extend it to store the next available ID for Keyborg instances. This way, each Keyborg instance would have a unique ID regardless of which module created it.
The solution would involve:
- Storing the last used ID in the global
__keyborgobject - Using that shared counter instead of module-level
_lastId - Ensuring ID generation is synchronized across all Keyborg instances
I am happy to provide a PR for this issue if you agree with the suggested solution or have other ideas to address this problem.
Priority
This issue requires priority attention as it impacts Microsoft Dynamics 365 functionality and any other application that might bundle multiple instances of Keyborg.
Other Concerns
If there will come the time where the core class gets an major update even fix, everything will break.
This is due to the fact that the core instance is a singleton across all modules even if they are not the same version.
See:
Lines 299 to 308 in 76e5740
| if (current) { | |
| this._core = current.core; | |
| current.refs[this._id] = this; | |
| } else { | |
| this._core = new KeyborgCore(win, props); | |
| win.__keyborg = { | |
| core: this._core, | |
| refs: { [this._id]: this }, | |
| }; | |
| } |
What ever the first keyborg instance is will provide the core functionality. This is concerning...
Workaround
I will provide a npm patch in the next days