-
Notifications
You must be signed in to change notification settings - Fork 64
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
Listbox #312
Listbox #312
Conversation
@@ -0,0 +1,264 @@ | |||
import { WidgetBase } from '@dojo/widget-core/WidgetBase'; |
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.
Imports should be alphabetical
src/listbox/Listbox.ts
Outdated
active?: boolean; | ||
classes?: (string | null)[]; | ||
disabled?: boolean; | ||
label: DNode; |
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.
Alphabetize
src/listbox/Listbox.ts
Outdated
import * as css from './styles/listbox.m.css'; | ||
|
||
/* Listbox Option sub-widget */ | ||
export interface ListboxOptionProperties extends ThemeableProperties { |
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.
Can/should this be moved to its own file?
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.
Good question 😂 . It used to be, then after talking with Ant/Matt G, the conclusion was that sub-widgets shouldn't really exist because they don't stand on their own, except that they make handling certain events much easier. I moved it out of its own file so it would seem less like a standalone widget, but I agree that it seemed better organized when separate
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.
Moved back to its own file. We'll see what happens 😄
src/listbox/Listbox.ts
Outdated
* Properties that can be set on a Listbox component | ||
* | ||
* @property activeIndex Index of the currently active listbox option | ||
* @property customOption Custom widget constructor for options. Should extend ListboxOption |
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.
customOption isn't in this interface
src/listbox/Listbox.ts
Outdated
describedBy?: string; | ||
visualFocus?: boolean; | ||
id?: string; | ||
multiselect?: boolean; |
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.
multiselect not in TypeDoc
active: activeIndex === index, | ||
classes: this.getOptionClasses(activeIndex === index, disabled, selected), | ||
disabled, | ||
label: this.renderOptionLabel(option, index), |
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.
Consider moving this above (alongside the getOptionDisabled
and getOptionSelected
to standardize.
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.
Should I break out id
as well? I originally only pulled out disabled
and selected
because they're also used to get the state classes whereas label
/id
are only used once.
src/listbox/Listbox.ts
Outdated
activeIndex = 0, | ||
key = '', | ||
optionData = [], | ||
getOptionDisabled = (option: any, index: number) => 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.
Consider pulling this out into a reusable function at the top of the file
src/combobox/ComboBox.ts
Outdated
import { ThemeableMixin, ThemeableProperties, theme } from '@dojo/widget-core/mixins/Themeable'; | ||
import { WidgetBase } from '@dojo/widget-core/WidgetBase'; | ||
import { diffProperty } from '@dojo/widget-core/decorators/diffProperty'; | ||
import { reference } from '@dojo/widget-core/diff'; | ||
import ResultItem from './ResultItem'; | ||
import ResultMenu from './ResultMenu'; | ||
import Listbox from '../listbox/Listbox'; |
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.
Alphebetize
private _getResultLabel(result: any) { | ||
const { getResultLabel } = this.properties; | ||
|
||
return getResultLabel ? getResultLabel(result) : result; | ||
return getResultLabel ? getResultLabel(result) : `${result}`; |
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.
Could this just be String(result)
?
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.
Template strings are the most optimal way to do string conversion now.
* @property disabled Prevents the user from interacting with the form field | ||
* @property invalid Indicates the value entered in the form field is invalid | ||
* @property label Label settings for form label text, position, and visibility | ||
* @property multiple Whether the widget supports multiple selection |
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 is this handled now?
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.
Listbox on its own is effectively a custom multiselect widget, and the native multiselect is so universally awful that I don't think we need to go out of our way to support it.
src/listbox/Listbox.ts
Outdated
|
||
@diffProperty('activeIndex', auto) | ||
protected calculateScroll(previousProperties: ListboxProperties, { activeIndex = 0 }: ListboxProperties) { | ||
const scrollOffset = this.meta(Dimensions).get('root').scroll.top; |
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 should be a single call to this.meta(Dimensions)
not multiple
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.
Partial review. Things look good. I'm mostly confused by the apparent removal of certain pieces of functionality, like multiple
on the Select, and the ability to pass custom options and menus to components. If this is supported, it's not immediately obvious based on the properties interfaces for each component that uses ListBox. I also had some various questions.
@@ -61,68 +59,14 @@ const data = [ | |||
{ value: 'West Virginia' } | |||
]; | |||
|
|||
class CustomResultItem extends ResultItem { |
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.
Why was the example of a custom result item removed?
} | ||
} | ||
|
||
class CustomResultMenu extends ResultMenu { |
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.
Why was the example of a custom result menuremoved?
src/listbox/Listbox.ts
Outdated
} | ||
|
||
@diffProperty('activeIndex', auto) | ||
protected calculateScroll(previousProperties: ListboxProperties, { activeIndex = 0 }: ListboxProperties) { |
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 is the purpose of this function? Is this what _scrollIntoView
used to do?
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.
More or less. I separated out calculateScroll
and animateScroll
so that the latter would be easy to override (e.g. to animate the scroll)
src/listbox/Listbox.ts
Outdated
} | ||
|
||
protected onElementUpdated(element: HTMLElement, key: string) { | ||
if (key === 'root' && typeof this._scroll === 'number') { |
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.
Can you refactor this with the login in onElementCreated
?
src/listbox/Listbox.ts
Outdated
onOptionSelect | ||
} = this.properties; | ||
|
||
const disabled = getOptionDisabled(option, index); |
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.
It looks like you call this function a few different places, and also define it in each of those places. Could you pull this out into a static function somewhere? Or consider using a default property.
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.
Never mind, default properties haven't landed.
src/listbox/example/index.ts
Outdated
import dojoTheme from '../../themes/dojo/theme'; | ||
|
||
interface CustomOption { | ||
value: string; |
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.
Nit, but alphabetize
getOptionLabel: (option: CustomOption) => option.label, | ||
getOptionDisabled: (option: CustomOption) => !!option.disabled, | ||
getOptionSelected: (option: CustomOption) => !!option.selected, | ||
onActiveIndexChange: (index: number) => { |
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 thought the types of the arguments for these callbacks could be inferred now, but I could be insane.
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 get implicit any errors without the type ¯_(ツ)_/¯
@@ -18,382 +17,332 @@ import * as iconCss from '../common/styles/icons.m.css'; | |||
* | |||
* Properties that can be set on a Select component | |||
* | |||
* @property CustomOption Custom widget constructor for options. Should use SelectOption as a base |
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.
Is this no longer supported?
this.invalidate(); | ||
} | ||
|
||
protected onElementCreated(element: HTMLElement, key: string) { | ||
protected onElementUpdated(element: HTMLElement, key: string) { |
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.
Can we remove reliance on onElement*
methods? Technically they're sort of kind of deprecated, though the message was temporarily removed. I'm wondering if we can leverage meta in a way that negates the need for these altogether.
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.
Would the animation meta be able to handle scroll? I suppose we could write our own within Listbox, but I'd rather use something that exists, if we can.
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.
Moved to meta 👍
} | ||
|
||
export const SelectBase = ThemeableMixin(WidgetBase); | ||
|
||
@theme(css) | ||
@theme(iconCss) | ||
@diffProperty('options', reference) |
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.
Was this working incorrectly before explicitly diffing by reference?
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.
Yup, it doesn't call a render if you update the options
property otherwise.
src/listbox/Listbox.ts
Outdated
} | ||
|
||
protected animateScroll(element: HTMLElement, scrollValue: number) { | ||
element.scrollTop = scrollValue; |
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 think the correct way to do this would be to create a local scrollTop
meta within this file that takes a key
and a scrollValue
. That would avoid the need to use onElementCreated
/ onElementUpdated
Codecov Report
@@ Coverage Diff @@
## master #312 +/- ##
=========================================
- Coverage 99.01% 98.8% -0.21%
=========================================
Files 24 22 -2
Lines 1718 1753 +35
Branches 447 469 +22
=========================================
+ Hits 1701 1732 +31
+ Misses 1 0 -1
- Partials 16 21 +5
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #312 +/- ##
=========================================
- Coverage 99.03% 98.84% -0.2%
=========================================
Files 24 23 -1
Lines 1866 1901 +35
Branches 480 488 +8
=========================================
+ Hits 1848 1879 +31
- Partials 18 22 +4
Continue to review full report at Codecov.
|
src/listbox/Listbox.ts
Outdated
id?: string; | ||
multiselect?: boolean; | ||
optionData?: any[]; | ||
ScrollMeta?: WidgetMetaConstructor<DefaultScroll>; |
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.
do you really think this might be passed in as a property? I think it's more likely that we end up moving it out into widget-core/meta
entirely if it's used in multiple places.
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 just want to make sure it's easy to customize, e.g. to animate the scroll or something. Do you think there's anything wrong with passing it in as a property? I suppose just having protected animateScroll
could be enough, since someone could override it.
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.
To me it feels somewhat overkill to provide both an extension point and an injection point. Should we not simply choose one pattern and stick to it?
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.
Works for me. I'm fine removing the prop and leaving protected animateScroll
as the point of extension 👍
const menuDimensions = this.meta(Dimensions).get('root'); | ||
const scrollOffset = menuDimensions.scroll.top; | ||
const menuHeight = menuDimensions.offset.height; | ||
const optionOffset = this.meta(Dimensions).get(this._getOptionId(activeIndex)).offset; |
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.
does _getOptionId
return an ID or a KEY?
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.
It returns a unique string that's used in two places, once for an id
and once for a key
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.
@smhigley this is looking really good now. One question: was logic in onElement*
methods actually moved to meta as commented? I still see the same focus functionality in those deprecated methods using direct DOM access. Other than that, I think we're at a point where merging this and fixing any nits after the fact is a good thing to do. Once the random CI failure about suite error
is fixed :)
Type: feature
The following has been addressed in the PR:
Description:
Refactors Select and ComboBox to use a common Listbox widget. Resolves #117 and #102. This PR also introduces some of the discussed changes to widget patterns, namely splitting the
render()
function into more granular, overridable functions.