From 99a9dab007213a7fdb0bb9b81bbb2c3c84df7b20 Mon Sep 17 00:00:00 2001 From: smhigley Date: Wed, 25 Oct 2017 23:35:25 -0700 Subject: [PATCH 01/14] button updates --- src/button/Button.ts | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/button/Button.ts b/src/button/Button.ts index f53ab40877..ec413c6f63 100644 --- a/src/button/Button.ts +++ b/src/button/Button.ts @@ -70,6 +70,30 @@ export default class Button extends ButtonBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } + protected getContent() { + return this.children; + } + + protected getModifierClasses() { + const { + disabled, + popup = false, + pressed + } = this.properties; + + return [ + disabled ? css.disabled : null, + popup ? css.popup : null, + pressed ? css.pressed : null + ]; + } + + protected renderPopupIcon() { + return v('i', { classes: this.classes(css.addon, iconCss.icon, iconCss.downIcon), + role: 'presentation', 'aria-hidden': 'true' + }); + } + render(): DNode { let { describedBy, @@ -87,12 +111,7 @@ export default class Button extends ButtonBase { } return v('button', { - classes: this.classes( - css.root, - disabled ? css.disabled : null, - popup ? css.popup : null, - pressed ? css.pressed : null - ), + classes: this.classes(css.root, ...this.getModifierClasses()), disabled, id, name, @@ -115,10 +134,8 @@ export default class Button extends ButtonBase { 'aria-pressed': typeof pressed === 'boolean' ? pressed.toString() : null, 'aria-describedby': describedBy }, [ - ...this.children, - popup ? v('i', { classes: this.classes(css.addon, iconCss.icon, iconCss.downIcon), - role: 'presentation', 'aria-hidden': 'true' - }) : null + ...this.getContent(), + popup ? this.renderPopupIcon() : null ]); } } From 17c672702d8ee9d0409be2ce0daa2b823aae1803 Mon Sep 17 00:00:00 2001 From: smhigley Date: Thu, 26 Oct 2017 08:58:58 -0700 Subject: [PATCH 02/14] calendar render udpates --- src/calendar/Calendar.ts | 126 +++++++++++++++----------- src/calendar/CalendarCell.ts | 17 ++-- src/calendar/DatePicker.ts | 102 +++++++++++---------- src/calendar/createCalendarElement.ts | 1 - src/calendar/tests/unit/Calendar.ts | 53 +++++------ 5 files changed, 159 insertions(+), 140 deletions(-) diff --git a/src/calendar/Calendar.ts b/src/calendar/Calendar.ts index e7f0435022..2fd3f7f389 100644 --- a/src/calendar/Calendar.ts +++ b/src/calendar/Calendar.ts @@ -1,7 +1,7 @@ import { WidgetBase } from '@dojo/widget-core/WidgetBase'; import { ThemeableMixin, ThemeableProperties, theme } from '@dojo/widget-core/mixins/Themeable'; import { v, w } from '@dojo/widget-core/d'; -import { DNode, Constructor } from '@dojo/widget-core/interfaces'; +import { DNode } from '@dojo/widget-core/interfaces'; import uuid from '@dojo/core/uuid'; import { Keys } from '../common/util'; import { CalendarMessages } from './DatePicker'; @@ -16,7 +16,6 @@ import * as iconCss from '../common/styles/icons.m.css'; * * Properties that can be set on a Calendar component * - * @property CustomDateCell Custom widget constructor for the date cell. Should use CalendarCell as a base. * @property labels Customize or internationalize accessible text for the Calendar widget * @property month Set the currently displayed month, 0-based * @property monthNames Customize or internationalize full month names and abbreviations @@ -30,7 +29,6 @@ import * as iconCss from '../common/styles/icons.m.css'; * @property onDateSelect Function called when the user selects a date */ export interface CalendarProperties extends ThemeableProperties { - CustomDateCell?: Constructor; labels?: CalendarMessages; month?: number; monthNames?: { short: string; long: string; }[]; @@ -234,7 +232,6 @@ export default class Calendar extends CalendarBase { month, year } = this._getMonthYear(); - const { theme = {}, CustomDateCell = CalendarCell } = this.properties; const currentMonthLength = this._getMonthLength(month, year); const previousMonthLength = this._getMonthLength(month - 1, year); @@ -279,19 +276,9 @@ export default class Calendar extends CalendarBase { isSelectedDay = false; } - days.push(w(CustomDateCell, { - key: `date-${week * 7 + i}`, - callFocus: this._callDateFocus && isCurrentMonth && date === this._focusedDay, - date, - disabled: !isCurrentMonth, - focusable: isCurrentMonth && date === this._focusedDay, - selected: isSelectedDay, - theme, - today: isCurrentMonth && dateString === todayString, - onClick: this._onDateClick, - onFocusCalled: this._onDateFocusCalled, - onKeyDown: this._onDateKeyDown - })); + const isToday = isCurrentMonth && dateString === todayString; + + days.push(this.renderDateCell(date, isSelectedDay, isCurrentMonth, isToday)); } weeks.push(v('tr', days)); @@ -300,19 +287,31 @@ export default class Calendar extends CalendarBase { return weeks; } - private _renderWeekdayCell(day: { short: string; long: string; }): DNode { - const { renderWeekdayCell } = this.properties; - return renderWeekdayCell ? renderWeekdayCell(day) : v('abbr', { title: day.long }, [ day.short ]); + protected renderDateCell(date: number, selected: boolean, currentMonth: boolean, today: boolean): DNode { + const key = currentMonth ? `date-${date}` : `date-${date}-dimmed`; + const { theme = {} } = this.properties; + + return w(CalendarCell, { + key, + callFocus: this._callDateFocus && currentMonth && date === this._focusedDay, + date, + disabled: !currentMonth, + focusable: currentMonth && date === this._focusedDay, + selected, + theme, + today, + onClick: this._onDateClick, + onFocusCalled: this._onDateFocusCalled, + onKeyDown: this._onDateKeyDown + }); } - protected render(): DNode { + protected renderDatePicker() { const { labels = DEFAULT_LABELS, monthNames = DEFAULT_MONTHS, renderMonthLabel, - selectedDate, theme = {}, - weekdayNames = DEFAULT_WEEKDAYS, onMonthChange, onYearChange } = this.properties; @@ -321,6 +320,51 @@ export default class Calendar extends CalendarBase { year } = this._getMonthYear(); + return w(DatePicker, { + key: 'date-picker', + labelId: this._monthLabelId, + labels, + month, + monthNames, + renderMonthLabel, + theme, + year, + onPopupChange: (open: boolean) => { + this._popupOpen = open; + }, + onRequestMonthChange: (requestMonth: number) => { + onMonthChange && onMonthChange(requestMonth); + }, + onRequestYearChange: (requestYear: number) => { + onYearChange && onYearChange(requestYear); + } + }); + } + + protected renderPagingButtonContent(type: 'next' | 'previous') { + const { labels = DEFAULT_LABELS } = this.properties; + const iconClass = type === 'next' ? iconCss.rightIcon : iconCss.leftIcon; + const labelText = type === 'next' ? labels.nextMonth : labels.previousMonth; + + return [ + v('i', { classes: this.classes(iconCss.icon, iconClass), + role: 'presentation', 'aria-hidden': 'true' + }), + v('span', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labelText ]) + ]; + } + + protected renderWeekdayCell(day: { short: string; long: string; }): DNode { + const { renderWeekdayCell } = this.properties; + return renderWeekdayCell ? renderWeekdayCell(day) : v('abbr', { title: day.long }, [ day.short ]); + } + + protected render(): DNode { + const { + selectedDate, + weekdayNames = DEFAULT_WEEKDAYS + } = this.properties; + // Calendar Weekday array const weekdays = []; for (const weekday in weekdayNames) { @@ -328,31 +372,13 @@ export default class Calendar extends CalendarBase { role: 'columnheader', classes: this.classes(css.weekday) }, [ - this._renderWeekdayCell(weekdayNames[weekday]) + this.renderWeekdayCell(weekdayNames[weekday]) ])); } return v('div', { classes: this.classes(css.root) }, [ // header - w(DatePicker, { - key: 'date-picker', - labelId: this._monthLabelId, - labels, - month, - monthNames, - renderMonthLabel, - theme, - year, - onPopupChange: (open: boolean) => { - this._popupOpen = open; - }, - onRequestMonthChange: (requestMonth: number) => { - onMonthChange && onMonthChange(requestMonth); - }, - onRequestYearChange: (requestYear: number) => { - onYearChange && onYearChange(requestYear); - } - }), + this.renderDatePicker(), // date table v('table', { cellspacing: '0', @@ -374,22 +400,12 @@ export default class Calendar extends CalendarBase { classes: this.classes(css.previous), tabIndex: this._popupOpen ? -1 : 0, onclick: this._onMonthPageDown - }, [ - v('i', { classes: this.classes(iconCss.icon, iconCss.leftIcon), - role: 'presentation', 'aria-hidden': 'true' - }), - v('span', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labels.previousMonth ]) - ]), + }, this.renderPagingButtonContent('previous')), v('button', { classes: this.classes(css.next), tabIndex: this._popupOpen ? -1 : 0, onclick: this._onMonthPageUp - }, [ - v('i', { classes: this.classes(iconCss.icon, iconCss.rightIcon), - role: 'presentation', 'aria-hidden': 'true' - }), - v('span', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labels.nextMonth ]) - ]) + }, this.renderPagingButtonContent('next')) ]) ]); } diff --git a/src/calendar/CalendarCell.ts b/src/calendar/CalendarCell.ts index 8d65dcf3e3..69c7758e69 100644 --- a/src/calendar/CalendarCell.ts +++ b/src/calendar/CalendarCell.ts @@ -65,28 +65,33 @@ export default class CalendarCell extends CalendarCellBase { onPopupChange && onPopupChange(this._getPopupState()); } - private _renderMonthRadios() { + protected renderControlsTrigger(type: 'month' | 'year'): DNode { + const { + month, + monthNames, + year + } = this.properties; + + const content = type === 'month' ? monthNames[month].long : `${year}`; + const open = type === 'month' ? this._monthPopupOpen : this._yearPopupOpen; + const onclick = type === 'month' ? this._onMonthButtonClick : this._onYearButtonClick; + + return v('button', { + key: `${type}-button`, + 'aria-controls': `${this._idBase}_${type}_dialog`, + 'aria-expanded': `${open}`, + 'aria-haspopup': 'true', + id: `${this._idBase}_${type}_button`, + classes: this.classes( + (css as any)[`${type}Trigger`], + open ? (css as any)[`${type}TriggerActive`] : null + ), + role: 'menuitem', + onclick + }, [ content ]); + } + + protected renderMonthLabel(month: number, year: number) { + const { monthNames, renderMonthLabel } = this.properties; + return renderMonthLabel ? renderMonthLabel(month, year) : `${monthNames[month].long} ${year}`; + } + + protected renderMonthRadios() { const { month } = this.properties; return this.properties.monthNames.map((monthName, i) => v('label', { @@ -215,7 +246,20 @@ export default class DatePicker extends DatePickerBase { ])); } - private _renderYearRadios() { + protected renderPagingButtonContent(type: 'next' | 'previous') { + const { labels } = this.properties; + const iconClass = type === 'next' ? iconCss.rightIcon : iconCss.leftIcon; + const labelText = type === 'next' ? labels.nextMonth : labels.previousMonth; + + return [ + v('i', { classes: this.classes(iconCss.icon, iconClass), + role: 'presentation', 'aria-hidden': 'true' + }), + v('span', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labelText ]) + ]; + } + + protected renderYearRadios() { const { year } = this.properties; const radios = []; @@ -244,17 +288,11 @@ export default class DatePicker extends DatePickerBase { return radios; } - private _renderMonthLabel(month: number, year: number) { - const { monthNames, renderMonthLabel } = this.properties; - return renderMonthLabel ? renderMonthLabel(month, year) : `${monthNames[month].long} ${year}`; - } - protected render(): DNode { const { labelId = `${this._idBase}_label`, labels, month, - monthNames, year } = this.properties; @@ -271,37 +309,13 @@ export default class DatePicker extends DatePickerBase { classes: this.classes().fixed(baseCss.visuallyHidden), 'aria-live': 'polite', 'aria-atomic': 'false' - }, [ this._renderMonthLabel(month, year) ]), + }, [ this.renderMonthLabel(month, year) ]), // month trigger - v('button', { - key: 'month-button', - 'aria-controls': `${this._idBase}_month_dialog`, - 'aria-expanded': `${this._monthPopupOpen}`, - 'aria-haspopup': 'true', - id: `${this._idBase}_month_button`, - classes: this.classes( - css.monthTrigger, - this._monthPopupOpen ? css.monthTriggerActive : null - ), - role: 'menuitem', - onclick: this._onMonthButtonClick - }, [ monthNames[month].long ]), + this.renderControlsTrigger('month'), // year trigger - v('button', { - key: 'year-button', - 'aria-controls': `${this._idBase}_year_dialog`, - 'aria-expanded': `${this._yearPopupOpen}`, - 'aria-haspopup': 'true', - id: `${this._idBase}_year_button`, - classes: this.classes( - css.yearTrigger, - this._yearPopupOpen ? css.yearTriggerActive : null - ), - role: 'menuitem', - onclick: this._onYearButtonClick - }, [ `${ year }` ]) + this.renderControlsTrigger('year') ]), // month grid @@ -318,7 +332,7 @@ export default class DatePicker extends DatePickerBase { onkeydown: this._onPopupKeyDown }, [ v('legend', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labels.chooseMonth ]), - ...this._renderMonthRadios() + ...this.renderMonthRadios() ]) ]), @@ -336,7 +350,7 @@ export default class DatePicker extends DatePickerBase { onkeydown: this._onPopupKeyDown }, [ v('legend', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labels.chooseYear ]), - ...this._renderYearRadios() + ...this.renderYearRadios() ]), v('div', { classes: this.classes(css.controls) @@ -345,22 +359,12 @@ export default class DatePicker extends DatePickerBase { classes: this.classes(css.previous), tabIndex: this._yearPopupOpen ? 0 : -1, onclick: this._onYearPageDown - }, [ - v('i', { classes: this.classes(iconCss.icon, iconCss.leftIcon), - role: 'presentation', 'aria-hidden': 'true' - }), - v('span', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labels.previousMonth ]) - ]), + }, this.renderPagingButtonContent('previous')), v('button', { classes: this.classes(css.next), tabIndex: this._yearPopupOpen ? 0 : -1, onclick: this._onYearPageUp - }, [ - v('i', { classes: this.classes(iconCss.icon, iconCss.rightIcon), - role: 'presentation', 'aria-hidden': 'true' - }), - v('span', { classes: this.classes().fixed(baseCss.visuallyHidden) }, [ labels.nextMonth ]) - ]) + }, this.renderPagingButtonContent('next')) ]) ]) ]); diff --git a/src/calendar/createCalendarElement.ts b/src/calendar/createCalendarElement.ts index 48feb86390..b438cc446e 100644 --- a/src/calendar/createCalendarElement.ts +++ b/src/calendar/createCalendarElement.ts @@ -24,7 +24,6 @@ export default function createCalendarElement(): CustomElementDescriptor { } ], properties: [ - { propertyName: 'CustomDateCell' }, { propertyName: 'labels' }, { propertyName: 'monthNames' }, { propertyName: 'weekdayNames' }, diff --git a/src/calendar/tests/unit/Calendar.ts b/src/calendar/tests/unit/Calendar.ts index 5ba7ac4cfb..44e802e8f9 100644 --- a/src/calendar/tests/unit/Calendar.ts +++ b/src/calendar/tests/unit/Calendar.ts @@ -20,11 +20,9 @@ const compareId = compareProperty((value: any) => { return typeof value === 'string'; }); -let dateIndex = -1; const expectedDateCell = function(widget: any, date: number, active: boolean) { - dateIndex++; return w(CalendarCell, { - key: `date-${dateIndex}`, + key: `date-${date}${active ? '' : '-dimmed'}`, callFocus: false, date, disabled: !active, @@ -38,7 +36,6 @@ const expectedDateCell = function(widget: any, date: number, active: boolean) { }; const expected = function(widget: any, popupOpen = false) { - dateIndex = -1; return v('div', { classes: widget.classes(css.root) }, [ w(DatePicker, { key: 'date-picker', @@ -177,7 +174,7 @@ registerSuite('Calendar', { }); const expectedVdom = expected(widget); - assignProperties(findKey(expectedVdom, 'date-6')!, { + assignProperties(findKey(expectedVdom, 'date-3')!, { selected: true }); @@ -186,7 +183,6 @@ registerSuite('Calendar', { 'Renders with custom properties'() { widget.setProperties({ - CustomDateCell: CalendarCell, labels: DEFAULT_LABELS, month: testDate.getMonth(), monthNames: DEFAULT_MONTHS, @@ -202,7 +198,7 @@ registerSuite('Calendar', { for (let i = 0; i < 7; i++) { replaceChild(expectedVdom, `1,0,0,${i},0`, 'Bar'); } - assignProperties(findKey(expectedVdom, 'date-4')!, { + assignProperties(findKey(expectedVdom, 'date-1')!, { selected: true }); assignChildProperties(expectedVdom, '0', { @@ -211,7 +207,6 @@ registerSuite('Calendar', { widget.expectRender(expectedVdom); - class CustomCalendarCell extends CalendarCell {} widget.setProperties({ month: testDate.getMonth(), year: testDate.getFullYear() @@ -232,7 +227,7 @@ registerSuite('Calendar', { widget.callListener('onClick', { args: [1, false], - key: 'date-4' + key: 'date-1' }); assert.strictEqual(selectedDate.getDate(), 1, 'Clicking cell selects correct date'); @@ -256,7 +251,7 @@ registerSuite('Calendar', { widget.callListener('onClick', { args: [1, true], - key: 'date-34' + key: 'date-1-dimmed' }); assert.strictEqual(currentMonth, 6, 'Month changes to July'); assert.strictEqual(selectedDate.getMonth(), 6, 'selected date in July'); @@ -264,7 +259,7 @@ registerSuite('Calendar', { widget.callListener('onClick', { args: [30, true], - key: 'date-2' + key: 'date-30-dimmed' }); assert.strictEqual(currentMonth, 4, 'Month changes to May'); assert.strictEqual(selectedDate.getMonth(), 4, 'selected date in May'); @@ -287,18 +282,18 @@ registerSuite('Calendar', { which: Keys.Right, preventDefault: () => {} }], - key: 'date-4' + key: 'date-1' }); // not a good way to test this, but this would be called with the arrow key widget.callListener('onFocusCalled', { - key: 'date-4' + key: 'date-1' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Enter, preventDefault: () => {} }], - key: 'date-5' + key: 'date-2' }); assert.strictEqual(selectedDate.getDate(), 2, 'Right arrow + enter selects second day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -309,14 +304,14 @@ registerSuite('Calendar', { which: Keys.Down, preventDefault: () => {} }], - key: 'date-5' + key: 'date-2' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Enter, preventDefault: () => {} }], - key: 'date-12' + key: 'date-9' }); assert.strictEqual(selectedDate.getDate(), 9, 'Down arrow + enter selects one week down'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -327,14 +322,14 @@ registerSuite('Calendar', { which: Keys.Left, preventDefault: () => {} }], - key: 'date-12' + key: 'date-9' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-11' + key: 'date-8' }); assert.strictEqual(selectedDate.getDate(), 8, 'Left arrow + space selects previous day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -345,14 +340,14 @@ registerSuite('Calendar', { which: Keys.Up, preventDefault: () => {} }], - key: 'date-11' + key: 'date-8' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-4' + key: 'date-1' }); assert.strictEqual(selectedDate.getDate(), 1, 'Left arrow + space selects previous day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -363,14 +358,14 @@ registerSuite('Calendar', { which: Keys.PageDown, preventDefault: () => {} }], - key: 'date-4' + key: 'date-1' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-33' + key: 'date-30' }); assert.strictEqual(selectedDate.getDate(), 30, 'Page Down + space selects last day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -381,14 +376,14 @@ registerSuite('Calendar', { which: Keys.PageUp, preventDefault: () => {} }], - key: 'date-33' + key: 'date-30' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-4' + key: 'date-1' }); assert.strictEqual(selectedDate.getDate(), 1, 'Page Up + space selects first day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -409,7 +404,7 @@ registerSuite('Calendar', { which: Keys.Left, preventDefault: () => {} }], - key: 'date-4' + key: 'date-1' }); assert.strictEqual(currentMonth, testDate.getMonth() - 1, 'Going left from the first day goes to previous month'); @@ -418,14 +413,14 @@ registerSuite('Calendar', { which: Keys.PageDown, preventDefault: () => {} }], - key: 'date-4' + key: 'date-1' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Right, preventDefault: () => {} }], - key: 'date-4' + key: 'date-1' }); assert.strictEqual(currentMonth, testDate.getMonth() + 1, 'Going right from the last day goes to next month'); }, @@ -449,7 +444,7 @@ registerSuite('Calendar', { which: Keys.Up, preventDefault: () => {} }], - key: 'date-0' + key: 'date-1' }); assert.strictEqual(currentMonth, 11, 'Previous month wraps from January to December'); assert.strictEqual(currentYear, 2016, 'Year decrements when month wraps'); @@ -470,7 +465,7 @@ registerSuite('Calendar', { which: Keys.Down, preventDefault: () => {} }], - key: 'date-35' + key: 'date-30' }); assert.strictEqual(currentMonth, 0, 'Next month wraps from December to January'); assert.strictEqual(currentYear, 2018, 'Year increments when month wraps'); From 8dc9ce625639d1173b8edcc3920525ad59cada0d Mon Sep 17 00:00:00 2001 From: smhigley Date: Thu, 26 Oct 2017 09:07:08 -0700 Subject: [PATCH 03/14] radio and checkbox render --- src/checkbox/Checkbox.ts | 38 ++++++++++++++++++++++++-------------- src/radio/Radio.ts | 28 +++++++++++++++++++--------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/checkbox/Checkbox.ts b/src/checkbox/Checkbox.ts index 75c7eb421e..799321bc78 100644 --- a/src/checkbox/Checkbox.ts +++ b/src/checkbox/Checkbox.ts @@ -87,6 +87,28 @@ export default class Checkbox extends CheckboxBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } + protected getModifierClasses() { + const { + checked = false, + disabled, + invalid, + mode, + readOnly, + required + } = this.properties; + + return [ + mode === Mode.toggle ? css.toggle : null, + checked ? css.checked : null, + disabled ? css.disabled : null, + this._focused ? css.focused : null, + invalid ? css.invalid : null, + invalid === false ? css.valid : null, + readOnly ? css.readonly : null, + required ? css.required : null + ]; + } + protected renderToggle(): DNode[] { const { checked, @@ -117,24 +139,12 @@ export default class Checkbox extends CheckboxBase { disabled, invalid, label, - mode, name, readOnly, required, value } = this.properties; - const stateClasses = [ - mode === Mode.toggle ? css.toggle : null, - checked ? css.checked : null, - disabled ? css.disabled : null, - this._focused ? css.focused : null, - invalid ? css.invalid : null, - invalid === false ? css.valid : null, - readOnly ? css.readonly : null, - required ? css.required : null - ]; - const children = [ v('div', { classes: this.classes(css.inputWrapper) }, [ ...this.renderToggle(), @@ -167,14 +177,14 @@ export default class Checkbox extends CheckboxBase { if (label) { checkboxWidget = w(Label, { - extraClasses: { root: parseLabelClasses(this.classes(css.root, ...stateClasses).get()) }, + extraClasses: { root: parseLabelClasses(this.classes(css.root, ...this.getModifierClasses()).get()) }, label, theme: this.properties.theme }, children); } else { checkboxWidget = v('div', { - classes: this.classes(css.root, ...stateClasses) + classes: this.classes(css.root, ...this.getModifierClasses()) }, children); } diff --git a/src/radio/Radio.ts b/src/radio/Radio.ts index 6c87ea828f..84a9c00a82 100644 --- a/src/radio/Radio.ts +++ b/src/radio/Radio.ts @@ -74,20 +74,16 @@ export default class Radio extends RadioBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - render(): DNode { + protected getModifierClasses() { const { checked = false, - describedBy, disabled, invalid, - label, - name, readOnly, - required, - value + required } = this.properties; - const stateClasses = [ + return [ checked ? css.checked : null, disabled ? css.disabled : null, this._focused ? css.focused : null, @@ -96,6 +92,20 @@ export default class Radio extends RadioBase { readOnly ? css.readonly : null, required ? css.required : null ]; + } + + render(): DNode { + const { + checked = false, + describedBy, + disabled, + invalid, + label, + name, + readOnly, + required, + value + } = this.properties; const radio = v('div', { classes: this.classes(css.inputWrapper) }, [ v('input', { @@ -126,14 +136,14 @@ export default class Radio extends RadioBase { if (label) { radioWidget = w(Label, { - extraClasses: { root: parseLabelClasses(this.classes(css.root, ...stateClasses).get()) }, + extraClasses: { root: parseLabelClasses(this.classes(css.root, ...this.getModifierClasses()).get()) }, label, theme: this.properties.theme }, [ radio ]); } else { radioWidget = v('div', { - classes: this.classes(css.root, ...stateClasses) + classes: this.classes(css.root, ...this.getModifierClasses()) }, [ radio]); } From a7262af3f451d694d3c64cfaedfe0b8215fc9772 Mon Sep 17 00:00:00 2001 From: smhigley Date: Thu, 26 Oct 2017 09:30:28 -0700 Subject: [PATCH 04/14] dialog update --- src/dialog/Dialog.ts | 52 +++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/dialog/Dialog.ts b/src/dialog/Dialog.ts index 881c803b33..8695a21741 100644 --- a/src/dialog/Dialog.ts +++ b/src/dialog/Dialog.ts @@ -83,6 +83,35 @@ export default class Dialog extends DialogBase { })); } + protected getContent() { + return v('div', { + classes: this.classes(css.content), + key: 'content' + }, this.children); + } + + protected renderCloseIcon() { + return v('i', { classes: this.classes(iconCss.icon, iconCss.closeIcon), + role: 'presentation', 'aria-hidden': 'true' + }); + } + + protected renderTitle() { + const { title = '' } = this.properties; + return v('div', { id: this._titleId }, [ title ]); + } + + protected renderUnderlay() { + const { underlay } = this.properties; + return v('div', { + classes: this.classes(underlay ? css.underlayVisible : null).fixed(css.underlay), + enterAnimation: animations.fadeIn, + exitAnimation: animations.fadeOut, + key: 'underlay', + onclick: this._onUnderlayClick + }); + } + render(): DNode { const { closeable = true, @@ -91,9 +120,7 @@ export default class Dialog extends DialogBase { exitAnimation = animations.fadeOut, onOpen, open = false, - role = 'dialog', - title = '', - underlay + role = 'dialog' } = this.properties; open && !this._wasOpen && onOpen && onOpen(); @@ -103,13 +130,7 @@ export default class Dialog extends DialogBase { return v('div', { classes: this.classes(css.root) }, open ? [ - v('div', { - classes: this.classes(underlay ? css.underlayVisible : null).fixed(css.underlay), - enterAnimation: animations.fadeIn, - exitAnimation: animations.fadeOut, - key: 'underlay', - onclick: this._onUnderlayClick - }), + this.renderUnderlay(), v('div', { 'aria-labelledby': this._titleId, classes: this.classes(css.main), @@ -122,21 +143,16 @@ export default class Dialog extends DialogBase { classes: this.classes(css.title), key: 'title' }, [ - v('div', { id: this._titleId }, [ title ]), + this.renderTitle(), closeable ? v('button', { classes: this.classes(css.close), onclick: this._onCloseClick }, [ closeText, - v('i', { classes: this.classes(iconCss.icon, iconCss.closeIcon), - role: 'presentation', 'aria-hidden': 'true' - }) + this.renderCloseIcon() ]) : null ]), - v('div', { - classes: this.classes(css.content), - key: 'content' - }, this.children) + this.getContent() ]) ] : []); } From d993eaf885715b15782a8b1ea739f1b1b294930f Mon Sep 17 00:00:00 2001 From: smhigley Date: Fri, 27 Oct 2017 02:25:41 -0700 Subject: [PATCH 05/14] slidepane --- src/slidepane/SlidePane.ts | 120 ++++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 41 deletions(-) diff --git a/src/slidepane/SlidePane.ts b/src/slidepane/SlidePane.ts index c7d7a8791b..eecd1cd844 100644 --- a/src/slidepane/SlidePane.ts +++ b/src/slidepane/SlidePane.ts @@ -172,51 +172,96 @@ export default class SlidePane extends SlidePaneBase { } } - render(): DNode { + protected getContent() { + return v('div', { classes: this.classes(css.content) }, this.children); + } + + protected getStyles() { const { align = Align.left, - closeText = 'close pane', - onOpen, open = false, - title = '', - underlay = false, width = DEFAULT_WIDTH } = this.properties; + let translate = ''; + const translateAxis = this.plane === Plane.x ? 'X' : 'Y'; + + // If pane is closing because of swipe + if (!open && this._wasOpen && this._transform) { + translate = align === Align.left || align === Align.top ? `-${this._transform}` : `${this._transform}`; + } + + return { + transform: translate ? `translate${translateAxis}(${translate}%)` : '', + width: this.plane === Plane.x ? `${ width }px` : null, + height: this.plane === Plane.y ? `${ width }px` : null + }; + } + + protected getFixedModifierClasses() { + const { + align = Align.left, + open = false + } = this.properties; const alignCss: {[key: string]: any} = css; - const contentClasses = [ - css.pane, + return [ + open ? css.openFixed : null, + alignCss[`${align}Fixed`], + this._slideIn || (open && !this._wasOpen) ? css.slideInFixed : null, + !open && this._wasOpen ? css.slideOutFixed : null + ]; + } + + protected getModifierClasses() { + const { + align = Align.left, + open = false + } = this.properties; + const alignCss: {[key: string]: any} = css; + + return [ alignCss[align], open ? css.open : null, this._slideIn || (open && !this._wasOpen) ? css.slideIn : null, !open && this._wasOpen ? css.slideOut : null ]; + } - const fixedContentClasses = [ - css.paneFixed, - open ? css.openFixed : null, - alignCss[`${align}Fixed`], - this._slideIn || (open && !this._wasOpen) ? css.slideInFixed : null, - !open && this._wasOpen ? css.slideOutFixed : null - ]; + protected renderCloseIcon() { + return v('i', { classes: this.classes(iconCss.icon, iconCss.closeIcon), + role: 'presentation', 'aria-hidden': 'true' + }); + } - const contentStyles: {[key: string]: any} = { - transform: '', - width: this.plane === Plane.x ? `${ width }px` : null, - height: this.plane === Plane.y ? `${ width }px` : null - }; + protected renderTitle() { + const { title = '' } = this.properties; + return v('div', { id: this._titleId }, [ title ]); + } - if (!open && this._wasOpen && this._transform) { - // If pane is closing because of swipe - if (this.plane === Plane.x) { - contentStyles['transform'] = `translateX(${ align === Align.left ? '-' : '' }${ this._transform }%)`; - } - else { - contentStyles['transform'] = `translateY(${ align === Align.top ? '-' : '' }${ this._transform }%)`; - } - } - else if (this._slideIn && this._content) { + protected renderUnderlay() { + const { underlay = false } = this.properties; + return v('div', { + classes: this.classes(underlay ? css.underlayVisible : null).fixed(css.underlay), + enterAnimation: animations.fadeIn, + exitAnimation: animations.fadeOut, + key: 'underlay' + }); + } + + render(): DNode { + const { + closeText = 'close pane', + onOpen, + open = false, + title = '' + } = this.properties; + + const contentStyles = this.getStyles(); + const contentClasses = this.getModifierClasses(); + const fixedContentClasses = this.getFixedModifierClasses(); + + if (this._slideIn && this._content) { this._content.style.transform = ''; } @@ -234,33 +279,26 @@ export default class SlidePane extends SlidePaneBase { ontouchmove: this._onSwipeMove, ontouchstart: this._onSwipeStart }, [ - open ? v('div', { - classes: this.classes(underlay ? css.underlayVisible : null).fixed(css.underlay), - enterAnimation: animations.fadeIn, - exitAnimation: animations.fadeOut, - key: 'underlay' - }) : null, + open ? this.renderUnderlay() : null, v('div', { key: 'content', - classes: this.classes(...contentClasses).fixed(...fixedContentClasses), + classes: this.classes(css.pane, ...contentClasses).fixed(css.paneFixed, ...fixedContentClasses), styles: contentStyles }, [ title ? v('div', { classes: this.classes(css.title), key: 'title' }, [ - v('div', { id: this._titleId }, [ title ]), + this.renderTitle(), v('button', { classes: this.classes(css.close), onclick: this._onCloseClick }, [ closeText, - v('i', { classes: this.classes(iconCss.icon, iconCss.closeIcon), - role: 'presentation', 'aria-hidden': 'true' - }) + this.renderCloseIcon() ]) ]) : null, - v('div', { classes: this.classes(css.content) }, this.children) + this.getContent() ]) ]); } From 37868af417ac14826c97a04c7bcf7b5c9884acc0 Mon Sep 17 00:00:00 2001 From: smhigley Date: Fri, 27 Oct 2017 02:37:43 -0700 Subject: [PATCH 06/14] slider --- src/slider/Slider.ts | 108 +++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 41 deletions(-) diff --git a/src/slider/Slider.ts b/src/slider/Slider.ts index bf1d58b45a..273a6d2ed9 100644 --- a/src/slider/Slider.ts +++ b/src/slider/Slider.ts @@ -91,31 +91,16 @@ export default class Slider extends SliderBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - render(): DNode { + protected getModifierClasses() { const { - describedBy, disabled, invalid, - label, - max = 100, - min = 0, - name, - output, - outputIsTooltip = false, readOnly, required, - step = 1, - vertical = false, - verticalHeight = '200px' + vertical = false } = this.properties; - let { - value = min - } = this.properties; - - value = value > max ? max : value; - value = value < min ? min : value; - const stateClasses = [ + return [ disabled ? css.disabled : null, invalid ? css.invalid : null, invalid === false ? css.valid : null, @@ -123,10 +108,37 @@ export default class Slider extends SliderBase { required ? css.required : null, vertical ? css.vertical : null ]; + } - const percentValue = (value - min) / (max - min) * 100; + protected renderControls(percentValue: number) { + const { + vertical = false, + verticalHeight = '200px' + } = this.properties; + + return v('div', { + classes: this.classes(css.track).fixed(css.trackFixed), + 'aria-hidden': 'true', + styles: vertical ? { width: verticalHeight } : {} + }, [ + v('span', { + classes: this.classes(css.fill).fixed(css.fillFixed), + styles: { width: `${percentValue}%` } + }), + v('span', { + classes: this.classes(css.thumb).fixed(css.thumbFixed), + styles: { left: `${percentValue}%` } + }) + ]); + } + + protected renderOutput(value: number, percentValue: number) { + const { + output, + outputIsTooltip = false, + vertical = false + } = this.properties; - // custom output node const outputNode = output ? output(value) : `${value}`; // output styles @@ -135,6 +147,37 @@ export default class Slider extends SliderBase { outputStyles = vertical ? { top: `${100 - percentValue}%` } : { left: `${percentValue}%` }; } + return v('output', { + classes: this.classes(css.output, outputIsTooltip ? css.outputTooltip : null), + for: `${this._inputId}`, + styles: outputStyles + }, [ outputNode ]); + } + + render(): DNode { + const { + describedBy, + disabled, + invalid, + label, + max = 100, + min = 0, + name, + readOnly, + required, + step = 1, + vertical = false, + verticalHeight = '200px' + } = this.properties; + let { + value = min + } = this.properties; + + value = value > max ? max : value; + value = value < min ? min : value; + + const percentValue = (value - min) / (max - min) * 100; + const slider = v('div', { classes: this.classes(css.inputWrapper).fixed(css.inputWrapperFixed), styles: vertical ? { height: verticalHeight } : {} @@ -169,39 +212,22 @@ export default class Slider extends SliderBase { ontouchend: this._onTouchEnd, ontouchcancel: this._onTouchCancel }), - v('div', { - classes: this.classes(css.track).fixed(css.trackFixed), - 'aria-hidden': 'true', - styles: vertical ? { width: verticalHeight } : {} - }, [ - v('span', { - classes: this.classes(css.fill).fixed(css.fillFixed), - styles: { width: `${percentValue}%` } - }), - v('span', { - classes: this.classes(css.thumb).fixed(css.thumbFixed), - styles: { left: `${percentValue}%` } - }) - ]), - v('output', { - classes: this.classes(css.output, outputIsTooltip ? css.outputTooltip : null), - for: `${this._inputId}`, - styles: outputStyles - }, [ outputNode ]) + this.renderControls(percentValue), + this.renderOutput(value, percentValue) ]); let sliderWidget; if (label) { sliderWidget = w(Label, { - extraClasses: { root: parseLabelClasses(this.classes(css.root, ...stateClasses).fixed(css.rootFixed)()) }, + extraClasses: { root: parseLabelClasses(this.classes(css.root, ...this.getModifierClasses()).fixed(css.rootFixed)()) }, label, theme: this.properties.theme }, [ slider ]); } else { sliderWidget = v('div', { - classes: this.classes(css.root, ...stateClasses).fixed(css.rootFixed) + classes: this.classes(css.root, ...this.getModifierClasses()).fixed(css.rootFixed) }, [ slider ]); } From 2bb986f62fa6be4d52aef495c708daf1b10ba7af Mon Sep 17 00:00:00 2001 From: smhigley Date: Fri, 27 Oct 2017 09:29:27 -0700 Subject: [PATCH 07/14] tabs --- src/tabcontroller/TabButton.ts | 40 ++++++----- src/tabcontroller/TabController.ts | 88 ++++++++++++----------- src/tabcontroller/tests/unit/TabButton.ts | 8 +-- 3 files changed, 73 insertions(+), 63 deletions(-) diff --git a/src/tabcontroller/TabButton.ts b/src/tabcontroller/TabButton.ts index 56e4061b19..be2e4c7d8f 100644 --- a/src/tabcontroller/TabButton.ts +++ b/src/tabcontroller/TabButton.ts @@ -130,6 +130,28 @@ export default class TabButton extends TabButtonBase { } } + protected getContent() { + const { active, closeable } = this.properties; + + return [ + ...this.children, + closeable ? v('button', { + tabIndex: active ? 0 : -1, + classes: this.classes(css.close), + innerHTML: 'close tab', + onclick: this._onCloseClick + }) : null + ]; + } + + protected getModifierClasses() { + const { active, disabled } = this.properties; + return [ + active ? css.activeTabButton : null, + disabled ? css.disabledTabButton : null + ]; + } + protected onElementCreated(element: HTMLElement, key: string) { key === 'tab-button' && this._callFocus(element); } @@ -141,36 +163,22 @@ export default class TabButton extends TabButtonBase { render(): DNode { const { active, - closeable, controls, disabled, id } = this.properties; - const children = closeable ? this.children.concat([ - v('button', { - tabIndex: active ? 0 : -1, - classes: this.classes(css.close), - innerHTML: 'close tab', - onclick: this._onCloseClick - }) - ]) : this.children; - return v('div', { 'aria-controls': controls, 'aria-disabled': disabled ? 'true' : 'false', 'aria-selected': active ? 'true' : 'false', - classes: this.classes( - css.tabButton, - active ? css.activeTabButton : null, - disabled ? css.disabledTabButton : null - ), + classes: this.classes(css.tabButton, ...this.getModifierClasses()), id, key: 'tab-button', onclick: this._onClick, onkeydown: this._onKeyDown, role: 'tab', tabIndex: active ? 0 : -1 - }, children); + }, this.getContent()); } } diff --git a/src/tabcontroller/TabController.ts b/src/tabcontroller/TabController.ts index 73b443b49b..9da9c6d5c8 100644 --- a/src/tabcontroller/TabController.ts +++ b/src/tabcontroller/TabController.ts @@ -71,7 +71,46 @@ export default class TabController extends TabControllerBase Boolean(result.properties.disabled))) { + return null; + } + + function nextIndex(index: number) { + if (backwards) { + return (tabs.length + (index - 1)) % tabs.length; + } + return (index + 1) % tabs.length; + } + + let i = !tabs[currentIndex] ? tabs.length - 1 : currentIndex; + + while (tabs[i].properties.disabled) { + i = nextIndex(i); + } + + return i; + } + + protected closeIndex(index: number) { + const { onRequestTabClose } = this.properties; + const key = this._tabs[index].properties.key; + this._callTabFocus = true; + + onRequestTabClose && onRequestTabClose(index, key); + } + + protected renderButtonContent(label?: DNode) { + return [ label || null ]; + } + + protected renderTabButtons() { return this._tabs.map((tab, i) => { const { closeable, @@ -100,13 +139,11 @@ export default class TabController extends TabControllerBase Boolean(result.properties.disabled))) { - return null; - } - - function nextIndex(index: number) { - if (backwards) { - return (tabs.length + (index - 1)) % tabs.length; - } - return (index + 1) % tabs.length; - } - - let i = !tabs[currentIndex] ? tabs.length - 1 : currentIndex; - - while (tabs[i].properties.disabled) { - i = nextIndex(i); - } - - return i; - } - - protected closeIndex(index: number) { - const { onRequestTabClose } = this.properties; - const key = this._tabs[index].properties.key; - this._callTabFocus = true; - - onRequestTabClose && onRequestTabClose(index, key); - } - protected selectFirstIndex() { this.selectIndex(0, true); } @@ -195,7 +197,7 @@ export default class TabController extends TabControllerBase Date: Fri, 27 Oct 2017 09:30:58 -0700 Subject: [PATCH 08/14] textarea and textinput --- src/textarea/Textarea.ts | 28 ++++++++++++++++++---------- src/textinput/TextInput.ts | 28 ++++++++++++++++++---------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/textarea/Textarea.ts b/src/textarea/Textarea.ts index 157cd18be2..2ed190fe53 100644 --- a/src/textarea/Textarea.ts +++ b/src/textarea/Textarea.ts @@ -86,6 +86,22 @@ export default class Textarea extends TextareaBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } + protected getModifierClasses() { + const { + disabled, + invalid, + readOnly, + required + } = this.properties; + return [ + disabled ? css.disabled : null, + invalid ? css.invalid : null, + invalid === false ? css.valid : null, + readOnly ? css.readonly : null, + required ? css.required : null + ]; + } + render(): DNode { const { columns, @@ -104,14 +120,6 @@ export default class Textarea extends TextareaBase { wrapText } = this.properties; - const stateClasses = [ - disabled ? css.disabled : null, - invalid ? css.invalid : null, - invalid === false ? css.valid : null, - readOnly ? css.readonly : null, - required ? css.required : null - ]; - const textarea = v('div', { classes: this.classes(css.inputWrapper) }, [ v('textarea', { classes: this.classes(css.input), @@ -149,14 +157,14 @@ export default class Textarea extends TextareaBase { if (label) { textareaWidget = w(Label, { - extraClasses: { root: parseLabelClasses(this.classes(css.root, ...stateClasses)()) }, + extraClasses: { root: parseLabelClasses(this.classes(css.root, ...this.getModifierClasses())()) }, label, theme: this.properties.theme }, [ textarea ]); } else { textareaWidget = v('div', { - classes: this.classes(css.root, ...stateClasses) + classes: this.classes(css.root, ...this.getModifierClasses()) }, [ textarea ]); } diff --git a/src/textinput/TextInput.ts b/src/textinput/TextInput.ts index 7eb2303c8c..f2479ebe9b 100644 --- a/src/textinput/TextInput.ts +++ b/src/textinput/TextInput.ts @@ -86,6 +86,22 @@ export default class TextInput extends TextInputBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } + protected getModifierClasses() { + const { + disabled, + invalid, + readOnly, + required + } = this.properties; + return [ + disabled ? css.disabled : null, + invalid ? css.invalid : null, + invalid === false ? css.valid : null, + readOnly ? css.readonly : null, + required ? css.required : null + ]; + } + render(): DNode { const { controls, @@ -103,14 +119,6 @@ export default class TextInput extends TextInputBase { value } = this.properties; - const stateClasses = [ - disabled ? css.disabled : null, - invalid ? css.invalid : null, - invalid === false ? css.valid : null, - readOnly ? css.readonly : null, - required ? css.required : null - ]; - const textinput = v('div', { classes: this.classes(css.inputWrapper) }, [ v('input', { classes: this.classes(css.input), @@ -147,14 +155,14 @@ export default class TextInput extends TextInputBase { if (label) { textinputWidget = w(Label, { - extraClasses: { root: parseLabelClasses(this.classes(css.root, ...stateClasses)()) }, + extraClasses: { root: parseLabelClasses(this.classes(css.root, ...this.getModifierClasses())()) }, label, theme: this.properties.theme }, [ textinput ]); } else { textinputWidget = v('div', { - classes: this.classes(css.root, ...stateClasses) + classes: this.classes(css.root, ...this.getModifierClasses()) }, [ textinput ]); } From 8248715b1a3dc59022466d299a40ebe55fbc66f6 Mon Sep 17 00:00:00 2001 From: smhigley Date: Fri, 27 Oct 2017 09:35:45 -0700 Subject: [PATCH 09/14] timepicker --- src/timepicker/TimePicker.ts | 189 +++++++++++++++++------------------ 1 file changed, 93 insertions(+), 96 deletions(-) diff --git a/src/timepicker/TimePicker.ts b/src/timepicker/TimePicker.ts index 0ef59786a4..8aee34f210 100644 --- a/src/timepicker/TimePicker.ts +++ b/src/timepicker/TimePicker.ts @@ -166,41 +166,50 @@ export const TimePickerBase = ThemeableMixin(WidgetBase); export class TimePicker extends TimePickerBase { protected options: TimeUnits[] | null; - render(): DNode { + private _formatUnits(units: TimeUnits): string { + const { step = 60 } = this.properties; + const { hour, minute, second } = units; + + return (step >= 60 ? [ hour, minute ] : [ hour, minute, second ]) + .map(unit => padStart(String(unit), 2, '0')) + .join(':'); + } + + private _getOptionLabel(value: TimeUnits) { + const { getOptionLabel } = this.properties; + const units = parseUnits(value); + return getOptionLabel ? getOptionLabel(units) : this._formatUnits(units); + } + + private _onNativeBlur(event: FocusEvent) { + this.properties.onBlur && this.properties.onBlur(( event.target).value); + } + + private _onNativeChange(event: FocusEvent) { + this.properties.onChange && this.properties.onChange(( event.target).value); + } + + private _onNativeFocus(event: FocusEvent) { + this.properties.onFocus && this.properties.onFocus(( event.target).value); + } + + private _onRequestOptions(value: string) { + this.properties.onRequestOptions && this.properties.onRequestOptions(value, this.getOptions()); + } + + protected getModifierClasses() { const { disabled, invalid, - label, readOnly, - required, - useNativeElement + required } = this.properties; - - if (useNativeElement) { - const input = this.renderNativeInput(); - let children: DNode[] = [ input ]; - - if (label) { - children = [ w(Label, { - extraClasses: { root: parseLabelClasses(this.classes( - css.input, - disabled ? css.disabled : null, - invalid ? css.invalid : null, - readOnly ? css.readonly : null, - required ? css.required : null).get()) - }, - label, - theme: this.properties.theme - }, [ input ]) ]; - } - - return v('span', { - classes: this.classes(css.root), - key: 'root' - }, children); - } - - return this.renderCustomInput(); + return [ + disabled ? css.disabled : null, + invalid ? css.invalid : null, + readOnly ? css.readonly : null, + required ? css.required : null + ]; } protected getOptions() { @@ -220,50 +229,6 @@ export class TimePicker extends TimePickerBase { this.options = null; } - protected renderNativeInput(): DNode { - const { - disabled, - end, - inputProperties, - invalid, - name, - readOnly, - required, - start, - step, - value - } = this.properties; - - const classes = [ - css.input, - disabled ? css.disabled : null, - invalid ? css.invalid : null, - readOnly ? css.readonly : null, - required ? css.required : null - ]; - - return v('input', { - 'aria-describedby': inputProperties && inputProperties.describedBy, - 'aria-invalid': invalid ? 'true' : null, - 'aria-readonly': readOnly ? 'true' : null, - classes: this.classes(...classes), - disabled, - invalid, - key: 'native-input', - max: end, - min: start, - name, - onblur: this._onNativeBlur, - onchange: this._onNativeChange, - onfocus: this._onNativeFocus, - readOnly, - required, - step, - type: 'time', - value - }); - } - protected renderCustomInput(): DNode { const { autoBlur, @@ -314,35 +279,67 @@ export class TimePicker extends TimePickerBase { }); } - private _formatUnits(units: TimeUnits): string { - const { step = 60 } = this.properties; - const { hour, minute, second } = units; + protected renderNativeInput(): DNode { + const { + disabled, + end, + inputProperties, + invalid, + name, + readOnly, + required, + start, + step, + value + } = this.properties; - return (step >= 60 ? [ hour, minute ] : [ hour, minute, second ]) - .map(unit => padStart(String(unit), 2, '0')) - .join(':'); + return v('input', { + 'aria-describedby': inputProperties && inputProperties.describedBy, + 'aria-invalid': invalid ? 'true' : null, + 'aria-readonly': readOnly ? 'true' : null, + classes: this.classes(css.input, ...this.getModifierClasses()), + disabled, + invalid, + key: 'native-input', + max: end, + min: start, + name, + onblur: this._onNativeBlur, + onchange: this._onNativeChange, + onfocus: this._onNativeFocus, + readOnly, + required, + step, + type: 'time', + value + }); } - private _getOptionLabel(value: TimeUnits) { - const { getOptionLabel } = this.properties; - const units = parseUnits(value); - return getOptionLabel ? getOptionLabel(units) : this._formatUnits(units); - } + render(): DNode { + const { + label, + useNativeElement + } = this.properties; - private _onNativeBlur(event: FocusEvent) { - this.properties.onBlur && this.properties.onBlur(( event.target).value); - } + if (useNativeElement) { + const input = this.renderNativeInput(); + let children: DNode[] = [ input ]; - private _onNativeChange(event: FocusEvent) { - this.properties.onChange && this.properties.onChange(( event.target).value); - } + if (label) { + children = [ w(Label, { + extraClasses: { root: parseLabelClasses(this.classes(css.input, ...this.getModifierClasses()).get()) }, + label, + theme: this.properties.theme + }, [ input ]) ]; + } - private _onNativeFocus(event: FocusEvent) { - this.properties.onFocus && this.properties.onFocus(( event.target).value); - } + return v('span', { + classes: this.classes(css.root), + key: 'root' + }, children); + } - private _onRequestOptions(value: string) { - this.properties.onRequestOptions && this.properties.onRequestOptions(value, this.getOptions()); + return this.renderCustomInput(); } } From 970071c634295c60ca2faf63c2949a4a871abc66 Mon Sep 17 00:00:00 2001 From: smhigley Date: Fri, 27 Oct 2017 10:05:31 -0700 Subject: [PATCH 10/14] titlepane --- src/themes/dojo/titlePane.m.css | 5 +- src/titlepane/TitlePane.ts | 79 ++++++++------- src/titlepane/tests/unit/TitlePane.ts | 136 ++------------------------ 3 files changed, 57 insertions(+), 163 deletions(-) diff --git a/src/themes/dojo/titlePane.m.css b/src/themes/dojo/titlePane.m.css index 1e63c47f1c..6a9b3afb60 100644 --- a/src/themes/dojo/titlePane.m.css +++ b/src/themes/dojo/titlePane.m.css @@ -16,8 +16,11 @@ background-color: var(--color-background); border: var(--border-width) solid var(--color-border); color: var(--color-text-faded); + cursor: pointer; + font-size: var(--font-size-base); padding: var(--grid-base) var(--grid-base) var(--grid-base) calc(var(--grid-base) * 4); position: relative; + width: 100%; z-index: 1; } @@ -47,7 +50,7 @@ .arrow { position: absolute; left: 8px; - top: 12px; + top: 10px; } .open .arrow { diff --git a/src/titlepane/TitlePane.ts b/src/titlepane/TitlePane.ts index aa80e93c46..eba9ca35e2 100644 --- a/src/titlepane/TitlePane.ts +++ b/src/titlepane/TitlePane.ts @@ -4,8 +4,6 @@ import { theme, ThemeableMixin, ThemeableProperties } from '@dojo/widget-core/mi import { v } from '@dojo/widget-core/d'; import { WidgetBase } from '@dojo/widget-core/WidgetBase'; -import { Keys } from '../common/util'; - import * as css from './styles/titlePane.m.css'; import * as iconCss from '../common/styles/icons.m.css'; @@ -55,14 +53,6 @@ export default class TitlePane extends TitlePaneBase { this._toggle(); } - private _onTitleKeyUp(event: KeyboardEvent) { - const {keyCode } = event; - - if (keyCode === Keys.Enter || keyCode === Keys.Space) { - this._toggle(); - } - } - private _toggle() { const { closeable = true, @@ -92,12 +82,46 @@ export default class TitlePane extends TitlePaneBase { key === 'content' && this._afterRender(element); } + protected getButtonContent() { + return this.properties.title; + } + + protected getFixedModifierClasses() { + const { closeable = true } = this.properties; + return [ + closeable ? css.closeableFixed : null + ]; + } + + protected getModifierClasses() { + const { closeable = true } = this.properties; + return [ + closeable ? css.closeable : null + ]; + } + + protected getPaneContent() { + return this.children; + } + + protected renderExpandIcon() { + const { open = true } = this.properties; + return v('i', { + classes: this.classes( + css.arrow, + iconCss.icon, + open ? iconCss.downIcon : iconCss.rightIcon + ), + role: 'presentation', + 'aria-hidden': 'true' + }); + } + render(): DNode { const { closeable = true, headingLevel, - open = true, - title + open = true } = this.properties; return v('div', { @@ -108,36 +132,19 @@ export default class TitlePane extends TitlePaneBase { }, [ v('div', { 'aria-level': headingLevel ? String(headingLevel) : null, - classes: this.classes( - closeable ? css.closeable : null, - css.title - ).fixed( - closeable ? css.closeableFixed : null, - css.titleFixed - ), - onclick: this._onTitleClick, - onkeyup: this._onTitleKeyUp, + classes: this.classes(css.title, ...this.getModifierClasses()).fixed(css.titleFixed, ...this.getFixedModifierClasses()), role: 'heading' }, [ - v('div', { + v('button', { 'aria-controls': this._contentId, - 'aria-disabled': closeable ? null : 'true', 'aria-expanded': String(open), + disabled: !closeable, classes: this.classes(css.titleButton), id: this._titleId, - role: 'button', - tabIndex: closeable ? 0 : -1 + onclick: this._onTitleClick }, [ - v('i', { - classes: this.classes( - css.arrow, - iconCss.icon, - open ? iconCss.downIcon : iconCss.rightIcon - ), - role: 'presentation', - 'aria-hidden': 'true' - }), - title + this.renderExpandIcon(), + this.getButtonContent() ]) ]), v('div', { @@ -146,7 +153,7 @@ export default class TitlePane extends TitlePaneBase { classes: this.classes(css.content), id: this._contentId, key: 'content' - }, this.children) + }, this.getPaneContent()) ]); } } diff --git a/src/titlepane/tests/unit/TitlePane.ts b/src/titlepane/tests/unit/TitlePane.ts index be58ed4ff3..cfd0cfafa8 100644 --- a/src/titlepane/tests/unit/TitlePane.ts +++ b/src/titlepane/tests/unit/TitlePane.ts @@ -7,7 +7,6 @@ import { v } from '@dojo/widget-core/d'; import TitlePane, { TitlePaneProperties } from '../../TitlePane'; import * as css from '../../styles/titlePane.m.css'; import * as iconCss from '../../../common/styles/icons.m.css'; -import { Keys } from '../../../common/util'; const isNonEmptyString = compareProperty((value: any) => { return typeof value === 'string' && value.length > 0; @@ -40,18 +39,15 @@ registerSuite('TitlePane', { v('div', { 'aria-level': null, classes: titlePane.classes(css.title, css.titleFixed, css.closeable, css.closeableFixed), - onclick: titlePane.listener, - onkeyup: titlePane.listener, role: 'heading' }, [ - v('div', { + v('button', { 'aria-controls': isNonEmptyString, - 'aria-disabled': null, 'aria-expanded': 'true', classes: titlePane.classes(css.titleButton), + disabled: false, id: isNonEmptyString, - role: 'button', - tabIndex: 0 + onclick: titlePane.listener }, [ v('i', { classes: titlePane.classes( @@ -89,18 +85,15 @@ registerSuite('TitlePane', { v('div', { 'aria-level': '5', classes: titlePane.classes(css.title, css.titleFixed), - onclick: titlePane.listener, - onkeyup: titlePane.listener, role: 'heading' }, [ - v('div', { + v('button', { 'aria-controls': isNonEmptyString, - 'aria-disabled': 'true', 'aria-expanded': 'false', classes: titlePane.classes(css.titleButton), + disabled: true, id: isNonEmptyString, - role: 'button', - tabIndex: -1 + onclick: titlePane.listener }, [ v('i', { classes: titlePane.classes( @@ -135,7 +128,7 @@ registerSuite('TitlePane', { }); titlePane.sendEvent('click', { - selector: `.${css.title}` + selector: `.${css.titleButton}` }); assert.isTrue(called, 'onRequestClose should be called on title click'); }, @@ -152,7 +145,7 @@ registerSuite('TitlePane', { }); titlePane.sendEvent('click', { - selector: `.${css.title}` + selector: `.${css.titleButton}` }); assert.isTrue(called, 'onRequestOpen should be called on title click'); }, @@ -169,7 +162,7 @@ registerSuite('TitlePane', { }); titlePane.getRender(); titlePane.sendEvent('click', { - selector: `.${css.title}` + selector: `.${css.titleButton}` }); titlePane.setProperties({ @@ -181,119 +174,10 @@ registerSuite('TitlePane', { }); titlePane.getRender(); titlePane.sendEvent('click', { - selector: `.${css.title}` + selector: `.${css.titleButton}` }); assert.strictEqual(called, 1, 'onRequestClose should only becalled once'); - }, - - 'can not open pane on keyup'() { - let called = 0; - titlePane.setProperties({ - closeable: false, - open: true, - onRequestClose() { - called++; - }, - title: 'test' - }); - titlePane.getRender(); - titlePane.sendEvent('keyup', { - eventInit: { keyCode: Keys.Enter }, - selector: `.${css.title}` - }); - - titlePane.setProperties({ - open: true, - onRequestClose() { - called++; - }, - title: 'test' - }); - titlePane.getRender(); - titlePane.sendEvent('keyup', { - eventInit: { keyCode: Keys.Enter }, - selector: `.${css.title}` - }); - - assert.strictEqual(called, 1, 'onRequestClose should only becalled once'); - }, - - 'open on keyup'() { - let openCount = 0; - const props = { - closeable: true, - open: false, - onRequestOpen() { - openCount++; - }, - title: 'test' - }; - - titlePane.setProperties(props); - titlePane.sendEvent('keyup', { - eventInit: { keyCode: Keys.Enter }, - selector: `.${css.title}` - }); - assert.strictEqual(openCount, 1, 'onRequestOpen should be called on title enter keyup'); - - titlePane.setProperties(props); - titlePane.sendEvent('keyup', { - eventInit: { keyCode: Keys.Space }, - selector: `.${css.title}` - }); - assert.strictEqual(openCount, 2, 'onRequestOpen should be called on title space keyup'); - }, - - 'close on keyup'() { - let closeCount = 0; - const props = { - closeable: true, - open: true, - onRequestClose() { - closeCount++; - }, - title: 'test' - }; - - titlePane.setProperties(props); - titlePane.sendEvent('keyup', { - eventInit: { keyCode: Keys.Enter }, - selector: `.${css.title}` - }); - assert.strictEqual(closeCount, 1, 'onRequestClose should be called on title enter keyup'); - - titlePane.setProperties(props); - titlePane.sendEvent('keyup', { - eventInit: { keyCode: Keys.Space }, - selector: `.${css.title}` - }); - assert.strictEqual(closeCount, 2, 'onRequestClose should be called on title space keyup'); - }, - - 'keyup: only respond to enter and space'() { - let called = false; - titlePane.setProperties({ - closeable: true, - open: false, - onRequestClose() { - called = true; - }, - onRequestOpen() { - called = true; - }, - title: 'test' - }); - - for (let i = 8; i < 223; i++) { - if (i !== Keys.Enter && i !== Keys.Space) { - titlePane.sendEvent('keyup', { - eventInit: { keyCode: i }, - selector: `.${css.title}` - }); - assert.isFalse(called, `keyCode {i} should be ignored`); - } - } } } }); From dc7e02a9c5f7da90eae46d6b84948fb8bd412bf4 Mon Sep 17 00:00:00 2001 From: smhigley Date: Fri, 27 Oct 2017 10:13:52 -0700 Subject: [PATCH 11/14] splitpane --- src/splitpane/SplitPane.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/splitpane/SplitPane.ts b/src/splitpane/SplitPane.ts index 5423611d2e..011d2e574b 100644 --- a/src/splitpane/SplitPane.ts +++ b/src/splitpane/SplitPane.ts @@ -128,6 +128,21 @@ export default class SplitPane extends SplitPaneBase { this._lastSize = undefined; } + protected getPaneContent(content: DNode) { + return [ content ]; + } + + protected getPaneStyles(): {[key: string]: string} { + const { + direction = Direction.row, + size = DEFAULT_SIZE + } = this.properties; + const styles: {[key: string]: string} = {}; + + styles[direction === Direction.row ? 'width' : 'height'] = `${size}px`; + return styles; + } + protected onElementCreated(element: HTMLElement, key: string) { if (key === 'root') { this._root = element; @@ -141,13 +156,9 @@ export default class SplitPane extends SplitPaneBase { const { direction = Direction.row, leading = null, - size = DEFAULT_SIZE, trailing = null } = this.properties; - const styles: {[key: string]: string} = {}; - styles[direction === Direction.row ? 'width' : 'height'] = `${size}px`; - return v('div', { classes: this.classes( css.root, @@ -165,8 +176,8 @@ export default class SplitPane extends SplitPaneBase { css.leadingFixed ), key: 'leading', - styles - }, [ leading ]), + styles: this.getPaneStyles() + }, this.getPaneContent(leading)), v('div', { classes: this.classes( css.divider @@ -185,7 +196,7 @@ export default class SplitPane extends SplitPaneBase { css.trailingFixed ), key: 'trailing' - }, [ trailing ]) + }, this.getPaneContent(trailing)) ]); } } From aba806bf4359283317fef4077a92c062a6d36cf9 Mon Sep 17 00:00:00 2001 From: smhigley Date: Thu, 2 Nov 2017 02:41:43 +0900 Subject: [PATCH 12/14] pr feedback --- src/calendar/Calendar.ts | 12 ++++++------ src/calendar/DatePicker.ts | 38 +++++++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/calendar/Calendar.ts b/src/calendar/Calendar.ts index 2fd3f7f389..be4c7bcd8d 100644 --- a/src/calendar/Calendar.ts +++ b/src/calendar/Calendar.ts @@ -5,7 +5,7 @@ import { DNode } from '@dojo/widget-core/interfaces'; import uuid from '@dojo/core/uuid'; import { Keys } from '../common/util'; import { CalendarMessages } from './DatePicker'; -import DatePicker from './DatePicker'; +import DatePicker, { Paging } from './DatePicker'; import CalendarCell from './CalendarCell'; import * as css from './styles/calendar.m.css'; import * as baseCss from '../common/styles/base.m.css'; @@ -341,10 +341,10 @@ export default class Calendar extends CalendarBase { }); } - protected renderPagingButtonContent(type: 'next' | 'previous') { + protected renderPagingButtonContent(type: Paging) { const { labels = DEFAULT_LABELS } = this.properties; - const iconClass = type === 'next' ? iconCss.rightIcon : iconCss.leftIcon; - const labelText = type === 'next' ? labels.nextMonth : labels.previousMonth; + const iconClass = type === Paging.next ? iconCss.rightIcon : iconCss.leftIcon; + const labelText = type === Paging.next ? labels.nextMonth : labels.previousMonth; return [ v('i', { classes: this.classes(iconCss.icon, iconClass), @@ -400,12 +400,12 @@ export default class Calendar extends CalendarBase { classes: this.classes(css.previous), tabIndex: this._popupOpen ? -1 : 0, onclick: this._onMonthPageDown - }, this.renderPagingButtonContent('previous')), + }, this.renderPagingButtonContent(Paging.previous)), v('button', { classes: this.classes(css.next), tabIndex: this._popupOpen ? -1 : 0, onclick: this._onMonthPageUp - }, this.renderPagingButtonContent('next')) + }, this.renderPagingButtonContent(Paging.next)) ]) ]); } diff --git a/src/calendar/DatePicker.ts b/src/calendar/DatePicker.ts index bd18851896..6cff19989d 100644 --- a/src/calendar/DatePicker.ts +++ b/src/calendar/DatePicker.ts @@ -8,6 +8,22 @@ import * as css from './styles/calendar.m.css'; import * as baseCss from '../common/styles/base.m.css'; import * as iconCss from '../common/styles/icons.m.css'; +/** + * Enum for next/previous buttons + */ +export const enum Paging { + next = 'next', + previous = 'previous' +}; + +/** + * Enum for month or year controls + */ +export const enum Controls { + month = 'month', + year = 'year' +}; + /** * @type CalendarMessages * @@ -191,16 +207,16 @@ export default class DatePicker extends DatePickerBase { onPopupChange && onPopupChange(this._getPopupState()); } - protected renderControlsTrigger(type: 'month' | 'year'): DNode { + protected renderControlsTrigger(type: Controls): DNode { const { month, monthNames, year } = this.properties; - const content = type === 'month' ? monthNames[month].long : `${year}`; - const open = type === 'month' ? this._monthPopupOpen : this._yearPopupOpen; - const onclick = type === 'month' ? this._onMonthButtonClick : this._onYearButtonClick; + const content = type === Controls.month ? monthNames[month].long : `${year}`; + const open = type === Controls.month ? this._monthPopupOpen : this._yearPopupOpen; + const onclick = type === Controls.month ? this._onMonthButtonClick : this._onYearButtonClick; return v('button', { key: `${type}-button`, @@ -246,10 +262,10 @@ export default class DatePicker extends DatePickerBase { ])); } - protected renderPagingButtonContent(type: 'next' | 'previous') { + protected renderPagingButtonContent(type: Paging) { const { labels } = this.properties; - const iconClass = type === 'next' ? iconCss.rightIcon : iconCss.leftIcon; - const labelText = type === 'next' ? labels.nextMonth : labels.previousMonth; + const iconClass = type === Paging.next ? iconCss.rightIcon : iconCss.leftIcon; + const labelText = type === Paging.next ? labels.nextMonth : labels.previousMonth; return [ v('i', { classes: this.classes(iconCss.icon, iconClass), @@ -312,10 +328,10 @@ export default class DatePicker extends DatePickerBase { }, [ this.renderMonthLabel(month, year) ]), // month trigger - this.renderControlsTrigger('month'), + this.renderControlsTrigger(Controls.month), // year trigger - this.renderControlsTrigger('year') + this.renderControlsTrigger(Controls.year) ]), // month grid @@ -359,12 +375,12 @@ export default class DatePicker extends DatePickerBase { classes: this.classes(css.previous), tabIndex: this._yearPopupOpen ? 0 : -1, onclick: this._onYearPageDown - }, this.renderPagingButtonContent('previous')), + }, this.renderPagingButtonContent(Paging.previous)), v('button', { classes: this.classes(css.next), tabIndex: this._yearPopupOpen ? 0 : -1, onclick: this._onYearPageUp - }, this.renderPagingButtonContent('next')) + }, this.renderPagingButtonContent(Paging.next)) ]) ]) ]); From b63b2a77a6414a0bbc7fb63060b767350cb0a848 Mon Sep 17 00:00:00 2001 From: smhigley Date: Thu, 2 Nov 2017 03:55:15 +0900 Subject: [PATCH 13/14] fixed functional tests and calendar keys --- .../tests/functional/AccordionPane.ts | 2 +- src/button/tests/functional/Button.ts | 3 ++ src/calendar/Calendar.ts | 7 ++- src/calendar/tests/unit/Calendar.ts | 51 ++++++++++--------- src/titlepane/tests/functional/TitlePane.ts | 2 +- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/accordionpane/tests/functional/AccordionPane.ts b/src/accordionpane/tests/functional/AccordionPane.ts index 2a0372a804..a1b35e1406 100644 --- a/src/accordionpane/tests/functional/AccordionPane.ts +++ b/src/accordionpane/tests/functional/AccordionPane.ts @@ -20,7 +20,7 @@ registerSuite('AccordionPane', { .then((size: { height: number }) => { assert.isBelow(size.height, 50); }) - .findByCssSelector('[role="heading"]') + .findByCssSelector('button') .click() .end() .sleep(DELAY) diff --git a/src/button/tests/functional/Button.ts b/src/button/tests/functional/Button.ts index 21fd7147c9..c197436aca 100644 --- a/src/button/tests/functional/Button.ts +++ b/src/button/tests/functional/Button.ts @@ -10,6 +10,8 @@ function getPage(remote: Remote) { .setFindTimeout(5000); } +const DELAY = 750; + registerSuite('Button', { 'button should be visible'() { return getPage(this.remote) @@ -49,6 +51,7 @@ registerSuite('Button', { assert.isNull(pressed, 'Initial state should be null'); }) .click() + .sleep(DELAY) .end() .findByCssSelector(`#example-4 .${css.root}`) .getAttribute('aria-pressed') diff --git a/src/calendar/Calendar.ts b/src/calendar/Calendar.ts index be4c7bcd8d..1653c9e66a 100644 --- a/src/calendar/Calendar.ts +++ b/src/calendar/Calendar.ts @@ -278,7 +278,7 @@ export default class Calendar extends CalendarBase { const isToday = isCurrentMonth && dateString === todayString; - days.push(this.renderDateCell(date, isSelectedDay, isCurrentMonth, isToday)); + days.push(this.renderDateCell(date, week * 7 + i, isSelectedDay, isCurrentMonth, isToday)); } weeks.push(v('tr', days)); @@ -287,12 +287,11 @@ export default class Calendar extends CalendarBase { return weeks; } - protected renderDateCell(date: number, selected: boolean, currentMonth: boolean, today: boolean): DNode { - const key = currentMonth ? `date-${date}` : `date-${date}-dimmed`; + protected renderDateCell(date: number, index: number, selected: boolean, currentMonth: boolean, today: boolean): DNode { const { theme = {} } = this.properties; return w(CalendarCell, { - key, + key: `date-${index}`, callFocus: this._callDateFocus && currentMonth && date === this._focusedDay, date, disabled: !currentMonth, diff --git a/src/calendar/tests/unit/Calendar.ts b/src/calendar/tests/unit/Calendar.ts index 44e802e8f9..414ba21c5f 100644 --- a/src/calendar/tests/unit/Calendar.ts +++ b/src/calendar/tests/unit/Calendar.ts @@ -20,9 +20,11 @@ const compareId = compareProperty((value: any) => { return typeof value === 'string'; }); +let dateIndex = -1; const expectedDateCell = function(widget: any, date: number, active: boolean) { + dateIndex++; return w(CalendarCell, { - key: `date-${date}${active ? '' : '-dimmed'}`, + key: `date-${dateIndex}`, callFocus: false, date, disabled: !active, @@ -36,6 +38,7 @@ const expectedDateCell = function(widget: any, date: number, active: boolean) { }; const expected = function(widget: any, popupOpen = false) { + dateIndex = -1; return v('div', { classes: widget.classes(css.root) }, [ w(DatePicker, { key: 'date-picker', @@ -174,7 +177,7 @@ registerSuite('Calendar', { }); const expectedVdom = expected(widget); - assignProperties(findKey(expectedVdom, 'date-3')!, { + assignProperties(findKey(expectedVdom, 'date-6')!, { selected: true }); @@ -198,7 +201,7 @@ registerSuite('Calendar', { for (let i = 0; i < 7; i++) { replaceChild(expectedVdom, `1,0,0,${i},0`, 'Bar'); } - assignProperties(findKey(expectedVdom, 'date-1')!, { + assignProperties(findKey(expectedVdom, 'date-4')!, { selected: true }); assignChildProperties(expectedVdom, '0', { @@ -227,7 +230,7 @@ registerSuite('Calendar', { widget.callListener('onClick', { args: [1, false], - key: 'date-1' + key: 'date-4' }); assert.strictEqual(selectedDate.getDate(), 1, 'Clicking cell selects correct date'); @@ -251,7 +254,7 @@ registerSuite('Calendar', { widget.callListener('onClick', { args: [1, true], - key: 'date-1-dimmed' + key: 'date-34' }); assert.strictEqual(currentMonth, 6, 'Month changes to July'); assert.strictEqual(selectedDate.getMonth(), 6, 'selected date in July'); @@ -259,7 +262,7 @@ registerSuite('Calendar', { widget.callListener('onClick', { args: [30, true], - key: 'date-30-dimmed' + key: 'date-2' }); assert.strictEqual(currentMonth, 4, 'Month changes to May'); assert.strictEqual(selectedDate.getMonth(), 4, 'selected date in May'); @@ -282,18 +285,18 @@ registerSuite('Calendar', { which: Keys.Right, preventDefault: () => {} }], - key: 'date-1' + key: 'date-4' }); // not a good way to test this, but this would be called with the arrow key widget.callListener('onFocusCalled', { - key: 'date-1' + key: 'date-4' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Enter, preventDefault: () => {} }], - key: 'date-2' + key: 'date-5' }); assert.strictEqual(selectedDate.getDate(), 2, 'Right arrow + enter selects second day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -304,14 +307,14 @@ registerSuite('Calendar', { which: Keys.Down, preventDefault: () => {} }], - key: 'date-2' + key: 'date-5' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Enter, preventDefault: () => {} }], - key: 'date-9' + key: 'date-12' }); assert.strictEqual(selectedDate.getDate(), 9, 'Down arrow + enter selects one week down'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -322,14 +325,14 @@ registerSuite('Calendar', { which: Keys.Left, preventDefault: () => {} }], - key: 'date-9' + key: 'date-12' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-8' + key: 'date-11' }); assert.strictEqual(selectedDate.getDate(), 8, 'Left arrow + space selects previous day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -340,14 +343,14 @@ registerSuite('Calendar', { which: Keys.Up, preventDefault: () => {} }], - key: 'date-8' + key: 'date-11' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-1' + key: 'date-4' }); assert.strictEqual(selectedDate.getDate(), 1, 'Left arrow + space selects previous day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -358,14 +361,14 @@ registerSuite('Calendar', { which: Keys.PageDown, preventDefault: () => {} }], - key: 'date-1' + key: 'date-4' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-30' + key: 'date-33' }); assert.strictEqual(selectedDate.getDate(), 30, 'Page Down + space selects last day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -376,14 +379,14 @@ registerSuite('Calendar', { which: Keys.PageUp, preventDefault: () => {} }], - key: 'date-30' + key: 'date-33' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Space, preventDefault: () => {} }], - key: 'date-1' + key: 'date-4' }); assert.strictEqual(selectedDate.getDate(), 1, 'Page Up + space selects first day'); assert.strictEqual(selectedDate.getMonth(), 5, 'Selected date is same month'); @@ -404,7 +407,7 @@ registerSuite('Calendar', { which: Keys.Left, preventDefault: () => {} }], - key: 'date-1' + key: 'date-4' }); assert.strictEqual(currentMonth, testDate.getMonth() - 1, 'Going left from the first day goes to previous month'); @@ -413,14 +416,14 @@ registerSuite('Calendar', { which: Keys.PageDown, preventDefault: () => {} }], - key: 'date-1' + key: 'date-4' }); widget.callListener('onKeyDown', { args: [{ which: Keys.Right, preventDefault: () => {} }], - key: 'date-1' + key: 'date-4' }); assert.strictEqual(currentMonth, testDate.getMonth() + 1, 'Going right from the last day goes to next month'); }, @@ -444,7 +447,7 @@ registerSuite('Calendar', { which: Keys.Up, preventDefault: () => {} }], - key: 'date-1' + key: 'date-0' }); assert.strictEqual(currentMonth, 11, 'Previous month wraps from January to December'); assert.strictEqual(currentYear, 2016, 'Year decrements when month wraps'); @@ -465,7 +468,7 @@ registerSuite('Calendar', { which: Keys.Down, preventDefault: () => {} }], - key: 'date-30' + key: 'date-35' }); assert.strictEqual(currentMonth, 0, 'Next month wraps from December to January'); assert.strictEqual(currentYear, 2018, 'Year increments when month wraps'); diff --git a/src/titlepane/tests/functional/TitlePane.ts b/src/titlepane/tests/functional/TitlePane.ts index 5e73925d33..25ebb2e6dc 100644 --- a/src/titlepane/tests/functional/TitlePane.ts +++ b/src/titlepane/tests/functional/TitlePane.ts @@ -32,7 +32,7 @@ registerSuite('TitlePane', { height = size.height; }) .end() - .findByCssSelector('#titlePane2 > div > :first-child') + .findByCssSelector('#titlePane2 button') .click() .end() .sleep(DELAY) From 570dcc797e2f8f8a77424a67b499d3bc469adc7f Mon Sep 17 00:00:00 2001 From: smhigley Date: Thu, 2 Nov 2017 04:35:21 +0900 Subject: [PATCH 14/14] pr feedback, return typings --- src/button/Button.ts | 6 +++--- src/calendar/Calendar.ts | 4 ++-- src/calendar/CalendarCell.ts | 2 +- src/calendar/DatePicker.ts | 8 ++++---- src/checkbox/Checkbox.ts | 2 +- src/dialog/Dialog.ts | 8 ++++---- src/radio/Radio.ts | 2 +- src/slidepane/SlidePane.ts | 14 +++++++------- src/slider/Slider.ts | 6 +++--- src/splitpane/SplitPane.ts | 2 +- src/tabcontroller/TabButton.ts | 4 ++-- src/tabcontroller/TabController.ts | 6 +++--- src/textarea/Textarea.ts | 2 +- src/textinput/TextInput.ts | 2 +- src/timepicker/TimePicker.ts | 18 +++++++++++------- src/titlepane/TitlePane.ts | 10 +++++----- 16 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/button/Button.ts b/src/button/Button.ts index ec413c6f63..443677477a 100644 --- a/src/button/Button.ts +++ b/src/button/Button.ts @@ -70,11 +70,11 @@ export default class Button extends ButtonBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - protected getContent() { + protected getContent(): DNode[] { return this.children; } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { disabled, popup = false, @@ -88,7 +88,7 @@ export default class Button extends ButtonBase { ]; } - protected renderPopupIcon() { + protected renderPopupIcon(): DNode { return v('i', { classes: this.classes(css.addon, iconCss.icon, iconCss.downIcon), role: 'presentation', 'aria-hidden': 'true' }); diff --git a/src/calendar/Calendar.ts b/src/calendar/Calendar.ts index 1653c9e66a..03f12e1702 100644 --- a/src/calendar/Calendar.ts +++ b/src/calendar/Calendar.ts @@ -305,7 +305,7 @@ export default class Calendar extends CalendarBase { }); } - protected renderDatePicker() { + protected renderDatePicker(): DNode { const { labels = DEFAULT_LABELS, monthNames = DEFAULT_MONTHS, @@ -340,7 +340,7 @@ export default class Calendar extends CalendarBase { }); } - protected renderPagingButtonContent(type: Paging) { + protected renderPagingButtonContent(type: Paging): DNode[] { const { labels = DEFAULT_LABELS } = this.properties; const iconClass = type === Paging.next ? iconCss.rightIcon : iconCss.leftIcon; const labelText = type === Paging.next ? labels.nextMonth : labels.previousMonth; diff --git a/src/calendar/CalendarCell.ts b/src/calendar/CalendarCell.ts index 69c7758e69..5ed28800e2 100644 --- a/src/calendar/CalendarCell.ts +++ b/src/calendar/CalendarCell.ts @@ -65,7 +65,7 @@ export default class CalendarCell extends CalendarCellBase { }, [ content ]); } - protected renderMonthLabel(month: number, year: number) { + protected renderMonthLabel(month: number, year: number): DNode { const { monthNames, renderMonthLabel } = this.properties; return renderMonthLabel ? renderMonthLabel(month, year) : `${monthNames[month].long} ${year}`; } - protected renderMonthRadios() { + protected renderMonthRadios(): DNode[] { const { month } = this.properties; return this.properties.monthNames.map((monthName, i) => v('label', { @@ -262,7 +262,7 @@ export default class DatePicker extends DatePickerBase { ])); } - protected renderPagingButtonContent(type: Paging) { + protected renderPagingButtonContent(type: Paging): DNode[] { const { labels } = this.properties; const iconClass = type === Paging.next ? iconCss.rightIcon : iconCss.leftIcon; const labelText = type === Paging.next ? labels.nextMonth : labels.previousMonth; @@ -275,7 +275,7 @@ export default class DatePicker extends DatePickerBase { ]; } - protected renderYearRadios() { + protected renderYearRadios(): DNode[] { const { year } = this.properties; const radios = []; diff --git a/src/checkbox/Checkbox.ts b/src/checkbox/Checkbox.ts index 799321bc78..e674fde2c7 100644 --- a/src/checkbox/Checkbox.ts +++ b/src/checkbox/Checkbox.ts @@ -87,7 +87,7 @@ export default class Checkbox extends CheckboxBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { checked = false, disabled, diff --git a/src/dialog/Dialog.ts b/src/dialog/Dialog.ts index 8695a21741..72ffb2ed06 100644 --- a/src/dialog/Dialog.ts +++ b/src/dialog/Dialog.ts @@ -83,25 +83,25 @@ export default class Dialog extends DialogBase { })); } - protected getContent() { + protected getContent(): DNode { return v('div', { classes: this.classes(css.content), key: 'content' }, this.children); } - protected renderCloseIcon() { + protected renderCloseIcon(): DNode { return v('i', { classes: this.classes(iconCss.icon, iconCss.closeIcon), role: 'presentation', 'aria-hidden': 'true' }); } - protected renderTitle() { + protected renderTitle(): DNode { const { title = '' } = this.properties; return v('div', { id: this._titleId }, [ title ]); } - protected renderUnderlay() { + protected renderUnderlay(): DNode { const { underlay } = this.properties; return v('div', { classes: this.classes(underlay ? css.underlayVisible : null).fixed(css.underlay), diff --git a/src/radio/Radio.ts b/src/radio/Radio.ts index 84a9c00a82..201dc8b424 100644 --- a/src/radio/Radio.ts +++ b/src/radio/Radio.ts @@ -74,7 +74,7 @@ export default class Radio extends RadioBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { checked = false, disabled, diff --git a/src/slidepane/SlidePane.ts b/src/slidepane/SlidePane.ts index eecd1cd844..245eaf6a6b 100644 --- a/src/slidepane/SlidePane.ts +++ b/src/slidepane/SlidePane.ts @@ -172,11 +172,11 @@ export default class SlidePane extends SlidePaneBase { } } - protected getContent() { + protected getContent(): DNode { return v('div', { classes: this.classes(css.content) }, this.children); } - protected getStyles() { + protected getStyles(): { [key: string]: string | null } { const { align = Align.left, open = false, @@ -198,7 +198,7 @@ export default class SlidePane extends SlidePaneBase { }; } - protected getFixedModifierClasses() { + protected getFixedModifierClasses(): (string | null)[] { const { align = Align.left, open = false @@ -213,7 +213,7 @@ export default class SlidePane extends SlidePaneBase { ]; } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { align = Align.left, open = false @@ -228,18 +228,18 @@ export default class SlidePane extends SlidePaneBase { ]; } - protected renderCloseIcon() { + protected renderCloseIcon(): DNode { return v('i', { classes: this.classes(iconCss.icon, iconCss.closeIcon), role: 'presentation', 'aria-hidden': 'true' }); } - protected renderTitle() { + protected renderTitle(): DNode { const { title = '' } = this.properties; return v('div', { id: this._titleId }, [ title ]); } - protected renderUnderlay() { + protected renderUnderlay(): DNode { const { underlay = false } = this.properties; return v('div', { classes: this.classes(underlay ? css.underlayVisible : null).fixed(css.underlay), diff --git a/src/slider/Slider.ts b/src/slider/Slider.ts index 273a6d2ed9..bafa654c7c 100644 --- a/src/slider/Slider.ts +++ b/src/slider/Slider.ts @@ -91,7 +91,7 @@ export default class Slider extends SliderBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { disabled, invalid, @@ -110,7 +110,7 @@ export default class Slider extends SliderBase { ]; } - protected renderControls(percentValue: number) { + protected renderControls(percentValue: number): DNode { const { vertical = false, verticalHeight = '200px' @@ -132,7 +132,7 @@ export default class Slider extends SliderBase { ]); } - protected renderOutput(value: number, percentValue: number) { + protected renderOutput(value: number, percentValue: number): DNode { const { output, outputIsTooltip = false, diff --git a/src/splitpane/SplitPane.ts b/src/splitpane/SplitPane.ts index 011d2e574b..5a26656aad 100644 --- a/src/splitpane/SplitPane.ts +++ b/src/splitpane/SplitPane.ts @@ -128,7 +128,7 @@ export default class SplitPane extends SplitPaneBase { this._lastSize = undefined; } - protected getPaneContent(content: DNode) { + protected getPaneContent(content: DNode): DNode[] { return [ content ]; } diff --git a/src/tabcontroller/TabButton.ts b/src/tabcontroller/TabButton.ts index be2e4c7d8f..01064739bb 100644 --- a/src/tabcontroller/TabButton.ts +++ b/src/tabcontroller/TabButton.ts @@ -130,7 +130,7 @@ export default class TabButton extends TabButtonBase { } } - protected getContent() { + protected getContent(): DNode[] { const { active, closeable } = this.properties; return [ @@ -144,7 +144,7 @@ export default class TabButton extends TabButtonBase { ]; } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { active, disabled } = this.properties; return [ active ? css.activeTabButton : null, diff --git a/src/tabcontroller/TabController.ts b/src/tabcontroller/TabController.ts index 9da9c6d5c8..77aaaeb53c 100644 --- a/src/tabcontroller/TabController.ts +++ b/src/tabcontroller/TabController.ts @@ -106,11 +106,11 @@ export default class TabController extends TabControllerBase { const { closeable, @@ -143,7 +143,7 @@ export default class TabController extends TabControllerBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { disabled, invalid, diff --git a/src/textinput/TextInput.ts b/src/textinput/TextInput.ts index f2479ebe9b..4607217e5f 100644 --- a/src/textinput/TextInput.ts +++ b/src/textinput/TextInput.ts @@ -86,7 +86,7 @@ export default class TextInput extends TextInputBase { private _onTouchEnd (event: TouchEvent) { this.properties.onTouchEnd && this.properties.onTouchEnd(event); } private _onTouchCancel (event: TouchEvent) { this.properties.onTouchCancel && this.properties.onTouchCancel(event); } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { disabled, invalid, diff --git a/src/timepicker/TimePicker.ts b/src/timepicker/TimePicker.ts index 8aee34f210..b4b828fe0a 100644 --- a/src/timepicker/TimePicker.ts +++ b/src/timepicker/TimePicker.ts @@ -10,6 +10,10 @@ import ComboBox from '../combobox/ComboBox'; import Label, { LabelOptions, parseLabelClasses } from '../label/Label'; import { TextInputProperties } from '../textinput/TextInput'; +interface FocusInputEvent extends FocusEvent { + target: HTMLInputElement; +} + /** * @type TimePickerProperties * @@ -181,23 +185,23 @@ export class TimePicker extends TimePickerBase { return getOptionLabel ? getOptionLabel(units) : this._formatUnits(units); } - private _onNativeBlur(event: FocusEvent) { - this.properties.onBlur && this.properties.onBlur(( event.target).value); + private _onNativeBlur(event: FocusInputEvent) { + this.properties.onBlur && this.properties.onBlur(event.target.value); } - private _onNativeChange(event: FocusEvent) { - this.properties.onChange && this.properties.onChange(( event.target).value); + private _onNativeChange(event: FocusInputEvent) { + this.properties.onChange && this.properties.onChange(event.target.value); } - private _onNativeFocus(event: FocusEvent) { - this.properties.onFocus && this.properties.onFocus(( event.target).value); + private _onNativeFocus(event: FocusInputEvent) { + this.properties.onFocus && this.properties.onFocus(event.target.value); } private _onRequestOptions(value: string) { this.properties.onRequestOptions && this.properties.onRequestOptions(value, this.getOptions()); } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { disabled, invalid, diff --git a/src/titlepane/TitlePane.ts b/src/titlepane/TitlePane.ts index eba9ca35e2..1353e5ec1a 100644 --- a/src/titlepane/TitlePane.ts +++ b/src/titlepane/TitlePane.ts @@ -82,29 +82,29 @@ export default class TitlePane extends TitlePaneBase { key === 'content' && this._afterRender(element); } - protected getButtonContent() { + protected getButtonContent(): DNode { return this.properties.title; } - protected getFixedModifierClasses() { + protected getFixedModifierClasses(): (string | null)[] { const { closeable = true } = this.properties; return [ closeable ? css.closeableFixed : null ]; } - protected getModifierClasses() { + protected getModifierClasses(): (string | null)[] { const { closeable = true } = this.properties; return [ closeable ? css.closeable : null ]; } - protected getPaneContent() { + protected getPaneContent(): DNode[] { return this.children; } - protected renderExpandIcon() { + protected renderExpandIcon(): DNode { const { open = true } = this.properties; return v('i', { classes: this.classes(