Skip to content

Initial set of Keytips work in experiments#4062

Merged
dzearing merged 84 commits intomicrosoft:masterfrom
kelseyyoung:features/keytips2
Mar 2, 2018
Merged

Initial set of Keytips work in experiments#4062
dzearing merged 84 commits intomicrosoft:masterfrom
kelseyyoung:features/keytips2

Conversation

@kelseyyoung
Copy link
Copy Markdown
Contributor

@kelseyyoung kelseyyoung commented Feb 21, 2018

Pull request checklist

  • Addresses an existing issue: Fixes #0000
  • Include a change request file using $ 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

keytiplayerexample

Focus areas to test

kelseyyoung and others added 30 commits February 1, 2018 16:50
- 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
// 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)) {
Copy link
Copy Markdown
Member

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++) {
Copy link
Copy Markdown
Member

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

Copy link
Copy Markdown
Contributor Author

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

Copy link
Copy Markdown
Member

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) {
Copy link
Copy Markdown
Member

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;
}
}
Copy link
Copy Markdown
Member

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 ?

Copy link
Copy Markdown
Contributor Author

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

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

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)

Copy link
Copy Markdown
Member

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++) {
Copy link
Copy Markdown
Member

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)

Copy link
Copy Markdown
Contributor Author

@kelseyyoung kelseyyoung Feb 22, 2018

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!

<div style={ divStyle }>
<div>
<ActionButton
data-ktp-id={ convertSequencesToKeytipID(this.keytipMap.Pivot1Keytip.keySequences) }
Copy link
Copy Markdown
Member

@dzearing dzearing Mar 1, 2018

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 }
/>

Copy link
Copy Markdown
Contributor Author

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]);
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

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

Copy link
Copy Markdown
Member

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 {
Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Contributor Author

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

Copy link
Copy Markdown
Member

@dzearing dzearing left a 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.

@dzearing dzearing merged commit 44ca7b8 into microsoft:master Mar 2, 2018
Markionium added a commit to Markionium/office-ui-fabric-react that referenced this pull request Mar 4, 2018
* 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.
  ...
Markionium added a commit to Markionium/office-ui-fabric-react that referenced this pull request Mar 5, 2018
* 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)
  ...
@microsoft microsoft locked as resolved and limited conversation to collaborators Aug 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants