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
- 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
- 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.
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.
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.
@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.
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.
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.
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.
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.
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.
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)
| * @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.
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.
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
| <div style={ divStyle }> | ||
| <div> | ||
| <ActionButton | ||
| data-ktp-id={ convertSequencesToKeytipID(this.keytipMap.Pivot1Keytip.keySequences) } |
There was a problem hiding this comment.
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.
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.
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.
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.
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.
Rather than an API which takes a single keytip, why not take in a map of keytips?
There was a problem hiding this comment.
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
dzearing
left a comment
There was a problem hiding this comment.
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) ...
Pull request checklist
$ npm run changeDescription 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