diff --git a/src/components/calendar/calendar.ts b/src/components/calendar/calendar.ts index 05b3f5dcf6..9a49bf8d40 100644 --- a/src/components/calendar/calendar.ts +++ b/src/components/calendar/calendar.ts @@ -248,7 +248,7 @@ export default class IgcCalendarComponent extends EventEmitterMixin< addKeybindings(this, { skip: this._shouldSkipKeyboardEvent, ref: this._contentRef, - bindingDefaults: { triggers: ['keydownRepeat'] }, + bindingDefaults: { repeat: true }, }) .set(arrowLeft, this._handleArrowKey.bind(this, 'day', -1)) .set(arrowRight, this._handleArrowKey.bind(this, 'day', 1)) diff --git a/src/components/combo/controllers/navigation.ts b/src/components/combo/controllers/navigation.ts index 0072d98526..5637e52d72 100644 --- a/src/components/combo/controllers/navigation.ts +++ b/src/components/combo/controllers/navigation.ts @@ -207,9 +207,7 @@ export class ComboNavigationController { this.combo.addController(this as ReactiveController); this._config = config; - const bindingDefaults = { - triggers: ['keydownRepeat'], - } as KeyBindingOptions; + const bindingDefaults = { repeat: true } as KeyBindingOptions; const skip = (): boolean => this.combo.disabled; diff --git a/src/components/common/controllers/key-bindings.spec.ts b/src/components/common/controllers/key-bindings.spec.ts index d42d5ab4c4..ba6193d2c2 100644 --- a/src/components/common/controllers/key-bindings.spec.ts +++ b/src/components/common/controllers/key-bindings.spec.ts @@ -17,153 +17,250 @@ import { } from './key-bindings.js'; describe('Key bindings controller', () => { - let tag: string; - let instance: LitElement & { - key: string; - event: KeyboardEvent; - input: HTMLInputElement; - }; - - before(() => { - tag = defineCE( - class extends LitElement { - public key?: string; - public event?: KeyboardEvent; - - public get input() { - return this.renderRoot.querySelector('input')!; - } + describe('addKeybindings', () => { + let tag: string; + let instance: LitElement & { + key: string; + event: KeyboardEvent; + input: HTMLInputElement; + }; + + before(() => { + tag = defineCE( + class extends LitElement { + public key?: string; + public event?: KeyboardEvent; + + public get input() { + return this.renderRoot.querySelector('input')!; + } - constructor() { - super(); - addKeybindings(this, { - skip: () => !!this.hidden, - }) - .setActivateHandler(this.handleKeyboardEvent) - .set('a', this.handleKeyboardEvent, { triggers: ['keydown'] }) - .set('s', this.handleKeyboardEvent, { triggers: ['keyup'] }) - .set('k', this.handleKeyboardEvent) - .set('d', this.handleKeyboardEvent, { - triggers: ['keydown', 'keyup'], - preventDefault: true, - stopPropagation: true, + constructor() { + super(); + addKeybindings(this, { + skip: () => !!this.hidden, }) - .set([shiftKey, 'c'], this.handleKeyboardEvent) - .set([shiftKey, altKey, arrowUp], this.handleKeyboardEvent); + .setActivateHandler(this.handleKeyboardEvent) + .set('a', this.handleKeyboardEvent, { triggers: ['keydown'] }) + .set('s', this.handleKeyboardEvent, { triggers: ['keyup'] }) + .set('k', this.handleKeyboardEvent) + .set('d', this.handleKeyboardEvent, { + triggers: ['keydown', 'keyup'], + preventDefault: true, + stopPropagation: true, + }) + .set([shiftKey, 'c'], this.handleKeyboardEvent) + .set([shiftKey, altKey, arrowUp], this.handleKeyboardEvent); - addKeybindings(this).set('x', this.handleKeyboardEvent); - } + addKeybindings(this).set('x', this.handleKeyboardEvent); + } - private handleKeyboardEvent(event: KeyboardEvent) { - this.key = event.key; - this.event = event; - } + private handleKeyboardEvent(event: KeyboardEvent) { + this.key = event.key; + this.event = event; + } - protected override render() { - return html``; + protected override render() { + return html``; + } } - } - ); - }); + ); + }); - function dispatch(node: Element, type: 'keydown' | 'keyup', key: string) { - node.dispatchEvent( - new KeyboardEvent(type, { - key, - bubbles: true, - composed: true, - cancelable: true, - }) - ); - } - - beforeEach(async () => { - const tagName = unsafeStatic(tag); - instance = await fixture(html`<${tagName}>`); - }); + beforeEach(async () => { + const tagName = unsafeStatic(tag); + instance = await fixture(html`<${tagName}>`); + }); - describe('Triggers', () => { - it('keydown - keydown event works', async () => { - dispatch(instance, 'keydown', 'a'); - expect(instance.key).to.equal('a'); - expect(instance.event.type).to.equal('keydown'); + describe('Triggers', () => { + it('keydown - keydown event works', async () => { + dispatch(instance, 'keydown', 'a'); + expect(instance.key).to.equal('a'); + expect(instance.event.type).to.equal('keydown'); + }); + + it('keydown - keyup event is ignored', async () => { + dispatch(instance, 'keyup', 'a'); + expect(instance.key).to.be.undefined; + expect(instance.event).to.be.undefined; + }); + + it('keyup - keyup event works', async () => { + dispatch(instance, 'keyup', 's'); + expect(instance.key).to.equal('s'); + expect(instance.event.type).to.equal('keyup'); + }); + + it('keyup - keydown event is ignored', async () => { + dispatch(instance, 'keydown', 's'); + expect(instance.key).to.be.undefined; + expect(instance.event).to.be.undefined; + }); }); - it('keydown - keyup event is ignored', async () => { - dispatch(instance, 'keyup', 'a'); + it('should honor default skip condition', () => { + simulateKeyboard(instance.input, 'x'); expect(instance.key).to.be.undefined; expect(instance.event).to.be.undefined; }); - it('keyup - keyup event works', async () => { - dispatch(instance, 'keyup', 's'); - expect(instance.key).to.equal('s'); + it('should honor skip condition', () => { + instance.hidden = true; + + simulateKeyboard(instance, 'k'); + expect(instance.key).to.be.undefined; + expect(instance.event).to.be.undefined; + }); + + it('event handler modifiers - prevent default', () => { + dispatch(instance, 'keydown', 'd'); + expect(instance.event.type).to.equal('keydown'); + expect(instance.event.defaultPrevented).to.be.true; + + dispatch(instance, 'keyup', 'd'); expect(instance.event.type).to.equal('keyup'); + expect(instance.event.defaultPrevented).to.be.true; + }); + + it('event handler modifiers - stop propagation', () => { + let parentHandlerCalled = false; + const handler = () => { + parentHandlerCalled = true; + }; + const container = instance.parentElement!; + container.addEventListener('keydown', handler, { once: true }); + container.addEventListener('keyup', handler, { once: true }); + + simulateKeyboard(instance, 'd'); + expect(parentHandlerCalled).to.be.false; + expect(instance.key).to.equal('d'); + }); + + it('activation keys', () => { + for (const key of [enterKey, spaceBar]) { + simulateKeyboard(instance, key); + expect(instance.key).to.equal(key.toLowerCase()); + } }); - it('keyup - keydown event is ignored', async () => { - dispatch(instance, 'keydown', 's'); + it('combinations', () => { + simulateKeyboard(instance, shiftKey); expect(instance.key).to.be.undefined; - expect(instance.event).to.be.undefined; + + simulateKeyboard(instance, 'c'); + expect(instance.key).to.be.undefined; + + simulateKeyboard(instance, [shiftKey, 'c']); + expect(instance.key).to.equal('c'); + + simulateKeyboard(instance, [shiftKey, altKey, arrowUp]); + expect(instance.key).to.equal(arrowUp.toLowerCase()); }); }); - it('should honor default skip condition', () => { - simulateKeyboard(instance.input, 'x'); - expect(instance.key).to.be.undefined; - expect(instance.event).to.be.undefined; - }); + describe('multi non-modifier key combinations', () => { + let multiTag: string; + let multiInstance: LitElement & { callCount: number }; - it('should honor skip condition', () => { - instance.hidden = true; + before(() => { + multiTag = defineCE( + class extends LitElement { + public callCount = 0; - simulateKeyboard(instance, 'k'); - expect(instance.key).to.be.undefined; - expect(instance.event).to.be.undefined; - }); + constructor() { + super(); + addKeybindings(this).set(['x', 'z'], () => this.callCount++); + } + } + ); + }); - it('event handler modifiers - prevent default', () => { - dispatch(instance, 'keydown', 'd'); - expect(instance.event.type).to.equal('keydown'); - expect(instance.event.defaultPrevented).to.be.true; + beforeEach(async () => { + const tagName = unsafeStatic(multiTag); + multiInstance = await fixture(html`<${tagName}>`); + }); - dispatch(instance, 'keyup', 'd'); - expect(instance.event.type).to.equal('keyup'); - expect(instance.event.defaultPrevented).to.be.true; - }); + it('should not fire when only one of the keys is pressed', () => { + dispatch(multiInstance, 'keydown', 'x'); + expect(multiInstance.callCount).to.equal(0); - it('event handler modifiers - stop propagation', () => { - let parentHandlerCalled = false; - const handler = () => { - parentHandlerCalled = true; - }; - const container = instance.parentElement!; - container.addEventListener('keydown', handler, { once: true }); - container.addEventListener('keyup', handler, { once: true }); + dispatch(multiInstance, 'keyup', 'x'); - simulateKeyboard(instance, 'd'); - expect(parentHandlerCalled).to.be.false; - expect(instance.key).to.equal('d'); - }); + dispatch(multiInstance, 'keydown', 'z'); + expect(multiInstance.callCount).to.equal(0); + }); - it('activation keys', () => { - for (const key of [enterKey, spaceBar]) { - simulateKeyboard(instance, key); - expect(instance.key).to.equal(key.toLowerCase()); - } + it('should fire when all keys are held simultaneously', () => { + dispatch(multiInstance, 'keydown', 'x'); + dispatch(multiInstance, 'keydown', 'z'); + expect(multiInstance.callCount).to.equal(1); + }); + + it('should clear pressed keys on window blur', () => { + // Hold 'x', then switch away without releasing it + dispatch(multiInstance, 'keydown', 'x'); + window.dispatchEvent(new FocusEvent('blur')); + + // 'x' should no longer be tracked; pressing 'z' alone should not fire + dispatch(multiInstance, 'keydown', 'z'); + expect(multiInstance.callCount).to.equal(0); + }); }); - it('combinations', () => { - simulateKeyboard(instance, shiftKey); - expect(instance.key).to.be.undefined; + describe('repeat option', () => { + let repeatTag: string; + let repeatInstance: LitElement & { callCount: number }; + + before(() => { + repeatTag = defineCE( + class extends LitElement { + public callCount = 0; + + constructor() { + super(); + addKeybindings(this) + .set('a', () => this.callCount++) + .set('b', () => this.callCount++, { repeat: true }); + } + } + ); + }); + + beforeEach(async () => { + const tagName = unsafeStatic(repeatTag); + repeatInstance = await fixture(html`<${tagName}>`); + }); - simulateKeyboard(instance, 'c'); - expect(instance.key).to.be.undefined; + it('should not fire on repeated keydown by default', () => { + dispatch(repeatInstance, 'keydown', 'a', true); + expect(repeatInstance.callCount).to.equal(0); + }); - simulateKeyboard(instance, [shiftKey, 'c']); - expect(instance.key).to.equal('c'); + it('should fire on initial keydown regardless of repeat option', () => { + dispatch(repeatInstance, 'keydown', 'b'); + expect(repeatInstance.callCount).to.equal(1); + }); - simulateKeyboard(instance, [shiftKey, altKey, arrowUp]); - expect(instance.key).to.equal(arrowUp.toLowerCase()); + it('should fire on repeated keydown when repeat is enabled', () => { + dispatch(repeatInstance, 'keydown', 'b', true); + expect(repeatInstance.callCount).to.equal(1); + }); }); }); + +function dispatch( + node: Element, + type: 'keydown' | 'keyup', + key: string, + repeat = false +) { + node.dispatchEvent( + new KeyboardEvent(type, { + key, + bubbles: true, + composed: true, + cancelable: true, + repeat, + }) + ); +} diff --git a/src/components/common/controllers/key-bindings.ts b/src/components/common/controllers/key-bindings.ts index 072a103cc2..3028584ad5 100644 --- a/src/components/common/controllers/key-bindings.ts +++ b/src/components/common/controllers/key-bindings.ts @@ -5,6 +5,7 @@ import { asArray, findElementFromEventPath, isFunction, + partition, toMerged, } from '../util.js'; @@ -50,13 +51,8 @@ type KeyBindingSkipCallback = (node: Element, event: KeyboardEvent) => boolean; /** * The event type which will trigger the bound handler. - * - * @remarks - * `keydownRepeat` is similar to `keydown` with the exception - * that after the handler is invoked the pressed state of the key is reset - * in the controller. */ -type KeyBindingTrigger = 'keydown' | 'keyup' | 'keydownRepeat'; +type KeyBindingTrigger = 'keydown' | 'keyup'; /** * Configuration object for the controller. @@ -112,6 +108,12 @@ interface KeyBindingOptions { * Defaults to `keydown` if not set. */ triggers?: KeyBindingTrigger[]; + /** + * Whether the handler should fire on auto-repeated keydown events (i.e. when a key is held down). + * + * Defaults to `false`. + */ + repeat?: boolean; /** * Whether to call `preventDefault` on the target event before the handler is invoked. */ @@ -133,14 +135,8 @@ interface KeyBinding { //#region Internal functions and constants -const Modifiers: Map = new Map([ - [altKey.toLowerCase(), altKey.toLowerCase()], - [ctrlKey.toLowerCase(), ctrlKey.toLowerCase()], - [metaKey.toLowerCase(), metaKey.toLowerCase()], - [shiftKey.toLowerCase(), shiftKey.toLowerCase()], -]); - -const ALL_MODIFIER_VALUES = Array.from(Modifiers.values()).sort(); +const MODIFIERS = new Set(['alt', 'ctrl', 'meta', 'shift']); +const ALL_MODIFIER_VALUES = Array.from(MODIFIERS).sort(); function normalizeKeys(keys: string | string[]): string[] { return asArray(keys).map((key) => key.toLowerCase()); @@ -154,20 +150,12 @@ function isKeyup(event: Event): boolean { return event.type === 'keyup'; } -function isKeydownTrigger(triggers?: KeyBindingTrigger[]): boolean { - return triggers - ? triggers.includes('keydown') || isKeydownRepeatTrigger(triggers) - : false; -} - -function isKeyupTrigger(triggers?: KeyBindingTrigger[]): boolean { - return triggers ? triggers.includes('keyup') : false; -} - -function isKeydownRepeatTrigger(triggers?: KeyBindingTrigger[]): boolean { - return triggers ? triggers.includes('keydownRepeat') : false; -} - +/** + * Creates a normalized combination key string from the provided keys and modifiers. + * + * The combination key is a string that uniquely identifies a specific combination of keys and modifiers. + * It is created by sorting the keys and modifiers alphabetically and joining them with a '+' separator. + */ function createCombinationKey(keys: string[], modifiers: string[]): string { const sortedKeys = keys.toSorted(); const sortedModifiers = ALL_MODIFIER_VALUES.filter((mod) => @@ -179,6 +167,14 @@ function createCombinationKey(keys: string[], modifiers: string[]): string { //#endregion +/** + * A controller for managing key bindings on a host element. It allows you to register handlers for specific key combinations, + * with support for modifier keys and event options such as `preventDefault` and `stopPropagation`. + * + * The controller listens for keyboard events on the host element (or an optionally specified element) and invokes the appropriate handlers + * when the registered key combinations are detected. + * + */ class KeyBindingController implements ReactiveController { //#region Private properties and state @@ -230,7 +226,9 @@ class KeyBindingController implements ReactiveController { //#region Private API /** - * Checks and executes any event modifiers that are present in the matched binding. + * Applies the event modifiers specified in the binding options to the provided keyboard event. + * If `preventDefault` is set, it calls `event.preventDefault()`. + * If `stopPropagation` is set, it calls `event.stopPropagation()`. */ private _applyEventModifiers( binding: KeyBinding, @@ -245,22 +243,31 @@ class KeyBindingController implements ReactiveController { } } + /** + * Determines whether the provided keyboard event matches the specified key binding, + * taking into account the event type and the binding's trigger options. + */ private _bindingMatches(binding: KeyBinding, event: KeyboardEvent): boolean { const triggers = binding.options?.triggers ?? ['keydown']; - if (isKeydown(event) && isKeydownTrigger(triggers)) { - return true; + if (isKeydown(event) && triggers.includes('keydown')) { + return !event.repeat || Boolean(binding.options?.repeat); } - if (isKeyup(event) && isKeyupTrigger(triggers)) { + if (isKeyup(event) && triggers.includes('keyup')) { return true; } return false; } + /** + * Determines whether the provided event should be ignored based on the controller's configuration and the event's context. + * The method checks if the event's key is among the allowed keys, if the event originated from within the controller's scope, + * and if it matches any of the skip conditions defined in the controller's options. + */ private _shouldSkip(event: KeyboardEvent): boolean { - const skip = this._options?.skip; + const skip = this._options.skip; if (!this._allowedKeys.has(event.key.toLowerCase())) { return true; @@ -287,13 +294,14 @@ class KeyBindingController implements ReactiveController { //#endregion - //#region Controller specific handlers + //#region Controller lifecycle /** @internal */ public hostConnected(): void { const { signal } = this._abortHandle; this._host.addEventListener('keyup', this, { signal }); this._host.addEventListener('keydown', this, { signal }); + globalThis.addEventListener('blur', this, { signal }); } /** @internal */ @@ -301,15 +309,38 @@ class KeyBindingController implements ReactiveController { this._abortHandle.abort(); } - /** @internal */ - public handleEvent(event: KeyboardEvent): void { + //#endregion + + //#region Event handling + + /** + * Handles the global blur event to clear the internal state of pressed keys. + * + * This is necessary to prevent "stuck" keys when the user switches to another application + * or tab while holding down a key. + */ + private _handleGlobalBlur(): void { + this._pressedKeys.clear(); + } + + /** + * Handles keyboard events on the observed element. + * + * It checks if the event should be skipped based on the controller's configuration, + * and if not, it determines if there is a registered handler for the combination of pressed keys and active modifiers. + * + * If a matching handler is found, it applies the specified event modifiers and invokes the handler. + * It also manages the internal state of currently pressed keys to accurately detect key combinations. + * + */ + private _handleKeyEvent(event: KeyboardEvent): void { if (this._shouldSkip(event)) { return; } const key = event.key.toLowerCase(); - if (!Modifiers.has(key)) { + if (!MODIFIERS.has(key)) { this._pressedKeys.add(key); } @@ -321,35 +352,50 @@ class KeyBindingController implements ReactiveController { Array.from(this._pressedKeys), activeModifiers ); - const binding = this._bindings.get(combination); if (binding && this._bindingMatches(binding, event)) { this._applyEventModifiers(binding, event); binding.handler.call(this._host, event); - - if (isKeydownRepeatTrigger(binding.options?.triggers)) { - this._pressedKeys.delete(key); - } } - if (isKeyup(event) && !Modifiers.has(key)) { + if (!MODIFIERS.has(key) && isKeyup(event)) { this._pressedKeys.delete(key); } } + /** @internal */ + public handleEvent(event: KeyboardEvent | FocusEvent): void { + switch (event.type) { + case 'keydown': + case 'keyup': + this._handleKeyEvent(event as KeyboardEvent); + break; + case 'blur': + this._handleGlobalBlur(); + break; + } + } + //#endregion //#region Public API /** - * Registers a keybinding handler. + * Registers a key binding with the specified key(s), handler function, and optional configuration. + * + * The `key` parameter can be a single key or an array of keys, and can include modifier keys (e.g., 'ctrl+s', ['shift', 'a']). + * The `handler` is a function that will be called when the specified key combination is detected. + * The `bindingOptions` allow you to customize the behavior of the binding, such as which event types trigger the handler, + * whether it should fire on auto-repeated keydown events, and whether to call `preventDefault` or `stopPropagation`. + * + * The method returns the controller instance to allow for method chaining. */ public set( key: string | string[], handler: KeyBindingHandler, bindingOptions?: KeyBindingOptions - ) { + ): this { const { keys, modifiers } = parseKeys(key); const combination = createCombinationKey(keys, modifiers); const options = toMerged( @@ -367,15 +413,18 @@ class KeyBindingController implements ReactiveController { } /** - * Register a handler function which is called when the target receives a key - * which "activates" it. + * Registers the provided handler function to be called when either the Enter key or Space bar is pressed. + * + * This is a common pattern for activating buttons or interactive elements, and this method provides a convenient way to set up such bindings. + * + * The method accepts optional `KeyBindingOptions` which are applied to both the Enter key and Space bar bindings. + * It returns the controller instance to allow for method chaining. * - * In the browser context this is usually either an Enter and Space bar keypress. */ public setActivateHandler( handler: KeyBindingHandler, options?: KeyBindingOptions - ) { + ): this { this.set(enterKey, handler, options); this.set(spaceBar, handler, options); @@ -406,18 +455,49 @@ class KeyBindingController implements ReactiveController { //#endregion } -/** @internal */ -export function parseKeys(keys: string | string[]) { - const normalizedKeys = normalizeKeys(keys); - return { - keys: normalizedKeys.filter((key) => !Modifiers.has(key)), - modifiers: normalizedKeys.filter((key) => Modifiers.has(key)), - }; +/** + * Parses the provided key(s) and separates them into modifiers and regular keys. + * + * Modifiers are keys like Alt, Ctrl, Meta and Shift which modify the behavior of other keys when pressed in combination. + * Regular keys are all other keys which trigger the bound handler when pressed. + * + * The returned `keys` and `modifiers` are normalized to lowercase for consistency. + * + * @internal + * + * @param inputKeys - The key or keys to parse, provided as a string or an array of strings. + * @returns An object containing the separated `keys` and `modifiers`. + */ +export function parseKeys(inputKeys: string | string[]): { + keys: string[]; + modifiers: string[]; +} { + const [modifiers, keys] = partition(normalizeKeys(inputKeys), (key) => + MODIFIERS.has(key) + ); + return { keys, modifiers }; } /** - * Creates a keybinding controller and adds to it to the passed `element` - * with the provided `options`. + * Controller factory function which creates a {@link KeyBindingController} instance and attaches it to the provided host. + * + * @param element - The host element to which the controller will be attached. + * @param options - Optional configuration for the controller. + * @returns The created {@link KeyBindingController} instance. + * + * @example + * ```ts + * class MyComponent extends LitElement { + * private _keyBindings = addKeybindings(this, { + * skip: ['input', 'textarea'], // Optional: Skip key events originating from these elements + * bindingDefaults: { preventDefault: true }, // Optional: Default options for all bindings + * }); + * + * constructor() { + * super(); + * this._keyBindings.set('ctrl+s', this._handleSave); // Register a key binding + * } + * ``` */ export function addKeybindings( element: ReactiveControllerHost & Element, @@ -427,11 +507,11 @@ export function addKeybindings( } export type { + KeyBindingController, + KeyBindingControllerOptions, KeyBindingHandler, KeyBindingObserverCleanup, + KeyBindingOptions, KeyBindingSkipCallback, KeyBindingTrigger, - KeyBindingControllerOptions, - KeyBindingOptions, - KeyBindingController, }; diff --git a/src/components/common/util.ts b/src/components/common/util.ts index 29e55b3407..85aa6af973 100644 --- a/src/components/common/util.ts +++ b/src/components/common/util.ts @@ -307,11 +307,31 @@ export function isEmpty( return 'length' in x ? x.length < 1 : x.size < 1; } +/** + * Ensures the given value is wrapped in an array. If the value is already an array, it is returned as-is. If the value is undefined, an empty array is returned. + * + * @example + * ```typescript + * asArray(5); // [5] + * asArray([1, 2, 3]); // [1, 2, 3] + * asArray(undefined); // [] + * ``` + */ export function asArray(value?: T | T[]): T[] { if (!isDefined(value)) return []; return Array.isArray(value) ? value : [value]; } +/** + * Splits an array into two based on a predicate function, returning a tuple of [truthy, falsy] arrays. + * + * @example + * ```typescript + * const [evens, odds] = partition([1, 2, 3, 4], x => x % 2 === 0); + * console.log(evens); // [2, 4] + * console.log(odds); // [1, 3] + * ``` + */ export function partition( array: T[], isTruthy: (value: T) => boolean diff --git a/src/components/date-time-input/date-time-input.ts b/src/components/date-time-input/date-time-input.ts index 80a12f9329..a41799ffdf 100644 --- a/src/components/date-time-input/date-time-input.ts +++ b/src/components/date-time-input/date-time-input.ts @@ -269,7 +269,7 @@ export default class IgcDateTimeInputComponent extends EventEmitterMixin< addKeybindings(this, { skip: () => this.readOnly, - bindingDefaults: { triggers: ['keydownRepeat'] }, + bindingDefaults: { repeat: true }, }) .set([ctrlKey, ';'], this._setCurrentDateTime) .set(arrowUp, this._keyboardSpin.bind(this, 'up')) diff --git a/src/components/dropdown/dropdown.ts b/src/components/dropdown/dropdown.ts index b89e036a42..63f2f2811c 100644 --- a/src/components/dropdown/dropdown.ts +++ b/src/components/dropdown/dropdown.ts @@ -212,7 +212,7 @@ export default class IgcDropdownComponent extends EventEmitterMixin< this._keyBindings = addKeybindings(this, { skip: () => !this.open, - bindingDefaults: { preventDefault: true, triggers: ['keydownRepeat'] }, + bindingDefaults: { preventDefault: true, repeat: true }, }) .set(tabKey, this.onTabKey, { preventDefault: false, diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts index 6633ff70d5..317396bf3c 100644 --- a/src/components/radio/radio.ts +++ b/src/components/radio/radio.ts @@ -183,7 +183,7 @@ export default class IgcRadioComponent extends FormAssociatedCheckboxRequiredMix addKeybindings(this, { skip: () => this.disabled, - bindingDefaults: { preventDefault: true, triggers: ['keydownRepeat'] }, + bindingDefaults: { preventDefault: true, repeat: true }, }) .set(arrowLeft, () => this._navigate(isLTR(this) ? -1 : 1)) .set(arrowRight, () => this._navigate(isLTR(this) ? 1 : -1)) diff --git a/src/components/select/select.ts b/src/components/select/select.ts index 8ceb39519a..a59782823c 100644 --- a/src/components/select/select.ts +++ b/src/components/select/select.ts @@ -292,7 +292,7 @@ export default class IgcSelectComponent extends FormAssociatedRequiredMixin( addKeybindings(this, { skip: () => this.disabled, - bindingDefaults: { preventDefault: true, triggers: ['keydownRepeat'] }, + bindingDefaults: { preventDefault: true, repeat: true }, }) .set([altKey, arrowDown], this._handleAltArrowDown) .set([altKey, arrowUp], this._handleAltArrowUp)