first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,53 @@
export interface IKeytipTreeNode {
/**
* ID of the <Keytip> DOM element. Needed to locate the correct keytip in the KeytipLayer's 'keytip' state array
*/
id: string;
/**
* KeySequence that invokes this KeytipTreeNode's onExecute function
*/
keySequences: string[];
/**
* Overflow set sequence for this keytip
*/
overflowSetSequence?: string[];
/**
* Control's execute function for when keytip is invoked, passed from the component to the Manager in the IKeytipProps
*/
onExecute?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;
/**
* Function to execute when we return to this keytip
*/
onReturn?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;
/**
* List of keytip IDs that should become visible when this keytip is pressed, can be empty
*/
children: string[];
/**
* Parent keytip ID
*/
parent: string;
/**
* Whether or not this keytip will have children keytips that are dynamically created (DOM is generated on
* keytip activation). Common cases are keytips in a menu or modal.
*/
hasDynamicChildren?: boolean;
/**
* Whether or not this keytip belongs to a component that has a menu
* Keytip mode will stay on when a menu is opened, even if the items in that menu have no keytips
*/
hasMenu?: boolean;
/**
* T/F if this keytip's component is currently disabled
*/
disabled?: boolean;
/**
* T/F if this keytip is a persisted keytip
*/
persisted?: boolean;
/**
* Whether or not this keytip belongs to a component that is in an overflow menu
* and also has a menu
*/
hasOverflowSubMenu?: boolean;
}
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=IKeytipTreeNode.js.map
@@ -0,0 +1 @@
{"version":3,"file":"IKeytipTreeNode.js","sourceRoot":"../src/","sources":["components/KeytipLayer/IKeytipTreeNode.ts"],"names":[],"mappings":"","sourcesContent":["export interface IKeytipTreeNode {\n /**\n * ID of the <Keytip> DOM element. Needed to locate the correct keytip in the KeytipLayer's 'keytip' state array\n */\n id: string;\n\n /**\n * KeySequence that invokes this KeytipTreeNode's onExecute function\n */\n keySequences: string[];\n\n /**\n * Overflow set sequence for this keytip\n */\n overflowSetSequence?: string[];\n\n /**\n * Control's execute function for when keytip is invoked, passed from the component to the Manager in the IKeytipProps\n */\n onExecute?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;\n\n /**\n * Function to execute when we return to this keytip\n */\n onReturn?: (executeTarget: HTMLElement | null, target: HTMLElement | null) => void;\n\n /**\n * List of keytip IDs that should become visible when this keytip is pressed, can be empty\n */\n children: string[];\n\n /**\n * Parent keytip ID\n */\n parent: string;\n\n /**\n * Whether or not this keytip will have children keytips that are dynamically created (DOM is generated on\n * keytip activation). Common cases are keytips in a menu or modal.\n */\n hasDynamicChildren?: boolean;\n\n /**\n * Whether or not this keytip belongs to a component that has a menu\n * Keytip mode will stay on when a menu is opened, even if the items in that menu have no keytips\n */\n hasMenu?: boolean;\n\n /**\n * T/F if this keytip's component is currently disabled\n */\n disabled?: boolean;\n\n /**\n * T/F if this keytip is a persisted keytip\n */\n persisted?: boolean;\n\n /**\n * Whether or not this keytip belongs to a component that is in an overflow menu\n * and also has a menu\n */\n hasOverflowSubMenu?: boolean;\n}\n"]}
@@ -0,0 +1,138 @@
import * as React from 'react';
import { KeytipTree } from './KeytipTree';
import type { IKeytipLayerProps } from './KeytipLayer.types';
import type { IKeytipProps } from '../../Keytip';
import type { IKeytipTransitionKey } from '../../utilities/keytips/IKeytipTransitionKey';
import type { JSXElement } from '@fluentui/utilities';
export interface IKeytipLayerState {
inKeytipMode: boolean;
keytips: IKeytipProps[];
visibleKeytips: IKeytipProps[];
}
/**
* A layer that holds all keytip items
* {@docCategory Keytips}
*/
export declare class KeytipLayerBase extends React.Component<IKeytipLayerProps, IKeytipLayerState> {
static defaultProps: IKeytipLayerProps;
static contextType: React.Context<import("@fluentui/react-window-provider").WindowProviderProps>;
context: any;
private _events;
private _async;
private _keytipTree;
private _keytipManager;
private _classNames;
private _currentSequence;
private _newCurrentKeytipSequences?;
private _delayedKeytipQueue;
private _delayedQueueTimeout;
private _keyHandled;
constructor(props: IKeytipLayerProps, context: any);
render(): JSXElement;
componentDidMount(): void;
componentWillUnmount(): void;
getCurrentSequence(): string;
getKeytipTree(): KeytipTree;
/**
* Processes an IKeytipTransitionKey entered by the user
*
* @param transitionKey - IKeytipTransitionKey received by the layer to process
*/
processTransitionInput(transitionKey: IKeytipTransitionKey, ev?: React.KeyboardEvent<HTMLElement>): void;
/**
* Processes inputs from the document listener and traverse the keytip tree
*
* @param key - Key pressed by the user
*/
processInput(key: string, ev?: React.KeyboardEvent<HTMLElement>): void;
/**
* Show the given keytips and hide all others
*
* @param ids - Keytip IDs to show
*/
showKeytips(ids: string[]): void;
/**
* Enters keytip mode for this layer
*/
private _enterKeytipMode;
private _buildTree;
/**
* Exits keytip mode for this layer
*/
private _exitKeytipMode;
/**
* Sets the keytips state property
*
* @param keytipProps - Keytips to set in this layer
*/
private _setKeytips;
/**
* Callback function to use for persisted keytips
*
* @param overflowButtonSequences - The overflow button sequence to execute
* @param keytipSequences - The keytip that should become the 'currentKeytip' when it is registered
*/
private _persistedKeytipExecute;
private _getVisibleKeytips;
private _isKeytipInstanceTargetVisible;
private _onDismiss;
private _onKeyDown;
/**
* Gets the ModifierKeyCodes based on the keyboard event
*
* @param ev - React.KeyboardEvent
* @returns List of ModifierKeyCodes that were pressed
*/
private _getModifierKey;
private _onKeyPress;
private _onKeytipAdded;
private _onKeytipUpdated;
/**
* Helper function to do checks related to persisted/overflow keytips
* Done on keytip added and keytip updated
*
* @param keytipProps - Keytip props
*/
private _persistedKeytipChecks;
private _onKeytipRemoved;
private _onPersistedKeytipAdded;
private _onPersistedKeytipRemoved;
private _onPersistedKeytipExecute;
/**
* Trigger a keytip immediately and set it as the current keytip
*
* @param keytipProps - Keytip to trigger immediately
*/
private _triggerKeytipImmediately;
private _addKeytipToQueue;
private _removeKeytipFromQueue;
private _getKtpExecuteTarget;
private _getKtpTarget;
/**
* Returns T/F if the keytipProps keySequences match the currentKeytip, and the currentKeytip is in an overflow well
* This will make 'keytipProps' the new currentKeytip
*
* @param keytipProps - Keytip props to check
* @returns - T/F if this keytip should become the currentKeytip
*/
private _isCurrentKeytipAnAlias;
/**
* Sets if we are in keytip mode.
* Note, this sets both the state for the layer as well as
* the value that the manager will expose externally.
* @param inKeytipMode - Boolean so set whether we are in keytip mode or not
*/
private _setInKeytipMode;
/**
* Emits a warning if duplicate keytips are found for the children of the current keytip
*/
private _warnIfDuplicateKeytips;
/**
* Returns duplicates among keytip IDs.
* If the returned array is empty, no duplicates were found.
*
* @param keytipIds - Array of keytip IDs to find duplicates for
* @returns - Array of duplicates that were found. Each duplicate will only be added once to this array.
*/
private _getDuplicateIds;
}
@@ -0,0 +1,593 @@
import { __assign, __extends, __spreadArray } from "tslib";
import * as React from 'react';
import { getLayerStyles } from './KeytipLayer.styles';
import { Keytip } from '../../Keytip';
import { Layer } from '../../Layer';
import { classNamesFunction, getDocument, arraysEqual, warn, isMac, EventGroup, Async, initializeComponentRef, KeyCodes, isElementVisibleAndNotHidden, } from '../../Utilities';
import { KeytipManager } from '../../utilities/keytips/KeytipManager';
import { KeytipTree } from './KeytipTree';
import { ktpTargetFromId, ktpTargetFromSequences, sequencesToID, mergeOverflows, } from '../../utilities/keytips/KeytipUtils';
import { transitionKeysContain } from '../../utilities/keytips/IKeytipTransitionKey';
import { KeytipEvents, KTP_LAYER_ID, KTP_ARIA_SEPARATOR } from '../../utilities/keytips/KeytipConstants';
import { WindowContext } from '@fluentui/react-window-provider';
import { getDocumentEx, getWindowEx } from '../../utilities/dom';
// Default sequence is Alt-Windows (Alt-Meta) in Windows, Option-Control (Alt-Control) in Mac
var defaultStartSequence = {
key: isMac() ? 'Control' : 'Meta',
modifierKeys: [KeyCodes.alt],
};
// Default exit sequence is the same as the start sequence
var defaultExitSequence = defaultStartSequence;
// Default return sequence is Escape
var defaultReturnSequence = {
key: 'Escape',
};
var getClassNames = classNamesFunction();
/**
* A layer that holds all keytip items
* {@docCategory Keytips}
*/
var KeytipLayerBase = /** @class */ (function (_super) {
__extends(KeytipLayerBase, _super);
function KeytipLayerBase(props, context) {
var _this = _super.call(this, props, context) || this;
_this._keytipManager = KeytipManager.getInstance();
_this._delayedKeytipQueue = [];
_this._keyHandled = false;
_this._isKeytipInstanceTargetVisible = function (keySequences, instanceCount) {
var _a;
var doc = getDocumentEx(_this.context);
var win = getWindowEx(_this.context);
var targetSelector = ktpTargetFromSequences(keySequences);
var matchingElements = (_a = doc === null || doc === void 0 ? void 0 : doc.querySelectorAll(targetSelector)) !== null && _a !== void 0 ? _a : [];
// If there are multiple elements for the keytip sequence, return true if the element instance
// that corresponds to the keytip instance is visible, otherwise return if there is only one instance
return matchingElements.length > 1 && instanceCount <= matchingElements.length
? isElementVisibleAndNotHidden(matchingElements[instanceCount - 1], win !== null && win !== void 0 ? win : undefined)
: instanceCount === 1;
};
_this._onDismiss = function (ev) {
// if we are in keytip mode, then exit keytip mode
if (_this.state.inKeytipMode) {
_this._exitKeytipMode(ev);
}
};
_this._onKeyDown = function (ev) {
_this._keyHandled = false;
// using key since which has been deprecated and key is now widely suporrted.
// See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/which
var key = ev.key;
switch (key) {
case 'Tab':
case 'Enter':
case 'Spacebar':
case ' ':
case 'ArrowUp':
case 'Up':
case 'ArrowDown':
case 'Down':
case 'ArrowLeft':
case 'Left':
case 'ArrowRight':
case 'Right':
if (_this.state.inKeytipMode) {
_this._keyHandled = true;
_this._exitKeytipMode(ev);
}
break;
default:
// Special cases for browser-specific keys that are not at standard
// (according to http://www.w3.org/TR/uievents-key/#keys-navigation)
if (key === 'Esc') {
// Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/5290772/
key = 'Escape';
}
else if (key === 'OS' || key === 'Win') {
// Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
// Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
// and https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16424492/
key = 'Meta';
}
var transitionKey = { key: key };
transitionKey.modifierKeys = _this._getModifierKey(key, ev);
_this.processTransitionInput(transitionKey, ev);
break;
}
};
_this._onKeyPress = function (ev) {
if (_this.state.inKeytipMode && !_this._keyHandled) {
// Call processInput
_this.processInput(ev.key.toLocaleLowerCase(), ev);
ev.preventDefault();
ev.stopPropagation();
}
};
_this._onKeytipAdded = function (eventArgs) {
var _a;
var keytipProps = eventArgs.keytip;
var uniqueID = eventArgs.uniqueID;
_this._keytipTree.addNode(keytipProps, uniqueID);
_this._setKeytips();
// Add the keytip to the queue to show later
if (_this._keytipTree.isCurrentKeytipParent(keytipProps)) {
// Ensure existing children are still shown.
_this._delayedKeytipQueue = _this._delayedKeytipQueue.concat(((_a = _this._keytipTree.currentKeytip) === null || _a === void 0 ? void 0 : _a.children) || []);
_this._addKeytipToQueue(sequencesToID(keytipProps.keySequences));
// Ensure the child of currentKeytip is successfully added to currentKeytip's children and update it if not.
// Note: Added this condition because KeytipTree.addNode was not always reflecting updates made to a parent node
// in currentKeytip when that parent is the currentKeytip.
if (_this._keytipTree.currentKeytip &&
_this._keytipTree.currentKeytip.hasDynamicChildren &&
_this._keytipTree.currentKeytip.children.indexOf(keytipProps.id) < 0) {
var currNode = _this._keytipTree.getNode(_this._keytipTree.currentKeytip.id);
if (currNode) {
_this._keytipTree.currentKeytip = currNode;
}
}
}
_this._persistedKeytipChecks(keytipProps);
};
_this._onKeytipUpdated = function (eventArgs) {
var _a;
var keytipProps = eventArgs.keytip;
var uniqueID = eventArgs.uniqueID;
_this._keytipTree.updateNode(keytipProps, uniqueID);
_this._setKeytips();
if (_this._keytipTree.isCurrentKeytipParent(keytipProps)) {
// Ensure existing children are still shown.
_this._delayedKeytipQueue = _this._delayedKeytipQueue.concat(((_a = _this._keytipTree.currentKeytip) === null || _a === void 0 ? void 0 : _a.children) || []);
_this._addKeytipToQueue(sequencesToID(keytipProps.keySequences));
}
_this._persistedKeytipChecks(keytipProps);
};
/**
* Helper function to do checks related to persisted/overflow keytips
* Done on keytip added and keytip updated
*
* @param keytipProps - Keytip props
*/
_this._persistedKeytipChecks = function (keytipProps) {
if (_this._newCurrentKeytipSequences && arraysEqual(keytipProps.keySequences, _this._newCurrentKeytipSequences)) {
_this._triggerKeytipImmediately(keytipProps);
}
if (_this._isCurrentKeytipAnAlias(keytipProps)) {
var keytipSequence = keytipProps.keySequences;
if (keytipProps.overflowSetSequence) {
keytipSequence = mergeOverflows(keytipSequence, keytipProps.overflowSetSequence);
}
_this._keytipTree.currentKeytip = _this._keytipTree.getNode(sequencesToID(keytipSequence));
}
};
_this._onKeytipRemoved = function (eventArgs) {
var keytipProps = eventArgs.keytip;
var uniqueID = eventArgs.uniqueID;
// Remove keytip from the delayed queue
_this._removeKeytipFromQueue(sequencesToID(keytipProps.keySequences));
// Remove the node from the Tree
_this._keytipTree.removeNode(keytipProps, uniqueID);
_this._setKeytips();
};
_this._onPersistedKeytipAdded = function (eventArgs) {
var keytipProps = eventArgs.keytip;
var uniqueID = eventArgs.uniqueID;
_this._keytipTree.addNode(keytipProps, uniqueID, true);
};
_this._onPersistedKeytipRemoved = function (eventArgs) {
var keytipProps = eventArgs.keytip;
var uniqueID = eventArgs.uniqueID;
_this._keytipTree.removeNode(keytipProps, uniqueID);
};
_this._onPersistedKeytipExecute = function (eventArgs) {
_this._persistedKeytipExecute(eventArgs.overflowButtonSequences, eventArgs.keytipSequences);
};
/**
* Sets if we are in keytip mode.
* Note, this sets both the state for the layer as well as
* the value that the manager will expose externally.
* @param inKeytipMode - Boolean so set whether we are in keytip mode or not
*/
_this._setInKeytipMode = function (inKeytipMode) {
_this.setState({ inKeytipMode: inKeytipMode });
_this._keytipManager.inKeytipMode = inKeytipMode;
};
/**
* Emits a warning if duplicate keytips are found for the children of the current keytip
*/
_this._warnIfDuplicateKeytips = function () {
var duplicateKeytips = _this._getDuplicateIds(_this._keytipTree.getChildren());
if (duplicateKeytips.length) {
warn('Duplicate keytips found for ' + duplicateKeytips.join(', '));
}
};
/**
* Returns duplicates among keytip IDs.
* If the returned array is empty, no duplicates were found.
*
* @param keytipIds - Array of keytip IDs to find duplicates for
* @returns - Array of duplicates that were found. Each duplicate will only be added once to this array.
*/
_this._getDuplicateIds = function (keytipIds) {
var seenIds = {};
return keytipIds.filter(function (keytipId) {
seenIds[keytipId] = seenIds[keytipId] ? seenIds[keytipId] + 1 : 1;
// Only add the first duplicate keytip seen
return seenIds[keytipId] === 2;
});
};
initializeComponentRef(_this);
_this._events = new EventGroup(_this);
_this._async = new Async(_this);
var keytips = _this._keytipManager.getKeytips();
_this.state = {
inKeytipMode: false,
keytips: keytips,
visibleKeytips: _this._getVisibleKeytips(keytips),
};
_this._buildTree();
_this._currentSequence = '';
// Add keytip listeners
_this._events.on(_this._keytipManager, KeytipEvents.KEYTIP_ADDED, _this._onKeytipAdded);
_this._events.on(_this._keytipManager, KeytipEvents.KEYTIP_UPDATED, _this._onKeytipUpdated);
_this._events.on(_this._keytipManager, KeytipEvents.KEYTIP_REMOVED, _this._onKeytipRemoved);
_this._events.on(_this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_ADDED, _this._onPersistedKeytipAdded);
_this._events.on(_this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_REMOVED, _this._onPersistedKeytipRemoved);
_this._events.on(_this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_EXECUTE, _this._onPersistedKeytipExecute);
return _this;
}
KeytipLayerBase.prototype.render = function () {
var _this = this;
var _a = this.props, content = _a.content, styles = _a.styles;
var _b = this.state, keytips = _b.keytips, visibleKeytips = _b.visibleKeytips;
this._classNames = getClassNames(styles, {});
return (React.createElement(Layer, { styles: getLayerStyles },
React.createElement("span", { id: KTP_LAYER_ID, className: this._classNames.innerContent }, "".concat(content).concat(KTP_ARIA_SEPARATOR)),
keytips &&
keytips.map(function (keytipProps, index) {
return (React.createElement("span", { key: index, id: sequencesToID(keytipProps.keySequences), className: _this._classNames.innerContent }, keytipProps.keySequences.join(KTP_ARIA_SEPARATOR)));
}),
visibleKeytips &&
visibleKeytips.map(function (visibleKeytipProps) {
return React.createElement(Keytip, __assign({ key: sequencesToID(visibleKeytipProps.keySequences) }, visibleKeytipProps));
})));
};
KeytipLayerBase.prototype.componentDidMount = function () {
var win = getWindowEx(this.context);
// Add window listeners
this._events.on(win, 'mouseup', this._onDismiss, true /* useCapture */);
this._events.on(win, 'pointerup', this._onDismiss, true /* useCapture */);
this._events.on(win, 'resize', this._onDismiss);
this._events.on(win, 'keydown', this._onKeyDown, true /* useCapture */);
this._events.on(win, 'keypress', this._onKeyPress, true /* useCapture */);
this._events.on(win, 'scroll', this._onDismiss, true /* useCapture */);
// Add keytip listeners
this._events.on(this._keytipManager, KeytipEvents.ENTER_KEYTIP_MODE, this._enterKeytipMode);
this._events.on(this._keytipManager, KeytipEvents.EXIT_KEYTIP_MODE, this._exitKeytipMode);
};
KeytipLayerBase.prototype.componentWillUnmount = function () {
this._async.dispose();
this._events.dispose();
};
// The below public functions are only public for testing purposes
// They are not intended to be used in app code by using a KeytipLayer reference
KeytipLayerBase.prototype.getCurrentSequence = function () {
return this._currentSequence;
};
KeytipLayerBase.prototype.getKeytipTree = function () {
return this._keytipTree;
};
/**
* Processes an IKeytipTransitionKey entered by the user
*
* @param transitionKey - IKeytipTransitionKey received by the layer to process
*/
KeytipLayerBase.prototype.processTransitionInput = function (transitionKey, ev) {
var 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(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(ev);
}
else {
// If this keytip has a onReturn prop, we execute the func.
if (currKtp.onReturn) {
currKtp.onReturn(this._getKtpExecuteTarget(currKtp), this._getKtpTarget(currKtp));
}
// Reset currentSequence
this._currentSequence = '';
// Return pointer to its parent
this._keytipTree.currentKeytip = this._keytipTree.getNode(currKtp.parent);
// Show children keytips of the new currentKeytip
this.showKeytips(this._keytipTree.getChildren());
this._warnIfDuplicateKeytips();
}
}
}
else if (transitionKeysContain(this.props.keytipStartSequences, transitionKey) && !currKtp) {
// If key sequence is in 'entry sequences' and currentKeytip is null, we enter keytip mode
this._keyHandled = true;
this._enterKeytipMode(transitionKey);
this._warnIfDuplicateKeytips();
}
};
/**
* Processes inputs from the document listener and traverse the keytip tree
*
* @param key - Key pressed by the user
*/
KeytipLayerBase.prototype.processInput = function (key, ev) {
// Concat the input key with the current sequence
var currSequence = this._currentSequence + key;
var currKtp = this._keytipTree.currentKeytip;
// currentKeytip must be defined, otherwise we haven't entered keytip mode yet
if (currKtp) {
var node = this._keytipTree.getExactMatchedNode(currSequence, currKtp);
if (node) {
this._keytipTree.currentKeytip = currKtp = node;
var currKtpChildren = this._keytipTree.getChildren();
// Execute this node's onExecute if defined
if (currKtp.onExecute) {
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(ev);
}
else {
// Show all children keytips
this.showKeytips(currKtpChildren);
this._warnIfDuplicateKeytips();
}
// Clear currentSequence
this._currentSequence = '';
return;
}
var partialNodes = this._keytipTree.getPartiallyMatchedNodes(currSequence, currKtp);
if (partialNodes.length > 0) {
// We found nodes that partially match the sequence, so we show only those
// Omit showing persisted nodes here
var ids = partialNodes
.filter(function (partialNode) {
return !partialNode.persisted;
})
.map(function (partialNode) {
return partialNode.id;
});
this.showKeytips(ids);
// Save currentSequence
this._currentSequence = currSequence;
}
}
};
/**
* Show the given keytips and hide all others
*
* @param ids - Keytip IDs to show
*/
KeytipLayerBase.prototype.showKeytips = function (ids) {
// Update the visible prop in the manager
for (var _i = 0, _a = this._keytipManager.getKeytips(); _i < _a.length; _i++) {
var keytip = _a[_i];
var keytipId = sequencesToID(keytip.keySequences);
if (keytip.overflowSetSequence) {
// Check if the ID with the overflow is the keytip we're looking for
keytipId = sequencesToID(mergeOverflows(keytip.keySequences, keytip.overflowSetSequence));
}
if (ids.indexOf(keytipId) >= 0) {
keytip.visible = true;
}
else {
keytip.visible = false;
}
}
// Apply the manager changes to the Layer state
this._setKeytips();
};
/**
* Enters keytip mode for this layer
*/
KeytipLayerBase.prototype._enterKeytipMode = function (transitionKey) {
if (this._keytipManager.shouldEnterKeytipMode) {
if (this._keytipManager.delayUpdatingKeytipChange) {
this._buildTree();
this._setKeytips();
}
this._keytipTree.currentKeytip = this._keytipTree.root;
// Show children of root
this.showKeytips(this._keytipTree.getChildren());
this._setInKeytipMode(true /* inKeytipMode */);
if (this.props.onEnterKeytipMode) {
this.props.onEnterKeytipMode(transitionKey);
}
}
};
KeytipLayerBase.prototype._buildTree = function () {
this._keytipTree = new KeytipTree();
// Add regular and persisted keytips to the tree
for (var _i = 0, _a = Object.keys(this._keytipManager.keytips); _i < _a.length; _i++) {
var id = _a[_i];
var uniqueKeytip = this._keytipManager.keytips[id];
this._keytipTree.addNode(uniqueKeytip.keytip, uniqueKeytip.uniqueID);
}
for (var _b = 0, _c = Object.keys(this._keytipManager.persistedKeytips); _b < _c.length; _b++) {
var id = _c[_b];
var uniqueKeytip = this._keytipManager.persistedKeytips[id];
this._keytipTree.addNode(uniqueKeytip.keytip, uniqueKeytip.uniqueID);
}
};
/**
* Exits keytip mode for this layer
*/
KeytipLayerBase.prototype._exitKeytipMode = function (ev) {
this._keytipTree.currentKeytip = undefined;
this._currentSequence = '';
// Hide all keytips
this.showKeytips([]);
// Reset the delayed keytips if any
this._delayedQueueTimeout && this._async.clearTimeout(this._delayedQueueTimeout);
this._delayedKeytipQueue = [];
this._setInKeytipMode(false /* inKeytipMode */);
if (this.props.onExitKeytipMode) {
this.props.onExitKeytipMode(ev);
}
};
/**
* Sets the keytips state property
*
* @param keytipProps - Keytips to set in this layer
*/
KeytipLayerBase.prototype._setKeytips = function (keytipProps) {
if (keytipProps === void 0) { keytipProps = this._keytipManager.getKeytips(); }
this.setState({ keytips: keytipProps, visibleKeytips: this._getVisibleKeytips(keytipProps) });
};
/**
* Callback function to use for persisted keytips
*
* @param overflowButtonSequences - The overflow button sequence to execute
* @param keytipSequences - The keytip that should become the 'currentKeytip' when it is registered
*/
KeytipLayerBase.prototype._persistedKeytipExecute = function (overflowButtonSequences, keytipSequences) {
// Save newCurrentKeytip for later
this._newCurrentKeytipSequences = keytipSequences;
// Execute the overflow button's onExecute
var overflowKeytipNode = this._keytipTree.getNode(sequencesToID(overflowButtonSequences));
if (overflowKeytipNode && overflowKeytipNode.onExecute) {
overflowKeytipNode.onExecute(this._getKtpExecuteTarget(overflowKeytipNode), this._getKtpTarget(overflowKeytipNode));
}
};
KeytipLayerBase.prototype._getVisibleKeytips = function (keytips) {
var _this = this;
// Filter out non-visible keytips and duplicates
var seenIds = {};
return keytips.filter(function (keytip) {
var keytipId = sequencesToID(keytip.keySequences);
if (keytip.overflowSetSequence) {
// Account for overflow set sequences when checking for duplicates
keytipId = sequencesToID(mergeOverflows(keytip.keySequences, keytip.overflowSetSequence));
}
seenIds[keytipId] = seenIds[keytipId] ? seenIds[keytipId] + 1 : 1;
// Return true only if the keytip is visible and the corresponding target is also visible
return keytip.visible && _this._isKeytipInstanceTargetVisible(keytip.keySequences, seenIds[keytipId]);
});
};
/**
* Gets the ModifierKeyCodes based on the keyboard event
*
* @param ev - React.KeyboardEvent
* @returns List of ModifierKeyCodes that were pressed
*/
KeytipLayerBase.prototype._getModifierKey = function (key, ev) {
var modifierKeys = [];
if (ev.altKey && key !== 'Alt') {
modifierKeys.push(KeyCodes.alt);
}
if (ev.ctrlKey && key !== 'Control') {
modifierKeys.push(KeyCodes.ctrl);
}
if (ev.shiftKey && key !== 'Shift') {
modifierKeys.push(KeyCodes.shift);
}
if (ev.metaKey && key !== 'Meta') {
modifierKeys.push(KeyCodes.leftWindow);
}
return modifierKeys.length ? modifierKeys : undefined;
};
/**
* Trigger a keytip immediately and set it as the current keytip
*
* @param keytipProps - Keytip to trigger immediately
*/
KeytipLayerBase.prototype._triggerKeytipImmediately = function (keytipProps) {
// This keytip should become the currentKeytip and should execute right away
var keytipSequence = __spreadArray([], keytipProps.keySequences, true);
if (keytipProps.overflowSetSequence) {
keytipSequence = mergeOverflows(keytipSequence, keytipProps.overflowSetSequence);
}
// Set currentKeytip
this._keytipTree.currentKeytip = this._keytipTree.getNode(sequencesToID(keytipSequence));
if (this._keytipTree.currentKeytip) {
// Show all children keytips if any
var children = this._keytipTree.getChildren();
if (children.length) {
this.showKeytips(children);
}
if (this._keytipTree.currentKeytip.onExecute) {
this._keytipTree.currentKeytip.onExecute(this._getKtpExecuteTarget(this._keytipTree.currentKeytip), this._getKtpTarget(this._keytipTree.currentKeytip));
}
}
// Unset _newCurrKtpSequences
this._newCurrentKeytipSequences = undefined;
};
KeytipLayerBase.prototype._addKeytipToQueue = function (keytipID) {
var _this = this;
// Add keytip
this._delayedKeytipQueue.push(keytipID);
// Clear timeout
this._delayedQueueTimeout && this._async.clearTimeout(this._delayedQueueTimeout);
// Reset timeout
this._delayedQueueTimeout = this._async.setTimeout(function () {
if (_this._delayedKeytipQueue.length) {
_this.showKeytips(_this._delayedKeytipQueue);
_this._delayedKeytipQueue = [];
}
}, 300);
};
KeytipLayerBase.prototype._removeKeytipFromQueue = function (keytipID) {
var _this = this;
var index = this._delayedKeytipQueue.indexOf(keytipID);
if (index >= 0) {
// Remove keytip
this._delayedKeytipQueue.splice(index, 1);
// Clear timeout
this._delayedQueueTimeout && this._async.clearTimeout(this._delayedQueueTimeout);
// Reset timeout
this._delayedQueueTimeout = this._async.setTimeout(function () {
if (_this._delayedKeytipQueue.length) {
_this.showKeytips(_this._delayedKeytipQueue);
_this._delayedKeytipQueue = [];
}
}, 300);
}
};
KeytipLayerBase.prototype._getKtpExecuteTarget = function (currKtp) {
return getDocument().querySelector(ktpTargetFromId(currKtp.id));
};
KeytipLayerBase.prototype._getKtpTarget = function (currKtp) {
return getDocument().querySelector(ktpTargetFromSequences(currKtp.keySequences));
};
/**
* Returns T/F if the keytipProps keySequences match the currentKeytip, and the currentKeytip is in an overflow well
* This will make 'keytipProps' the new currentKeytip
*
* @param keytipProps - Keytip props to check
* @returns - T/F if this keytip should become the currentKeytip
*/
KeytipLayerBase.prototype._isCurrentKeytipAnAlias = function (keytipProps) {
var currKtp = this._keytipTree.currentKeytip;
if (currKtp &&
(currKtp.overflowSetSequence || currKtp.persisted) &&
arraysEqual(keytipProps.keySequences, currKtp.keySequences)) {
return true;
}
return false;
};
KeytipLayerBase.defaultProps = {
keytipStartSequences: [defaultStartSequence],
keytipExitSequences: [defaultExitSequence],
keytipReturnSequences: [defaultReturnSequence],
content: '',
};
KeytipLayerBase.contextType = WindowContext;
return KeytipLayerBase;
}(React.Component));
export { KeytipLayerBase };
//# sourceMappingURL=KeytipLayer.base.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
import * as React from 'react';
import type { IKeytipLayerProps } from './KeytipLayer.types';
export declare const KeytipLayer: React.FunctionComponent<IKeytipLayerProps>;
@@ -0,0 +1,7 @@
import { styled } from '../../Utilities';
import { KeytipLayerBase } from './KeytipLayer.base';
import { getStyles } from './KeytipLayer.styles';
export var KeytipLayer = styled(KeytipLayerBase, getStyles, undefined, {
scope: 'KeytipLayer',
});
//# sourceMappingURL=KeytipLayer.js.map
@@ -0,0 +1 @@
{"version":3,"file":"KeytipLayer.js","sourceRoot":"../src/","sources":["components/KeytipLayer/KeytipLayer.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGjD,MAAM,CAAC,IAAM,WAAW,GAA+C,MAAM,CAI3E,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE;IACvC,KAAK,EAAE,aAAa;CACrB,CAAC,CAAC","sourcesContent":["import * as React from 'react';\nimport { styled } from '../../Utilities';\nimport { KeytipLayerBase } from './KeytipLayer.base';\nimport { getStyles } from './KeytipLayer.styles';\nimport type { IKeytipLayerProps, IKeytipLayerStyleProps, IKeytipLayerStyles } from './KeytipLayer.types';\n\nexport const KeytipLayer: React.FunctionComponent<IKeytipLayerProps> = styled<\n IKeytipLayerProps,\n IKeytipLayerStyleProps,\n IKeytipLayerStyles\n>(KeytipLayerBase, getStyles, undefined, {\n scope: 'KeytipLayer',\n});\n"]}
@@ -0,0 +1,4 @@
import type { ILayerStyles, ILayerStyleProps } from '../../Layer';
import type { IKeytipLayerStyleProps, IKeytipLayerStyles } from './KeytipLayer.types';
export declare const getLayerStyles: (props: ILayerStyleProps) => ILayerStyles;
export declare const getStyles: (props: IKeytipLayerStyleProps) => IKeytipLayerStyles;
@@ -0,0 +1,28 @@
import { ZIndexes } from '../../Styling';
export var getLayerStyles = function (props) {
return {
root: [
{
// Prioritize the Keytips above all other Layers
zIndex: ZIndexes.KeytipLayer,
},
],
};
};
export var getStyles = function (props) {
return {
innerContent: [
{
position: 'absolute',
width: 0,
height: 0,
margin: 0,
padding: 0,
border: 0,
overflow: 'hidden',
visibility: 'hidden',
},
],
};
};
//# sourceMappingURL=KeytipLayer.styles.js.map
@@ -0,0 +1 @@
{"version":3,"file":"KeytipLayer.styles.js","sourceRoot":"../src/","sources":["components/KeytipLayer/KeytipLayer.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,MAAM,CAAC,IAAM,cAAc,GAAG,UAAC,KAAuB;IACpD,OAAO;QACL,IAAI,EAAE;YACJ;gBACE,gDAAgD;gBAChD,MAAM,EAAE,QAAQ,CAAC,WAAW;aAC7B;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,IAAM,SAAS,GAAG,UAAC,KAA6B;IACrD,OAAO;QACL,YAAY,EAAE;YACZ;gBACE,QAAQ,EAAE,UAAU;gBACpB,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,CAAC;gBACT,OAAO,EAAE,CAAC;gBACV,MAAM,EAAE,CAAC;gBACT,QAAQ,EAAE,QAAQ;gBAClB,UAAU,EAAE,QAAQ;aACrB;SACF;KACF,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { ZIndexes } from '../../Styling';\nimport type { ILayerStyles, ILayerStyleProps } from '../../Layer';\nimport type { IKeytipLayerStyleProps, IKeytipLayerStyles } from './KeytipLayer.types';\n\nexport const getLayerStyles = (props: ILayerStyleProps): ILayerStyles => {\n return {\n root: [\n {\n // Prioritize the Keytips above all other Layers\n zIndex: ZIndexes.KeytipLayer,\n },\n ],\n };\n};\n\nexport const getStyles = (props: IKeytipLayerStyleProps): IKeytipLayerStyles => {\n return {\n innerContent: [\n {\n position: 'absolute',\n width: 0,\n height: 0,\n margin: 0,\n padding: 0,\n border: 0,\n overflow: 'hidden',\n visibility: 'hidden',\n },\n ],\n };\n};\n"]}
@@ -0,0 +1,62 @@
import * as React from 'react';
import type { IRefObject, IStyleFunctionOrObject } from '../../Utilities';
import type { IStyle } from '../../Styling';
import type { IKeytipTransitionKey } from '../../utilities/keytips/IKeytipTransitionKey';
/**
* {@docCategory Keytips}
*/
export interface IKeytipLayer {
}
/**
* {@docCategory Keytips}
*/
export interface IKeytipLayerProps extends React.ClassAttributes<IKeytipLayer> {
/**
* Optional callback to access the KeytipLayer component. Use this instead of ref for accessing
* the public methods and properties of the component.
*/
componentRef?: IRefObject<IKeytipLayer>;
/**
* String to put inside the layer to be used for the aria-describedby for the component with the keytip
* Should be one of the starting sequences
*/
content: string;
/**
* List of key sequences that will start keytips mode
*/
keytipStartSequences?: IKeytipTransitionKey[];
/**
* List of key sequences that execute the return functionality in keytips
* (going back to the previous level of keytips)
*/
keytipReturnSequences?: IKeytipTransitionKey[];
/**
* List of key sequences that will exit keytips mode
*/
keytipExitSequences?: IKeytipTransitionKey[];
/**
* Callback function triggered when keytip mode is exited.
* ev is the Mouse or Keyboard Event that triggered the exit, if any.
*/
onExitKeytipMode?: (ev?: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>) => void;
/**
* Callback function triggered when keytip mode is entered
* @param transitionKey - The key sequence that triggered keytip mode, if any.
*/
onEnterKeytipMode?: (transitionKey?: IKeytipTransitionKey) => void;
/**
* (Optional) Call to provide customized styling.
*/
styles?: IStyleFunctionOrObject<IKeytipLayerStyleProps, IKeytipLayerStyles>;
}
/**
* {@docCategory Keytips}
*/
export interface IKeytipLayerStyles {
innerContent: IStyle;
}
/**
* {@docCategory Keytips}
*/
export interface IKeytipLayerStyleProps {
}
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=KeytipLayer.types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"KeytipLayer.types.js","sourceRoot":"../src/","sources":["components/KeytipLayer/KeytipLayer.types.ts"],"names":[],"mappings":"","sourcesContent":["import * as React from 'react';\nimport type { IRefObject, IStyleFunctionOrObject } from '../../Utilities';\nimport type { IStyle } from '../../Styling';\nimport type { IKeytipTransitionKey } from '../../utilities/keytips/IKeytipTransitionKey';\n\n/**\n * {@docCategory Keytips}\n */\nexport interface IKeytipLayer {}\n\n/**\n * {@docCategory Keytips}\n */\nexport interface IKeytipLayerProps extends React.ClassAttributes<IKeytipLayer> {\n /**\n * Optional callback to access the KeytipLayer component. Use this instead of ref for accessing\n * the public methods and properties of the component.\n */\n componentRef?: IRefObject<IKeytipLayer>;\n\n /**\n * String to put inside the layer to be used for the aria-describedby for the component with the keytip\n * Should be one of the starting sequences\n */\n content: string;\n\n /**\n * List of key sequences that will start keytips mode\n */\n keytipStartSequences?: IKeytipTransitionKey[];\n\n /**\n * List of key sequences that execute the return functionality in keytips\n * (going back to the previous level of keytips)\n */\n keytipReturnSequences?: IKeytipTransitionKey[];\n\n /**\n * List of key sequences that will exit keytips mode\n */\n keytipExitSequences?: IKeytipTransitionKey[];\n\n /**\n * Callback function triggered when keytip mode is exited.\n * ev is the Mouse or Keyboard Event that triggered the exit, if any.\n */\n onExitKeytipMode?: (ev?: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>) => void;\n\n /**\n * Callback function triggered when keytip mode is entered\n * @param transitionKey - The key sequence that triggered keytip mode, if any.\n */\n onEnterKeytipMode?: (transitionKey?: IKeytipTransitionKey) => void;\n\n /**\n * (Optional) Call to provide customized styling.\n */\n styles?: IStyleFunctionOrObject<IKeytipLayerStyleProps, IKeytipLayerStyles>;\n}\n\n/**\n * {@docCategory Keytips}\n */\nexport interface IKeytipLayerStyles {\n innerContent: IStyle;\n}\n\n/**\n * {@docCategory Keytips}\n */\nexport interface IKeytipLayerStyleProps {}\n"]}
@@ -0,0 +1,90 @@
import type { IKeytipProps } from '../../Keytip';
import type { IKeytipTreeNode } from './IKeytipTreeNode';
/**
* This class is responsible for handling the parent/child relationships between keytips
*/
export declare class KeytipTree {
currentKeytip?: IKeytipTreeNode;
root: IKeytipTreeNode;
nodeMap: {
[nodeId: string]: IKeytipTreeNode;
};
/**
* KeytipTree constructor
*/
constructor();
/**
* Add a keytip node to this KeytipTree
*
* @param keytipProps - Keytip to add to the Tree
* @param uniqueID - Unique ID for this keytip
* @param persisted - T/F if this keytip should be marked as persisted
*/
addNode(keytipProps: IKeytipProps, uniqueID: string, persisted?: boolean): void;
/**
* Updates a node in the tree
*
* @param keytipProps - Keytip props to update
* @param uniqueID - Unique ID for this keytip
*/
updateNode(keytipProps: IKeytipProps, uniqueID: string): void;
/**
* Removes a node from the KeytipTree
*
* @param sequence - full string of the node to remove
*/
removeNode(keytipProps: IKeytipProps, uniqueID: string): void;
/**
* Searches the currentKeytip's children to exactly match a sequence. Will not match disabled nodes but
* will match persisted nodes
*
* @param keySequence - string to match
* @param currentKeytip - The keytip whose children will try to match
* @param doc - The document for DOM operations
* @returns The node that exactly matched the keySequence, or undefined if none matched
*/
getExactMatchedNode(keySequence: string, currentKeytip: IKeytipTreeNode, doc?: Document): IKeytipTreeNode | undefined;
/**
* Searches the currentKeytip's children to find nodes that start with the given sequence. Will not match
* disabled nodes but will match persisted nodes
*
* @param keySequence - string to partially match
* @param currentKeytip - The keytip whose children will try to partially match
* @returns List of tree nodes that partially match the given sequence
*/
getPartiallyMatchedNodes(keySequence: string, currentKeytip: IKeytipTreeNode): IKeytipTreeNode[];
/**
* Get the non-persisted children of the give node
* If no node is given, will use the 'currentKeytip'
*
* @param node - Node to get the children for
* @returns List of node IDs that are the children of the node
*/
getChildren(node?: IKeytipTreeNode): string[];
/**
* Gets all nodes from their IDs
*
* @param ids - List of keytip IDs
* @returns Array of nodes that match the given IDs, can be empty
*/
getNodes(ids: string[]): IKeytipTreeNode[];
/**
* Gets a single node from its ID
*
* @param id - ID of the node to get
* @returns Node with the given ID, if found
*/
getNode(id: string): IKeytipTreeNode | undefined;
/**
* Tests if the currentKeytip in this.keytipTree is the parent of 'keytipProps'
*
* @param keytipProps - Keytip to test the parent for
* @returns T/F if the currentKeytip is this keytipProps' parent
*/
isCurrentKeytipParent(keytipProps: IKeytipProps): boolean;
private _getParentID;
private _getFullSequence;
private _getNodeSequence;
private _createNode;
private _removeChildFromParents;
}
+298
View File
@@ -0,0 +1,298 @@
import { __spreadArray } from "tslib";
import { find, getDocument, isElementVisibleAndNotHidden, values } from '../../Utilities';
import { ktpTargetFromSequences, mergeOverflows, sequencesToID } from '../../utilities/keytips/KeytipUtils';
import { KTP_LAYER_ID } from '../../utilities/keytips/KeytipConstants';
/**
* This class is responsible for handling the parent/child relationships between keytips
*/
var KeytipTree = /** @class */ (function () {
/**
* KeytipTree constructor
*/
function KeytipTree() {
this.nodeMap = {};
// Root has no keytipSequence
this.root = {
id: KTP_LAYER_ID,
children: [],
parent: '',
keySequences: [],
};
this.nodeMap[this.root.id] = this.root;
}
/**
* Add a keytip node to this KeytipTree
*
* @param keytipProps - Keytip to add to the Tree
* @param uniqueID - Unique ID for this keytip
* @param persisted - T/F if this keytip should be marked as persisted
*/
KeytipTree.prototype.addNode = function (keytipProps, uniqueID, persisted) {
var fullSequence = this._getFullSequence(keytipProps);
var nodeID = sequencesToID(fullSequence);
// Take off the last item to calculate the parent sequence
fullSequence.pop();
// Parent ID is the root if there aren't any more sequences
var parentID = this._getParentID(fullSequence);
// Create node and add to map
var node = this._createNode(nodeID, parentID, [], keytipProps, persisted);
this.nodeMap[uniqueID] = node;
// Try to add self to parents children
var parents = this.getNodes([parentID]);
parents.forEach(function (parent) { return parent.children.push(nodeID); });
};
/**
* Updates a node in the tree
*
* @param keytipProps - Keytip props to update
* @param uniqueID - Unique ID for this keytip
*/
KeytipTree.prototype.updateNode = function (keytipProps, uniqueID) {
var node = this.nodeMap[uniqueID];
if (node) {
var fullSequence = this._getFullSequence(keytipProps);
var nodeID_1 = sequencesToID(fullSequence);
// Take off the last item to calculate the parent sequence
fullSequence.pop();
// Parent ID is the root if there aren't any more sequences
var parentID = this._getParentID(fullSequence);
var prevParent = node.parent;
// Fix parent nodes if needed
if (prevParent !== parentID) {
// If parent has changed, remove child from old parent
this._removeChildFromParents(prevParent, node.id);
}
if (node.id !== nodeID_1) {
// If the ID of the node has changed, update node's parent's array of children with new ID
var parents = this.getNodes([parentID]);
parents.forEach(function (parent) {
var index = parent.children.indexOf(node.id);
index >= 0 ? (parent.children[index] = nodeID_1) : parent.children.push(nodeID_1);
});
}
// Update values
node.id = nodeID_1;
node.keySequences = keytipProps.keySequences;
node.overflowSetSequence = keytipProps.overflowSetSequence;
node.onExecute = keytipProps.onExecute;
node.onReturn = keytipProps.onReturn;
node.hasDynamicChildren = keytipProps.hasDynamicChildren;
node.hasMenu = keytipProps.hasMenu;
node.parent = parentID;
node.disabled = keytipProps.disabled;
}
};
/**
* Removes a node from the KeytipTree
*
* @param sequence - full string of the node to remove
*/
KeytipTree.prototype.removeNode = function (keytipProps, uniqueID) {
var fullSequence = this._getFullSequence(keytipProps);
var nodeID = sequencesToID(fullSequence);
// Take off the last sequence to calculate the parent ID
fullSequence.pop();
// Parent ID is the root if there aren't any more sequences
this._removeChildFromParents(this._getParentID(fullSequence), nodeID);
if (this.nodeMap[uniqueID]) {
// Remove the node from the nodeMap
delete this.nodeMap[uniqueID];
}
};
/**
* Searches the currentKeytip's children to exactly match a sequence. Will not match disabled nodes but
* will match persisted nodes
*
* @param keySequence - string to match
* @param currentKeytip - The keytip whose children will try to match
* @param doc - The document for DOM operations
* @returns The node that exactly matched the keySequence, or undefined if none matched
*/
KeytipTree.prototype.getExactMatchedNode = function (keySequence, currentKeytip, doc) {
var _this = this;
var theDoc = doc !== null && doc !== void 0 ? doc : getDocument();
var possibleNodes = this.getNodes(currentKeytip.children);
var matchingNodes = possibleNodes.filter(function (node) {
return _this._getNodeSequence(node) === keySequence && !node.disabled;
});
// If we found no nodes, we are done
if (matchingNodes.length === 0) {
return undefined;
}
// Since the matching nodes all have the same key sequence,
// Grab the first one build the correct selector
var node = matchingNodes[0];
// If we have exactly one node, return it
if (matchingNodes.length === 1) {
return node;
}
// Get the potential target elements based on a selector from the sequences
var keySequences = node.keySequences;
var overflowSetSequence = node.overflowSetSequence;
var fullKeySequences = overflowSetSequence ? mergeOverflows(keySequences, overflowSetSequence) : keySequences;
var keytipTargetSelector = ktpTargetFromSequences(fullKeySequences);
var potentialTargetElements = theDoc.querySelectorAll(keytipTargetSelector);
// If we have less nodes than the potential target elements,
// we won't be able to map element to node, return the first node.
// Note, the number of nodes could be more than the number of potential
// target elements, if an OverflowSet is involved
if (matchingNodes.length < potentialTargetElements.length) {
return node;
}
// Attempt to find the node that corresponds to the first visible/non-hidden element
var matchingIndex = Array.from(potentialTargetElements).findIndex(function (element) { var _a; return isElementVisibleAndNotHidden(element, (_a = theDoc.defaultView) !== null && _a !== void 0 ? _a : undefined); });
if (matchingIndex !== -1) {
return matchingNodes[matchingIndex];
}
// We did not find any visible elements associated with any of the nodes.
// We may be dealing with a keytip that is a submenu in an OverflowSet.
// Worst case, fall back to the first node returned
var overflowNode = matchingNodes.find(function (matchingNode) { return matchingNode.hasOverflowSubMenu; });
return overflowNode || node;
};
/**
* Searches the currentKeytip's children to find nodes that start with the given sequence. Will not match
* disabled nodes but will match persisted nodes
*
* @param keySequence - string to partially match
* @param currentKeytip - The keytip whose children will try to partially match
* @returns List of tree nodes that partially match the given sequence
*/
KeytipTree.prototype.getPartiallyMatchedNodes = function (keySequence, currentKeytip) {
var _this = this;
// Get children that are persisted
var possibleNodes = this.getNodes(currentKeytip.children);
return possibleNodes.filter(function (node) {
return _this._getNodeSequence(node).indexOf(keySequence) === 0 && !node.disabled;
});
};
/**
* Get the non-persisted children of the give node
* If no node is given, will use the 'currentKeytip'
*
* @param node - Node to get the children for
* @returns List of node IDs that are the children of the node
*/
KeytipTree.prototype.getChildren = function (node) {
var _this = this;
if (!node) {
node = this.currentKeytip;
if (!node) {
return [];
}
}
var children = node.children;
return Object.keys(this.nodeMap).reduce(function (nodes, key) {
if (children.indexOf(_this.nodeMap[key].id) >= 0 && !_this.nodeMap[key].persisted) {
nodes.push(_this.nodeMap[key].id);
}
return nodes;
}, []);
};
/**
* Gets all nodes from their IDs
*
* @param ids - List of keytip IDs
* @returns Array of nodes that match the given IDs, can be empty
*/
KeytipTree.prototype.getNodes = function (ids) {
var _this = this;
return Object.keys(this.nodeMap).reduce(function (nodes, key) {
if (ids.indexOf(_this.nodeMap[key].id) >= 0) {
nodes.push(_this.nodeMap[key]);
}
return nodes;
}, []);
};
/**
* Gets a single node from its ID
*
* @param id - ID of the node to get
* @returns Node with the given ID, if found
*/
KeytipTree.prototype.getNode = function (id) {
var nodeMapValues = values(this.nodeMap);
return find(nodeMapValues, function (node) {
return node.id === id;
});
};
/**
* Tests if the currentKeytip in this.keytipTree is the parent of 'keytipProps'
*
* @param keytipProps - Keytip to test the parent for
* @returns T/F if the currentKeytip is this keytipProps' parent
*/
KeytipTree.prototype.isCurrentKeytipParent = function (keytipProps) {
if (this.currentKeytip) {
var fullSequence = __spreadArray([], keytipProps.keySequences, true);
if (keytipProps.overflowSetSequence) {
fullSequence = mergeOverflows(fullSequence, keytipProps.overflowSetSequence);
}
// Take off the last sequence to calculate the parent ID
fullSequence.pop();
// Parent ID is the root if there aren't any more sequences
var parentID = fullSequence.length === 0 ? this.root.id : sequencesToID(fullSequence);
var matchesCurrWithoutOverflow = false;
if (this.currentKeytip.overflowSetSequence) {
var currKeytipIdWithoutOverflow = sequencesToID(this.currentKeytip.keySequences);
matchesCurrWithoutOverflow = currKeytipIdWithoutOverflow === parentID;
}
return matchesCurrWithoutOverflow || this.currentKeytip.id === parentID;
}
return false;
};
KeytipTree.prototype._getParentID = function (fullSequence) {
return fullSequence.length === 0 ? this.root.id : sequencesToID(fullSequence);
};
KeytipTree.prototype._getFullSequence = function (keytipProps) {
var fullSequence = __spreadArray([], keytipProps.keySequences, true);
if (keytipProps.overflowSetSequence) {
fullSequence = mergeOverflows(fullSequence, keytipProps.overflowSetSequence);
}
return fullSequence;
};
KeytipTree.prototype._getNodeSequence = function (node) {
var fullSequence = __spreadArray([], node.keySequences, true);
if (node.overflowSetSequence) {
fullSequence = mergeOverflows(fullSequence, node.overflowSetSequence);
}
return fullSequence[fullSequence.length - 1];
};
KeytipTree.prototype._createNode = function (id, parentId, children, keytipProps, persisted) {
var _this = this;
var keySequences = keytipProps.keySequences, hasDynamicChildren = keytipProps.hasDynamicChildren, overflowSetSequence = keytipProps.overflowSetSequence, hasMenu = keytipProps.hasMenu, onExecute = keytipProps.onExecute, onReturn = keytipProps.onReturn, disabled = keytipProps.disabled, hasOverflowSubMenu = keytipProps.hasOverflowSubMenu;
var node = {
id: id,
keySequences: keySequences,
overflowSetSequence: overflowSetSequence,
parent: parentId,
children: children,
onExecute: onExecute,
onReturn: onReturn,
hasDynamicChildren: hasDynamicChildren,
hasMenu: hasMenu,
disabled: disabled,
persisted: persisted,
hasOverflowSubMenu: hasOverflowSubMenu,
};
node.children = Object.keys(this.nodeMap).reduce(function (array, nodeMapKey) {
if (_this.nodeMap[nodeMapKey].parent === id) {
array.push(_this.nodeMap[nodeMapKey].id);
}
return array;
}, []);
return node;
};
KeytipTree.prototype._removeChildFromParents = function (parentID, childID) {
var parents = this.getNodes([parentID]);
parents.forEach(function (parent) {
var childIndex = parent.children.indexOf(childID);
if (childIndex >= 0) {
parent.children.splice(childIndex, 1);
}
});
};
return KeytipTree;
}());
export { KeytipTree };
//# sourceMappingURL=KeytipTree.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
export * from './KeytipLayer';
export * from './KeytipLayer.base';
export * from './KeytipLayer.types';
+4
View File
@@ -0,0 +1,4 @@
export * from './KeytipLayer';
export * from './KeytipLayer.base';
export * from './KeytipLayer.types';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["components/KeytipLayer/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC","sourcesContent":["export * from './KeytipLayer';\nexport * from './KeytipLayer.base';\nexport * from './KeytipLayer.types';\n"]}