-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial set of Keytips work in experiments #4062
Conversation
- Made key sequences in KeytipLayer optional again - Add utility constants, refactored code - Moved IKeySequence to string code from KeytipManager to IKeytipSequence, also moved tests
- Manually add Keytip through manager after mount for experiments
…fice-ui-fabric-react into features/keytips2
- Keytips will now hide on scroll
- Introduce idea of modifier key codes - Split processInput with processTransitionInput
- Remove ID and KeytipTarget from Keytip props
- Added some documentation and Dos/Donts
…fice-ui-fabric-react into features/keytips2
- Move and rename ModifierKeyCodes to Keytip.types to prevent errors in testing
// Try to find keytipProps in previousKeytips to update | ||
let keytipToUpdateIndex = -1; | ||
for (let i = 0; i < previousKeytips.length; i++) { | ||
if (fullKeySequencesAreEqual(keytipProps.keySequences, previousKeytips[i].keySequences)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use findIndex from the utilities package here instead of rolling your own loop.
* @returns {boolean} T/F if 'sequences' contains 'seq' | ||
*/ | ||
export function keySequencesContain(sequences: IKeySequence[], seq: IKeySequence): boolean { | ||
for (let i = 0; i < sequences.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this is just an implemetation of includes, unfortunately this doesn't work in IE11, but we can add an implementation in utilities (like there is for findIndex) to use to reduce boilerplate here in a few places. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@christiango can we use includes() if we have an array of objects? (in this case, IKeySequence[]) I would think then we would need an object deep equals function...
at its base IKeySequence.keys is an array of strings, but that is an equality check we do with .join(), it wouldn't be an includes() check there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use find if you need more elaborate comparison logic
*/ | ||
export function convertSequencesToKeytipID(keySequences: IKeySequence[]): string { | ||
let conversion = ktpPrefix; | ||
for (let keySequence of keySequences) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good candidate for array.prototype.reduce instead of a for of loop
if (mod2Copy.length !== 0) { | ||
return false; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is N^2, can we use a JavaScript set instead? I'm not sure what the policy on using Set is in fabric, it is supported by IE11, but it is in typescripts ES2015.collections library, which is not included by default when transpiling to ES5. Thoughts @dzearing ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about sorting the two arrays and looping through? The arrays are just of enums (so numbers) so they can be sorted naturally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How big do you expect these arrays to get? Sorting will still be slower than using the set, which might matter if these lists get pretty long or this code runs a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The arrays are just a list of modifiers (Alt, Shift, Ctrl). We only have 5 defined now so that would be the max, but the developer wouldn't define more then 2-3 (otherwise the user has to press 5 keys to enable keytip mode)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorting is fine then
* @returns {boolean} T/F if 'keys' contains 'key' | ||
*/ | ||
export function transitionKeysContain(keys: IKeytipTransitionKey[], key: IKeytipTransitionKey): boolean { | ||
for (let i = 0; i < keys.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, go through all the for loops we have here and see if we can use some of the built in higher order functions in JavaScript (or the equivalent implementation in fabric utilities when that API is not supported in IE11)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah there were a few places where we wanted to use those functions but didn't because of compatibility. I didn't know there would be equivalent ones in fabric so thanks for pointing that out!
- Use reduce or map where applicable
…fice-ui-fabric-react into features/keytips2
<div style={ divStyle }> | ||
<div> | ||
<ActionButton | ||
data-ktp-id={ convertSequencesToKeytipID(this.keytipMap.Pivot1Keytip.keySequences) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feedback (not required to change in this PR)
abstract in a helper function so that caller doesn't know about data-*.
Also this is quite verbose. There needs to be a better experience than to call an API convertSequencesToKeytipID
here for the caller.
If we go with the helper, maybe BaseComponent could auto dispose the cached options from global state, if that's what we do to get access to the global state.
private _keytips = this.autoDispose(registerKeytips({
pivot1: { options },
pivot2: { options }
}));
// ...and then:
<ActionButton
{ ...this._keytips.pivot1 }
/>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, this is only because the keytipProps haven't been added to ActionButton. We have to mock it right now because we haven't moved to office-ui-fabric-react. Hardcoding the data-ktp-id would be replaced with just defining keytipProps
// Manually add keytips to the KeytipManager for now | ||
// This should really be done in each component | ||
for (let component of Object.keys(this.keytipMap)) { | ||
registerKeytip(this.keytipMap[component]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a registerKeytip
call but no unregisterKeytip
.
I am really nervous about this pattern because if it's easy for you to forget to do this, it will be easy for partners to as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is also mocked, the register and unregister should only be in the component render() and componentWillUnmount() (I know we have discussed this, we can try to come up with a better solution). In this sample we don't have any dynamic DOM elements so we aren't doing any unregistering. When we move to the normal package we will have keytips on menus, etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't the sample itself dynamic? E.g. you will have a memory leak if you go to the page, then to another, then back to the original.
* | ||
* @param keytipProps - IKeytipProps to add to this layer | ||
*/ | ||
public registerKeytip(keytipProps: IKeytipProps): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than an API which takes a single keytip, why not take in a map of keytips?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
register should only be called from one component''s render() at a time, so I don't see a use case for registering multiple right now but if we come across one we can definitely do it! would be very easy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some feedback! But let's get it in and iterate.
* master: (30 commits) Addressing Issue microsoft#4147 - Nav Buttons have invalid `aria-describedby` value (microsoft#4159) Fix issue 3608: DetailsList horizontal scroll (microsoft#4164) Update package.json Image SCSS to MergeStyles Part 2: Style Conversion (microsoft#4151) Applying package updates. [SpinButton] Consistent styles to Button and TextField (microsoft#4098) ChoiceGroup: Flex layout for image and icon options (microsoft#4137) Initial set of Keytips work in experiments (microsoft#4062) Updating tsconfig in button bundle. BaseExtendedPicker: Create contextmenu for renderedItem, fix auto focus (microsoft#3954) Dropdown: Custom render options for multiselect - Bug fix microsoft#3571 (microsoft#3589) Fix documentcard theming (microsoft#4155) BasePicker: Fix not used onBlur callback of inputProps (microsoft#4000) (microsoft#4131) Allow Elements as Callout targets (microsoft#4134) CommandBar: Fixed null commandItemWidths (microsoft#4136) add check for when this.suggestionElement may be undefined (microsoft#4157) Addressing Issue microsoft#4143 - is-selected missing on ms-Nav-link (microsoft#4158) Aria selected (microsoft#4161) Move fabric to TypeScript 2.7.2 (microsoft#4153) Updating SearchBox examples to have the removed string in placeholder prop. ...
* master: (51 commits) Applying package updates. No unused variable (microsoft#4173) Use correct _list ref string (microsoft#4168) Nav: wire a link to expand/collapse behavior if it has no URL but has children (microsoft#4171) Addressing Issue microsoft#4147 - Nav Buttons have invalid `aria-describedby` value (microsoft#4159) Fix issue 3608: DetailsList horizontal scroll (microsoft#4164) Update package.json Image SCSS to MergeStyles Part 2: Style Conversion (microsoft#4151) Applying package updates. [SpinButton] Consistent styles to Button and TextField (microsoft#4098) ChoiceGroup: Flex layout for image and icon options (microsoft#4137) Initial set of Keytips work in experiments (microsoft#4062) Updating tsconfig in button bundle. BaseExtendedPicker: Create contextmenu for renderedItem, fix auto focus (microsoft#3954) Dropdown: Custom render options for multiselect - Bug fix microsoft#3571 (microsoft#3589) Fix documentcard theming (microsoft#4155) BasePicker: Fix not used onBlur callback of inputProps (microsoft#4000) (microsoft#4131) Allow Elements as Callout targets (microsoft#4134) CommandBar: Fixed null commandItemWidths (microsoft#4136) add check for when this.suggestionElement may be undefined (microsoft#4157) ...
* Adding files to experiments * Fix most tslint errors * Fix initial tslint errors * Fix other tslint errors * Fix last compiling issues * Add keysequence files * Add pages to example app * Removed IKeySequence files in packages/utilities - Made key sequences in KeytipLayer optional again - Add utility constants, refactored code - Moved IKeySequence to string code from KeytipManager to IKeytipSequence, also moved tests * First pass of writing processInput * Update KeytipLayer Example to use data attribute - Manually add Keytip through manager after mount for experiments * Add Keytipsequencestarts with tests * Add matched and partially matched nodes tests * Add keysequenceStarts with tests and partial and exact node matched tests * Add more tests to keysquences * Add initial processInput tests * Add initial processInput tests * Clean up unused imports * Add partial nodes tests * Show/hide methods for the tree nodes * Add hasChildrenNodes prop and a test for it * Moved function to comply with tslint * Get keytip to show on starting sequence in example - Keytips will now hide on scroll * Refactor KeySequences to use strings instead of KeyCodes - Introduce idea of modifier key codes - Split processInput with processTransitionInput * disable and enable keytipmode correctly * Add more items to KeytipLayer example - Remove ID and KeytipTarget from Keytip props * Fix unit tests * Fix exit points for keys * Exit when scrolled and enter keytipmode again * Add exit sequences * Fix broken unit tests * Add modifier to example * Fix hierarchy bugs * Add more examples keytips demo * Add get execture func * Make processInput consume only one key * Change goback to return * Add overflowsetSequence props * Documentation and refactoring * Unregister keytip functionality - Register and unregister utility functions - removeNode tests for KeytipTree * Remove IKeytipTranstionSequence, transition keys can only be one key now * Add comments algorithm * Change addNode to receive keytipProps * Remove duplicate identifiers * Allow for multiple modifier keys - Extract transition key out into own files and tests * Fix import errors by moving KeytipManager and Tree to utilities/keytip - Remove visible from the state of the Keytip * Show keytip right away if its parent is currentKeytip * Add tests for overflow node adding * Delete keytiptree tests from wrong directory * Re-add tests to overflowset sequence for add node * Fix go back and clean current Sequence array * registerKeytip update functionality - Removed visible property from KeytipTreeNode * Updated KeytipLayer demo * Add test for keytiplink process input * Added logic for keytiplink in remove and added unit test * Add tests for matching disabled nodes * Make content required on IKeytipProps - Add element to onExecute and onReturn functions - Update KeytipLayer example to click buttons - Removed index.ts files for utils to avoid import issues * Remove outdated comments * Remove outdated comment * Comments and small refactoring * Add refactoring changes for ketip and keytipcontent * Add tests * Very basic layer test * Added examples for Keytip page - Added some documentation and Dos/Donts * Remove unused keytipstate * Remove unused files * Small fixes * Fix Keytip language example * Make KeyCodes a const enum again - Move and rename ModifierKeyCodes to Keytip.types to prevent errors in testing * Add rush change files * Add offset default prop and test UI for offset * Improve code with Fabric array utils - Use reduce or map where applicable
Pull request checklist
$ npm run change
Description of changes
Experiments: Initial set of work for Keytips. See Keytip and KeytipLayer demo pages in experiments
Utilities: Add missing key codes to KeyCodes enum
Focus areas to test