Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "office-ui-fabric-react",
"comment": "Allow for more control over event handling for keytips",
"type": "minor"
}
],
"packageName": "office-ui-fabric-react",
"email": "keyou@microsoft.com"
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,22 @@ export interface IKeytipProps {
visible?: boolean;

/**
* Function to call when this keytip is activated
* 'el' is the DOM element marked with 'data-ktp-execute-target'
* Function to call when this keytip is activated.
* 'executeTarget' is the DOM element marked with 'data-ktp-execute-target'.
* 'target' is the DOM element marked with 'data-ktp-target'.
*
* @type {(HTMLElement) => void}
* @type {(HTMLElement | null, HTMLElement | null) => void}
*/
onExecute?: (el: HTMLElement | null) => void;
onExecute?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;

/**
* Function to call when the keytip is returned to
* 'el' is the DOM element marked with 'data-ktp-execute-target'
* Function to call when the keytip is the currentKeytip and a return sequence is pressed.
* 'executeTarget' is the DOM element marked with 'data-ktp-execute-target'.
* 'target' is the DOM element marked with 'data-ktp-target'.
*
* @type {(HTMLElement) => void}
* @type {(HTMLElement | null, HTMLElement | null) => void}
*/
onReturn?: (el: HTMLElement | null) => void;
onReturn?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;

/**
* Array of KeySequences which is the full key sequence to trigger this keytip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export interface IKeytipTreeNode {
/**
* Control's execute function for when keytip is invoked, passed from the component to the Manager in the IKeytipProps
*/
onExecute?: (el: HTMLElement | null) => void;
onExecute?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;

/**
* Function to execute when we return to this keytip
*/
onReturn?: (el: HTMLElement | null) => void;
onReturn?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;

/**
* List of keytip IDs that should become visible when this keytip is pressed, can be empty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { KeytipTree } from './KeytipTree';
import { IKeytipTreeNode } from './IKeytipTreeNode';
import {
ktpTargetFromId,
ktpTargetFromSequences,
sequencesToID,
mergeOverflows
} from '../../utilities/keytips/KeytipUtils';
Expand Down Expand Up @@ -193,23 +194,23 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
*
* @param transitionKey - IKeytipTransitionKey received by the layer to process
*/
public processTransitionInput(transitionKey: IKeytipTransitionKey): void {
public processTransitionInput(transitionKey: IKeytipTransitionKey, ev?: React.KeyboardEvent<HTMLElement>): void {
const currKtp = this._keytipTree.currentKeytip;
if (transitionKeysContain(this.props.keytipExitSequences!, transitionKey) && currKtp) {
// If key sequence is in 'exit sequences', exit keytip mode
this._keyHandled = true;
this._exitKeytipMode();
this._exitKeytipMode(ev);
} else if (transitionKeysContain(this.props.keytipReturnSequences!, transitionKey)) {
// If key sequence is in return sequences, move currentKeytip to parent (or if currentKeytip is the root, exit)
if (currKtp) {
this._keyHandled = true;
if (currKtp.id === this._keytipTree.root.id) {
// We are at the root, exit keytip mode
this._exitKeytipMode();
this._exitKeytipMode(ev);
} else {
// If this keytip has a onReturn prop, we execute the func.
if (currKtp.onReturn) {
currKtp.onReturn(this._getKeytipDOMElement(currKtp.id));
currKtp.onReturn(this._getKtpExecuteTarget(currKtp), this._getKtpTarget(currKtp));
}

// Reset currentSequence
Expand All @@ -232,7 +233,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
*
* @param key - Key pressed by the user
*/
public processInput(key: string): void {
public processInput(key: string, ev?: React.KeyboardEvent<HTMLElement>): void {
// Concat the input key with the current sequence
const currSequence: string = this._currentSequence + key;
let currKtp = this._keytipTree.currentKeytip;
Expand All @@ -246,14 +247,14 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay

// Execute this node's onExecute if defined
if (currKtp.onExecute) {
currKtp.onExecute(this._getKeytipDOMElement(currKtp.id));
currKtp.onExecute(this._getKtpExecuteTarget(currKtp), this._getKtpTarget(currKtp));
// Reset currKtp, this might have changed from the onExecute
currKtp = this._keytipTree.currentKeytip;
}

// To exit keytipMode after executing the keytip it must not have a menu or have dynamic children
if (currKtpChildren.length === 0 && !(currKtp.hasDynamicChildren || currKtp.hasMenu)) {
this._exitKeytipMode();
this._exitKeytipMode(ev);
} else {
// Show all children keytips
this.showKeytips(currKtpChildren);
Expand Down Expand Up @@ -323,7 +324,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
/**
* Exits keytip mode for this layer
*/
private _exitKeytipMode(): void {
private _exitKeytipMode(ev?: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>): void {
this._keytipTree.currentKeytip = undefined;
this._currentSequence = '';
// Hide all keytips
Expand All @@ -336,7 +337,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
this._setInKeytipMode(false /* inKeytipMode */);

if (this.props.onExitKeytipMode) {
this.props.onExitKeytipMode();
this.props.onExitKeytipMode(ev);
}
}

Expand All @@ -362,7 +363,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
// Execute the overflow button's onExecute
const overflowKeytipNode = this._keytipTree.getNode(sequencesToID(overflowButtonSequences));
if (overflowKeytipNode && overflowKeytipNode.onExecute) {
overflowKeytipNode.onExecute(this._getKeytipDOMElement(overflowKeytipNode.id));
overflowKeytipNode.onExecute(this._getKtpExecuteTarget(overflowKeytipNode), this._getKtpTarget(overflowKeytipNode));
}
}

Expand All @@ -375,7 +376,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
private _onDismiss = (ev?: React.MouseEvent<HTMLElement>): void => {
// if we are in keytip mode, then exit keytip mode
if (this.state.inKeytipMode) {
this._exitKeytipMode();
this._exitKeytipMode(ev);
}
}

Expand Down Expand Up @@ -403,9 +404,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
case 'Right':
if (this.state.inKeytipMode) {
this._keyHandled = true;
this._exitKeytipMode();
ev.preventDefault();
ev.stopPropagation();
this._exitKeytipMode(ev);
}
break;
default:
Expand All @@ -422,7 +421,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
}
const transitionKey: IKeytipTransitionKey = { key };
transitionKey.modifierKeys = this._getModifierKey(key, ev);
this.processTransitionInput(transitionKey);
this.processTransitionInput(transitionKey, ev);
break;
}
}
Expand Down Expand Up @@ -453,7 +452,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
private _onKeyPress = (ev: React.KeyboardEvent<HTMLElement>): void => {
if (this.state.inKeytipMode && !this._keyHandled) {
// Call processInput
this.processInput(ev.key.toLocaleLowerCase());
this.processInput(ev.key.toLocaleLowerCase(), ev);
ev.preventDefault();
ev.stopPropagation();
}
Expand Down Expand Up @@ -541,7 +540,7 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
}

if (this._keytipTree.currentKeytip.onExecute) {
this._keytipTree.currentKeytip.onExecute(this._getKeytipDOMElement(this._keytipTree.currentKeytip.id));
this._keytipTree.currentKeytip.onExecute(this._getKtpExecuteTarget(this._keytipTree.currentKeytip), this._getKtpTarget(this._keytipTree.currentKeytip));
}
}

Expand Down Expand Up @@ -580,15 +579,12 @@ export class KeytipLayerBase extends BaseComponent<IKeytipLayerProps, IKeytipLay
}
}

/**
* Gets the DOM element for the specified keytip
*
* @param keytipId - ID of the keytip to query for
* @returns {HTMLElement | null} DOM element of the keytip if found
*/
private _getKeytipDOMElement(keytipId: string): HTMLElement | null {
const dataKtpExecuteTarget = ktpTargetFromId(keytipId);
return getDocument()!.querySelector(dataKtpExecuteTarget);
private _getKtpExecuteTarget(currKtp: IKeytipTreeNode): HTMLElement | null {
return getDocument()!.querySelector(ktpTargetFromId(currKtp.id));
}

private _getKtpTarget(currKtp: IKeytipTreeNode): HTMLElement | null {
return getDocument()!.querySelector(ktpTargetFromSequences(currKtp.keySequences));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ export interface IKeytipLayerProps extends React.Props<IKeytipLayer> {
keytipExitSequences?: IKeytipTransitionKey[];

/**
* Callback function triggered when keytip mode is exited
* Callback function triggered when keytip mode is exited.
* ev is the Mouse or Keyboard Event that triggered the exit, if any.
*
* @type {() => void}
*/
onExitKeytipMode?: () => void;
onExitKeytipMode?: (ev?: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>) => void;

/**
* Callback function triggered when keytip mode is entered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export class KeytipTree {
* @param persisted - T/F if this keytip should be marked as persisted
*/
public addNode(keytipProps: IKeytipProps, uniqueID: string, persisted?: boolean): void {
const { keySequences, hasDynamicChildren, overflowSetSequence, hasMenu, onExecute, onReturn, disabled } = keytipProps;
const fullSequence = this._getFullSequence(keytipProps);
const nodeID = sequencesToID(fullSequence);

Expand All @@ -44,8 +43,7 @@ export class KeytipTree {
const parentID = this._getParentID(fullSequence);

// Create node and add to map
const node = this._createNode(nodeID, keySequences, parentID, [], hasDynamicChildren, overflowSetSequence, hasMenu,
onExecute, onReturn, disabled, persisted);
const node = this._createNode(nodeID, parentID, [], keytipProps, persisted);
this.nodeMap[uniqueID] = node;

// Try to add self to parents children, if they exist
Expand Down Expand Up @@ -240,16 +238,11 @@ export class KeytipTree {

private _createNode(
id: string,
keySequences: string[],
parentId: string,
children: string[],
hasDynamicChildren?: boolean,
overflowSetSequence?: string[],
hasMenu?: boolean,
onExecute?: (el: HTMLElement) => void,
onReturn?: (el: HTMLElement) => void,
disabled?: boolean,
keytipProps: IKeytipProps,
persisted?: boolean): IKeytipTreeNode {
const { keySequences, hasDynamicChildren, overflowSetSequence, hasMenu, onExecute, onReturn, disabled } = keytipProps;
const node = {
id,
keySequences,
Expand Down