From 3cc3559348155f7e05b6c1c0da934910072dcf77 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Sun, 15 Dec 2024 21:01:59 +0100 Subject: [PATCH 01/43] accessibility(Autocomplete) implemented tabindex and aria expanded #538 --- src/autocomplete.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/autocomplete.ts b/src/autocomplete.ts index e07ee62335..944736b8b7 100644 --- a/src/autocomplete.ts +++ b/src/autocomplete.ts @@ -3,7 +3,7 @@ import { Dropdown, DropdownOptions } from "./dropdown"; import { Component, BaseOptions, InitElements, MElement } from "./component"; export interface AutocompleteData { - /** + /** * A primitive value that can be converted to string. * If "text" is not provided, it will also be used as "option text" as well */ @@ -82,7 +82,7 @@ let _defaults: AutocompleteOptions = { onSearch: (text: string, autocomplete: Autocomplete) => { const normSearch = text.toLocaleLowerCase(); autocomplete.setMenuItems( - autocomplete.options.data.filter((option) => + autocomplete.options.data.filter((option) => option.id.toString().toLocaleLowerCase().includes(normSearch) || option.text?.toLocaleLowerCase().includes(normSearch) ) @@ -118,7 +118,7 @@ export class Autocomplete extends Component { ...Autocomplete.defaults, ...options }; - + this.isOpen = false; this.count = 0; this.activeIndex = -1; @@ -215,6 +215,7 @@ export class Autocomplete extends Component { this.container.style.maxHeight = this.options.maxDropDownHeight; this.container.id = `autocomplete-options-${Utils.guid()}`; this.container.classList.add('autocomplete-content', 'dropdown-content'); + this.container.ariaExpanded = 'true'; this.el.setAttribute('data-target', this.container.id); this.menuItems.forEach(menuItem => { @@ -260,6 +261,7 @@ export class Autocomplete extends Component { } _removeDropdown() { + this.container.ariaExpanded = 'false'; this.container.parentNode.removeChild(this.container); } @@ -368,6 +370,7 @@ export class Autocomplete extends Component { 'style', 'display:grid; grid-auto-flow: column; user-select: none; align-items: center;' ); + item.tabIndex = 0; // Checkbox if (this.options.isMultiSelect) { item.innerHTML = ` From 535105125c56739bb8cdc2af88932c91a8b3682c Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Sun, 15 Dec 2024 20:05:16 +0100 Subject: [PATCH 02/43] accessibility(FloatingActionButton) implemented tabindex and aria expanded #540 --- src/buttons.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/buttons.ts b/src/buttons.ts index 3d4a6a5853..cd79262146 100644 --- a/src/buttons.ts +++ b/src/buttons.ts @@ -1,4 +1,5 @@ import { Component, BaseOptions, InitElements, MElement, Openable } from "./component"; +import { Utils } from './utils'; export interface FloatingActionButtonOptions extends BaseOptions { /** @@ -59,6 +60,8 @@ export class FloatingActionButton extends Component this.offsetX = 0; this.el.classList.add(`direction-${this.options.direction}`); + this._anchor.tabIndex = 0; + this._menu.ariaExpanded = 'false'; if (this.options.direction === 'top') this.offsetY = 40; else if (this.options.direction === 'right') @@ -111,6 +114,7 @@ export class FloatingActionButton extends Component } else { this.el.addEventListener('click', this._handleFABClick); } + this.el.addEventListener('keypress', this._handleFABKeyPress); } _removeEventHandlers() { @@ -120,9 +124,20 @@ export class FloatingActionButton extends Component } else { this.el.removeEventListener('click', this._handleFABClick); } + this.el.removeEventListener('keypress', this._handleFABKeyPress); } _handleFABClick = () => { + this._handleFABToggle() +} + + _handleFABKeyPress = (e) => { + if(Utils.keys.ENTER.includes(e.key)) { + this._handleFABToggle(); + } + } + + _handleFABToggle = () => { if (this.isOpen) { this.close(); } else { @@ -164,9 +179,10 @@ export class FloatingActionButton extends Component _animateInFAB() { this.el.classList.add('active'); + this._menu.ariaExpanded = 'true'; const delayIncrement = 40; const duration = 275; - + this._floatingBtnsReverse.forEach((el, index) => { const delay = delayIncrement * index; el.style.transition = 'none'; @@ -181,6 +197,7 @@ export class FloatingActionButton extends Component el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`; el.style.opacity = '1'; el.style.transform = 'translate(0, 0) scale(1)'; + el.tabIndex = 0; }, 1); }, delay); }); @@ -188,12 +205,16 @@ export class FloatingActionButton extends Component _animateOutFAB() { const duration = 175; - setTimeout(() => this.el.classList.remove('active'), duration); + setTimeout(() => { + this.el.classList.remove('active'), duration; + this._menu.ariaExpanded = 'false'; + }); this._floatingBtnsReverse.forEach((el) => { el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`; // to el.style.opacity = '0'; el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`; + el.tabIndex = -1; }); } @@ -225,6 +246,7 @@ export class FloatingActionButton extends Component this.el.style.left = '0'; this.el.style.transform = 'translateX(' + this.offsetX + 'px)'; this.el.style.transition = 'none'; + this._menu.ariaExpanded = 'true'; this._anchor.style.transform = `translateY(${this.offsetY}px`; this._anchor.style.transition = 'none'; @@ -246,7 +268,10 @@ export class FloatingActionButton extends Component backdrop.style.transform = 'scale(' + scaleFactor + ')'; backdrop.style.transition = 'transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)'; - this._menu.querySelectorAll('li > a').forEach((a: HTMLAnchorElement) => a.style.opacity = '1'); + this._menu.querySelectorAll('li > a').forEach((a: HTMLAnchorElement) => { + a.style.opacity = '1'; + a.tabIndex = 0; + }); // Scroll to close. window.addEventListener('scroll', this.close, true); From 7e1f83fa122d922c998402a1abb19b7f2be0db17 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 15:58:18 +0100 Subject: [PATCH 03/43] enhancement(Datepicker) refactor styling to accomply with material m3 standards #558; implemented styling for in range (wip) #360 --- sass/components/_datepicker.scss | 84 ++++++++++++++++++++++++-------- src/datepicker.ts | 13 +++-- 2 files changed, 74 insertions(+), 23 deletions(-) diff --git a/sass/components/_datepicker.scss b/sass/components/_datepicker.scss index be6491e9c3..f276473b58 100644 --- a/sass/components/_datepicker.scss +++ b/sass/components/_datepicker.scss @@ -1,8 +1,9 @@ /* Modal */ .datepicker-modal { max-width: 325px; - min-width: 300px; - max-height: none; + // @removed since v2.2.1-dev regarding Material M3 standards + /* min-width: 300px; + max-height: none; */ } .datepicker-container.modal-content { @@ -75,9 +76,11 @@ /* Date Display */ .datepicker-date-display { flex: 1 auto; - background-color: var(--md-sys-color-primary); - color: var(--md-sys-color-on-primary); + // @removed since v2.2.1-dev regarding Material M3 standards + // background-color: var(--md-sys-color-primary); + // color: var(--md-sys-color-on-primary); padding: 20px 22px; + border-bottom: 1px solid var(--md-sys-color-surface-variant-light); font-weight: 500; .year-text { @@ -124,46 +127,88 @@ color: var(--md-sys-color-on-surface-variant); } - td { + .datepicker-day { color: var(--md-sys-color-on-background); &.is-today { color: var(--md-sys-color-primary); } - &.is-selected { + /*&.is-selected button { background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); - } - - &.is-outside-current-month, - &.is-disabled { - color: var(--md-sys-color-on-surface); - pointer-events: none; - } + }*/ - border-radius: 50%; + // border-radius: 50%; padding: 0; } } +.datepicker-day.is-inrange { + .datepicker-day-container { + position: relative; + background-color: var(--md-sys-color-primary-container); + z-index: 0; + } + + // @todo find solution why pseudo selectors are not working or implement other method + &:first-child .datepicker-day-container:before, + &:last-child .datepicker-day-container:before { + position: absolute; + height: 100%; + width: 50%; + content: ''; + background-color: var(--md-sys-color-primary-container); + z-index: 0; + } + + &:first-child .datepicker-day-container:before { + left: -50%; + } + + &:last-child .datepicker-day-container:before { + left: auto; + right: -50%; + } +} + .datepicker-day-button { background-color: transparent; border: none; - line-height: 38px; + line-height: 34px; display: block; - width: 100%; + width: 34px; border-radius: 50%; + margin: 5px; padding: 0 5px; cursor: pointer; color: inherit; + position: relative; + z-index: 1; + &:hover { background-color: rgba(var(--md-sys-color-primary-numeric), 0.06); } &:focus { - background-color: rgba(var(--md-sys-color-primary-numeric), 0.18); + border-color: var(--md-sys-color-primary); + } + + .is-selected & { + background-color: var(--md-sys-color-primary); + color: var(--md-sys-color-on-primary); + + &:focus { + background-color: var(--md-sys-color-surface-variant); + color: var(--md-sys-color-primary); + } + } + + &.is-outside-current-month button, + &.is-disabled button { + color: var(--md-sys-color-on-surface); + pointer-events: none; } } @@ -192,7 +237,8 @@ /* Media Queries */ @media #{$medium-and-up} { - .datepicker-modal { + @removed since v2.2.1-dev regarding Material M3 standards + /*.datepicker-modal { max-width: 625px; } @@ -208,7 +254,7 @@ .datepicker-table, .datepicker-footer { width: 320px; - } + }*/ .datepicker-day-button { line-height: 44px; diff --git a/src/datepicker.ts b/src/datepicker.ts index a29c29204d..10190df938 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -804,7 +804,7 @@ export class Datepicker extends Component { } renderDay(opts) { - const arr = []; + const arr = ['datepicker-day']; let ariaSelected = 'false'; if (opts.isEmpty) { if (opts.showDaysInNextAndPreviousMonths) { @@ -834,9 +834,9 @@ export class Datepicker extends Component { arr.push('has-event'); } - // @todo should we create this additional css class? if (opts.isInRange) { arr.push('is-inrange'); + ariaSelected = 'true'; } // @todo should we create this additional css class? @@ -855,7 +855,9 @@ export class Datepicker extends Component { } return ( `` + + '
' + `` + + '
' + '' ); } @@ -955,7 +957,8 @@ export class Datepicker extends Component { ''; html += ``; + // @todo remove button class and add scss mixin, current implementation temporary for focus states, @see https://github.com/materializecss/materialize/issues/566 + } btn" type="button">${leftArrow}`; html += '
'; if (opts.showMonthAfterYear) { @@ -977,7 +980,8 @@ export class Datepicker extends Component { ''; html += ``; + // @todo remove button class and add scss mixin, current implementation temporary for focus states, @see https://github.com/materializecss/materialize/issues/566 + } btn" type="button">${rightArrow}`; return (html += '
'); } @@ -1035,6 +1039,7 @@ export class Datepicker extends Component { // Init Materialize Select const yearSelect = this.calendarEl.querySelector('.orig-select-year') as HTMLSelectElement; const monthSelect = this.calendarEl.querySelector('.orig-select-month') as HTMLSelectElement; + // @todo fix accessibility @see https://github.com/materializecss/materialize/issues/522 FormSelect.init(yearSelect, { classes: 'select-year', dropdownOptions: { container: document.body, constrainWidth: false } From a12be6193e23b7bb598b9e3e917ee32db70886c0 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 18:05:03 +0100 Subject: [PATCH 04/43] enhancement(Datepicker) refinement on daterange styling, revert day cell to initial, implemented additional is-daterange-start + is-daterange-end classes and refactored styling to use pseudo selectors on newly created classes #360 --- sass/components/_datepicker.scss | 37 ++++++++++++++++---------------- src/datepicker.ts | 16 +++++++++++--- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/sass/components/_datepicker.scss b/sass/components/_datepicker.scss index f276473b58..16a99d45a9 100644 --- a/sass/components/_datepicker.scss +++ b/sass/components/_datepicker.scss @@ -144,32 +144,33 @@ } } -.datepicker-day.is-inrange { - .datepicker-day-container { - position: relative; - background-color: var(--md-sys-color-primary-container); - z-index: 0; - } +.datepicker-day.is-daterange-start, +.datepicker-day.is-daterange-end, +.datepicker-day.is-daterange { + position: relative; - // @todo find solution why pseudo selectors are not working or implement other method - &:first-child .datepicker-day-container:before, - &:last-child .datepicker-day-container:before { + &:before { position: absolute; - height: 100%; - width: 50%; + top: 5px; + width: 100%; + height: 34px; content: ''; background-color: var(--md-sys-color-primary-container); z-index: 0; } +} - &:first-child .datepicker-day-container:before { - left: -50%; - } +.datepicker-day.is-daterange-start:before, +.datepicker-day.is-daterange-end:before { + width: 50%; +} - &:last-child .datepicker-day-container:before { - left: auto; - right: -50%; - } +.datepicker-day.is-daterange-start:before { + left: 50%; +} + +.datepicker-day.is-daterange .datepicker-day-button:before { + background-color: var(--md-sys-color-primary-container); } .datepicker-day-button { diff --git a/src/datepicker.ts b/src/datepicker.ts index 10190df938..b8d4ca7bad 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -739,6 +739,8 @@ export class Datepicker extends Component { (this.options.maxDate && day > this.options.maxDate) || (this.options.disableWeekends && Datepicker._isWeekend(day)) || (this.options.disableDayFn && this.options.disableDayFn(day)), + isDateRangeStart = this.options.isDateRange && Datepicker._compareDates(this.date, day), + isDateRangeEnd = this.options.isDateRange && Datepicker._compareDates(this.endDate, day), isDateRange = this.options.isDateRange && Datepicker._isDate(this.endDate) && @@ -788,6 +790,8 @@ export class Datepicker extends Component { isEndRange: isEndRange, isInRange: isInRange, showDaysInNextAndPreviousMonths: this.options.showDaysInNextAndPreviousMonths, + isDateRangeStart: isDateRangeStart, + isDateRangeEnd: isDateRangeEnd, isDateRange: isDateRange }; @@ -834,6 +838,7 @@ export class Datepicker extends Component { arr.push('has-event'); } + // @todo create additional css class if (opts.isInRange) { arr.push('is-inrange'); ariaSelected = 'true'; @@ -849,15 +854,20 @@ export class Datepicker extends Component { arr.push('is-endrange'); } - // @todo create additional css class + if (opts.isDateRangeStart) { + arr.push('is-daterange-start'); + } + + if (opts.isDateRangeEnd) { + arr.push('is-daterang-eend'); + } + if (opts.isDateRange) { arr.push('is-daterange'); } return ( `` + - '
' + `` + - '
' + '' ); } From b8c344b74c260209a1cf529a1de2fc3046276c4d Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 20:32:26 +0100 Subject: [PATCH 05/43] enhancement(Datepicker) refactor all modal related selectors #558 --- src/datepicker.ts | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/datepicker.ts b/src/datepicker.ts index b8d4ca7bad..46c747801b 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -260,7 +260,7 @@ export class Datepicker extends Component { doneBtn: HTMLElement; cancelBtn: HTMLElement; - modalEl: HTMLElement; + containerEl: HTMLElement; yearTextEl: HTMLElement; dateTextEl: HTMLElement; endDateEl: HTMLInputElement; @@ -409,7 +409,7 @@ export class Datepicker extends Component { destroy() { this._removeEventHandlers(); - this.modalEl.remove(); + this.containerEl.remove(); this.destroySelects(); this.el['M_Datepicker'] = undefined; } @@ -447,10 +447,11 @@ export class Datepicker extends Component { const optEl = this.options.container; this.options.container = optEl instanceof HTMLElement ? optEl : (document.querySelector(optEl) as HTMLElement); - this.options.container.append(this.modalEl); + this.options.container.append(this.containerEl); } else { - //this.modalEl.before(this.el); - this.el.parentElement.appendChild(this.modalEl); + //this.containerEl.before(this.el); + const appendTo = !this.endDateEl ? this.el : this.endDateEl; + appendTo.parentElement.after(this.containerEl); } } @@ -859,7 +860,7 @@ export class Datepicker extends Component { } if (opts.isDateRangeEnd) { - arr.push('is-daterang-eend'); + arr.push('is-daterange-end'); } if (opts.isDateRange) { @@ -1085,17 +1086,17 @@ export class Datepicker extends Component { const template = document.createElement('template'); template.innerHTML = Datepicker._template.trim(); - this.modalEl = template.content.firstChild; + this.containerEl = template.content.firstChild; - this.calendarEl = this.modalEl.querySelector('.datepicker-calendar'); - this.yearTextEl = this.modalEl.querySelector('.year-text'); - this.dateTextEl = this.modalEl.querySelector('.date-text'); + this.calendarEl = this.containerEl.querySelector('.datepicker-calendar'); + this.yearTextEl = this.containerEl.querySelector('.year-text'); + this.dateTextEl = this.containerEl.querySelector('.date-text'); if (this.options.showClearBtn) { - this.clearBtn = this.modalEl.querySelector('.datepicker-clear'); + this.clearBtn = this.containerEl.querySelector('.datepicker-clear'); } // TODO: This should not be part of the datepicker - this.doneBtn = this.modalEl.querySelector('.datepicker-done'); - this.cancelBtn = this.modalEl.querySelector('.datepicker-cancel'); + this.doneBtn = this.containerEl.querySelector('.datepicker-done'); + this.cancelBtn = this.containerEl.querySelector('.datepicker-cancel'); this.formats = { d: (date: Date) => { @@ -1305,8 +1306,7 @@ export class Datepicker extends Component { static { Datepicker._template = ` -
- -
- `; + `; } } From 1e9c276f54b8e38a70f414b392d17b8df84f4316 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 20:33:33 +0100 Subject: [PATCH 06/43] enhancement(Datepicker) refactor modal css selectors --- sass/components/_datepicker.scss | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sass/components/_datepicker.scss b/sass/components/_datepicker.scss index 16a99d45a9..caf2073eba 100644 --- a/sass/components/_datepicker.scss +++ b/sass/components/_datepicker.scss @@ -1,14 +1,16 @@ /* Modal */ -.datepicker-modal { +// @removed since v2.2.1 +/*.datepicker-modal { max-width: 325px; // @removed since v2.2.1-dev regarding Material M3 standards - /* min-width: 300px; - max-height: none; */ -} + min-width: 300px; + max-height: none; +}*/ -.datepicker-container.modal-content { +.datepicker-container { display: flex; flex-direction: column; + max-width: 325px; padding: 0; background-color: var(--md-sys-color-surface); } @@ -238,7 +240,7 @@ /* Media Queries */ @media #{$medium-and-up} { - @removed since v2.2.1-dev regarding Material M3 standards + // @removed since v2.2.1-dev regarding Material M3 standards /*.datepicker-modal { max-width: 625px; } From b599ff23a3e59dba57a21aac753a44c69784940a Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 20:35:44 +0100 Subject: [PATCH 07/43] enhancement(Datepicker) implemented input field callback functions #558 --- src/datepicker.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/datepicker.ts b/src/datepicker.ts index 46c747801b..676bf9c71e 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -143,7 +143,11 @@ export interface DatepickerOptions extends BaseOptions { * @default null */ onDraw: (() => void) | null; - + /** + * Callback function for interaction with input field. + * @default null + */ + onInputInteraction: (() => void) | null; /** Field used for internal calculations DO NOT CHANGE IT */ minYear?: number; /** Field used for internal calculations DO NOT CHANGE IT */ @@ -245,7 +249,8 @@ const _defaults: DatepickerOptions = { events: [], // callback function onSelect: null, - onDraw: null + onDraw: null, + onInputInteraction: null, }; export class Datepicker extends Component { @@ -1154,6 +1159,7 @@ export class Datepicker extends Component { this.setDateFromInput(e.target as HTMLInputElement); this.draw(); this.gotoDate(e.target === this.el ? this.date : this.endDate); + if (this.options.onInputInteraction) this.options.onInputInteraction.call(this); }; _handleInputKeydown = (e: KeyboardEvent) => { @@ -1161,6 +1167,7 @@ export class Datepicker extends Component { e.preventDefault(); this.setDateFromInput(e.target as HTMLInputElement); this.draw(); + if (this.options.onInputInteraction) this.options.onInputInteraction.call(this); } }; From 4f5c37a33d79657f92b69ef4c30db70292354657 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 20:37:30 +0100 Subject: [PATCH 08/43] enhancement(Datepicker) allow user specified end date element #558 --- src/datepicker.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/datepicker.ts b/src/datepicker.ts index 676bf9c71e..06b13b3282 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -30,6 +30,10 @@ export interface DatepickerOptions extends BaseOptions { * @default false */ isDateRange: boolean; + /** + * The selector of the user specified date range end element + */ + dateRangeEndEl: string | null; /** * The initial condition if the datepicker is based on multiple date selection. * @default false @@ -169,6 +173,8 @@ const _defaults: DatepickerOptions = { parse: null, // The initial condition if the datepicker is based on date range isDateRange: false, + // The selector of the user specified date range end element + dateRangeEndEl: null, // The initial condition if the datepicker is based on multiple date selection isMultipleSelection: false, // The initial date to view when first opened @@ -437,8 +443,14 @@ export class Datepicker extends Component { } if (this.options.isDateRange) { - this.endDateEl = this.createDateInput(); - this.endDateEl.classList.add('datepicker-end-date'); + if (!this.options.dateRangeEndEl) { + this.endDateEl = this.createDateInput(); + this.endDateEl.classList.add('datepicker-end-date'); + } else if(document.querySelector(this.options.dateRangeEndEl) as HTMLInputElement === undefined) { + console.warn('Specified date range end input element in dateRangeEndEl not found'); + } else { + this.endDateEl = document.querySelector(this.options.dateRangeEndEl) as HTMLInputElement; + } } if (this.options.showClearBtn) { @@ -745,8 +757,8 @@ export class Datepicker extends Component { (this.options.maxDate && day > this.options.maxDate) || (this.options.disableWeekends && Datepicker._isWeekend(day)) || (this.options.disableDayFn && this.options.disableDayFn(day)), - isDateRangeStart = this.options.isDateRange && Datepicker._compareDates(this.date, day), - isDateRangeEnd = this.options.isDateRange && Datepicker._compareDates(this.endDate, day), + isDateRangeStart = this.options.isDateRange && this.date && this.endDate && Datepicker._compareDates(this.date, day), + isDateRangeEnd = this.options.isDateRange && this.endDate && Datepicker._compareDates(this.endDate, day), isDateRange = this.options.isDateRange && Datepicker._isDate(this.endDate) && From ae8a06a4be2c6d931af894343f914855b30eab80 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 20:39:12 +0100 Subject: [PATCH 09/43] enhancement(Datepicker) implemented date format rendering in supporting text field if present #558 --- src/datepicker.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/datepicker.ts b/src/datepicker.ts index 06b13b3282..1ebeb70e31 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -442,6 +442,10 @@ export class Datepicker extends Component { this.el.classList.add('datepicker-date-input'); } + if (this.el.parentElement.querySelector('.datepicker-format') !== undefined) { + this._renderDateInputFormat(this.el); + } + if (this.options.isDateRange) { if (!this.options.dateRangeEndEl) { this.endDateEl = this.createDateInput(); @@ -450,6 +454,9 @@ export class Datepicker extends Component { console.warn('Specified date range end input element in dateRangeEndEl not found'); } else { this.endDateEl = document.querySelector(this.options.dateRangeEndEl) as HTMLInputElement; + if (this.endDateEl.parentElement.querySelector('.datepicker-format') !== undefined) { + this._renderDateInputFormat(this.endDateEl); + } } } @@ -472,6 +479,13 @@ export class Datepicker extends Component { } } + /** + * Renders the date input format + */ + _renderDateInputFormat(el: HTMLInputElement) { + el.parentElement.querySelector('.datepicker-format').innerHTML = this.options.format.toString(); + } + /** * Gets a string representation of the given date. */ From 9c455bb7d7c343553bd44573d33260149c45ca9c Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 21:10:05 +0100 Subject: [PATCH 10/43] enhancement(Datepicker) implemented open by default option #558 --- src/datepicker.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/datepicker.ts b/src/datepicker.ts index 1ebeb70e31..ea1a4c8d00 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -110,6 +110,10 @@ export interface DatepickerOptions extends BaseOptions { * @default false */ showDaysInNextAndPreviousMonths: boolean; + /** + * Specify if the docked datepicker is in open state by default + */ + openByDefault: boolean; /** * Specify a DOM element OR selector for a DOM element to render * the calendar in, by default it will be placed before the input. @@ -208,6 +212,8 @@ const _defaults: DatepickerOptions = { showMonthAfterYear: false, // Render days of the calendar grid that fall in the next or previous month showDaysInNextAndPreviousMonths: false, + // Specify if docked picker is in open state by default + openByDefault: false, // Specify a DOM element to render the calendar in container: null, // Show clear button @@ -475,6 +481,7 @@ export class Datepicker extends Component { } else { //this.containerEl.before(this.el); const appendTo = !this.endDateEl ? this.el : this.endDateEl; + if (!this.options.openByDefault) (this.containerEl as HTMLElement).setAttribute('style', 'display: none; visibility: hidden;'); appendTo.parentElement.after(this.containerEl); } } From 3bcbccf9f2fe62c89aa303ece34d411fda70ddb3 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 22:14:02 +0100 Subject: [PATCH 11/43] enhancement(Datepicker) fix inconsistency in datepicker days rendering --- sass/components/_datepicker.scss | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/sass/components/_datepicker.scss b/sass/components/_datepicker.scss index caf2073eba..cb3eac879b 100644 --- a/sass/components/_datepicker.scss +++ b/sass/components/_datepicker.scss @@ -46,6 +46,11 @@ vertical-align: middle; } + .select-year input, + .select-month input { + background-color: transparent; + } + .select-year input { width: 50px; } @@ -98,6 +103,12 @@ line-height: 47px; font-weight: 500; } + + .daterange & { + .date-text { + font-size: 1.8rem; + } + } } @@ -257,9 +268,9 @@ .datepicker-table, .datepicker-footer { width: 320px; - }*/ + } .datepicker-day-button { line-height: 44px; - } + }*/ } From 695cbe92402332f13b0e641ccb9ce516f75241d1 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 22:32:42 +0100 Subject: [PATCH 12/43] fix failing spec tests --- spec/tests/datepicker/datepickerSpec.js | 20 ++++++++++---------- src/datepicker.ts | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/tests/datepicker/datepickerSpec.js b/spec/tests/datepicker/datepickerSpec.js index 4f2491dbf6..21ca5459d4 100644 --- a/spec/tests/datepicker/datepickerSpec.js +++ b/spec/tests/datepicker/datepickerSpec.js @@ -16,11 +16,11 @@ describe('Datepicker Plugin', () => { it('can have a string format', (done) => { const input = document.querySelector('#datepickerInput'); const today = new Date(); - M.Datepicker.init(input, { format: 'mm/dd/yyyy' }); + M.Datepicker.init(input, { format: 'mm/dd/yyyy', openByDefault: true }); const datepicker = M.Datepicker.getInstance(input); //datepicker.open(); setTimeout(() => { - const day1 = document.querySelector('.datepicker-modal button[data-day="1"]'); + const day1 = document.querySelector('.datepicker-container button[data-day="1"]'); day1.click(); document.querySelector('.datepicker-done').click(); setTimeout(() => { @@ -37,11 +37,11 @@ describe('Datepicker Plugin', () => { const input = document.querySelector('#datepickerInput'); const today = new Date(); const formatFn = `${today.getFullYear() - 100}-${today.getMonth() + 1}-99`; - M.Datepicker.init(input, { format: formatFn }); + M.Datepicker.init(input, { format: formatFn, openByDefault: true }); const datepicker = M.Datepicker.getInstance(input); //datepicker.open(); setTimeout(() => { - const day1 = document.querySelector('.datepicker-modal button[data-day="1"]'); + const day1 = document.querySelector('.datepicker-container button[data-day="1"]'); day1.click(); document.querySelector('.datepicker-done').click(); setTimeout(() => { @@ -55,7 +55,7 @@ describe('Datepicker Plugin', () => { it('can change the calendar modal selected date by input', (done) => { const input = document.querySelector('#datepickerInput'); - M.Datepicker.init(input, { format: 'mm/dd/yyyy' }); + M.Datepicker.init(input, { format: 'mm/dd/yyyy', openByDefault: true }); const today = new Date(); const month = today.getMonth(); const year = today.getFullYear() - 44; @@ -73,7 +73,7 @@ describe('Datepicker Plugin', () => { const selectedDayElem = document.querySelector(`.datepicker-row td[data-day="${day}"]`); expect( selectMonthElem.querySelector('option[selected="selected"]').value === - (month - 1).toString() + (month - 1).toString() ).toEqual( true, `selected month should be ${month}, given value ${selectMonthElem.querySelector('option[selected="selected"]').value}` @@ -94,7 +94,7 @@ describe('Datepicker Plugin', () => { it('should have a date range input field if date range option is enabled', (done) => { const input = document.querySelector('#datepickerInput'); - M.Datepicker.init(input, { isDateRange: true }); + M.Datepicker.init(input, { isDateRange: true, openByDefault: true }); setTimeout(() => { expect(document.querySelector('.datepicker-end-date')).toExist( 'end date input should exist' @@ -105,13 +105,13 @@ describe('Datepicker Plugin', () => { it('should have multiple input fields if multiple select option is enabled and multiple dates are selected', (done) => { const input = document.querySelector('#datepickerInput'); - M.Datepicker.init(input, { isMultipleSelection: true }); + M.Datepicker.init(input, { isMultipleSelection: true, openByDefault: true }); const datepicker = M.Datepicker.getInstance(input); datepicker.open(); setTimeout(() => { - for (let i = 1; i < 4; i++) { + for (let i = 1; i <= 3; i++) { setTimeout(() => { - document.querySelector(`.datepicker-modal button[data-day="${i}"]`).click(); + document.querySelector(`.datepicker-container button[data-day="${i}"]`).click(); }, i * 10); } setTimeout(() => { diff --git a/src/datepicker.ts b/src/datepicker.ts index ea1a4c8d00..bef32e92a3 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -448,7 +448,7 @@ export class Datepicker extends Component { this.el.classList.add('datepicker-date-input'); } - if (this.el.parentElement.querySelector('.datepicker-format') !== undefined) { + if (!this.el.parentElement.querySelector('.datepicker-format') === null) { this._renderDateInputFormat(this.el); } @@ -460,7 +460,7 @@ export class Datepicker extends Component { console.warn('Specified date range end input element in dateRangeEndEl not found'); } else { this.endDateEl = document.querySelector(this.options.dateRangeEndEl) as HTMLInputElement; - if (this.endDateEl.parentElement.querySelector('.datepicker-format') !== undefined) { + if (!this.endDateEl.parentElement.querySelector('.datepicker-format') === null) { this._renderDateInputFormat(this.endDateEl); } } From bd835535d03b316b33c6800152bf6dae9e07ed76 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Fri, 27 Dec 2024 22:35:34 +0100 Subject: [PATCH 13/43] enhancement(Datepicker) add daterange class programmatically for date display font size adjustment when rendering multiple dates --- src/datepicker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datepicker.ts b/src/datepicker.ts index bef32e92a3..4f971f1f86 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -453,6 +453,7 @@ export class Datepicker extends Component { } if (this.options.isDateRange) { + this.containerEl.classList.add('daterange'); if (!this.options.dateRangeEndEl) { this.endDateEl = this.createDateInput(); this.endDateEl.classList.add('datepicker-end-date'); From 29ff96931ef668395dcd2cf007e72b43067fcc4f Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Sat, 28 Dec 2024 13:26:23 +0100 Subject: [PATCH 14/43] enhancement(Datepicker) fixed classmapping calendar day element css selectors #558 --- src/datepicker.ts | 66 ++++++++++++++--------------------------------- 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/src/datepicker.ts b/src/datepicker.ts index 4f971f1f86..a84e567f5e 100644 --- a/src/datepicker.ts +++ b/src/datepicker.ts @@ -643,7 +643,6 @@ export class Datepicker extends Component { * Sets given date as the input value on the given element. */ setInputValue(el, date) { - console.log('setinputvalue'); if (el.type == 'date') { this.setDataDate(el, date); el.value = this.formatDate(date, 'yyyy-mm-dd'); @@ -848,8 +847,21 @@ export class Datepicker extends Component { } renderDay(opts) { - const arr = ['datepicker-day']; - let ariaSelected = 'false'; + const classMap = { + isDisabled: 'is-disabled', + isToday: 'is-today', + isSelected: 'is-selected', + hasEvent: 'has-event', + isInRange: 'is-inrange', + isStartRange: 'is-startrange', + isEndRange: 'is-endrange', + isDateRangeStart: 'is-daterange-start', + isDateRangeEnd: 'is-daterange-end', + isDateRange: 'is-daterange' + }, + ariaSelected = !(['isSelected', 'isDateRange'].filter((prop) => !!(opts.hasOwnProperty(prop) && opts[prop] === true)).length === 0), + arr = ['datepicker-day']; + if (opts.isEmpty) { if (opts.showDaysInNextAndPreviousMonths) { arr.push('is-outside-current-month'); @@ -859,52 +871,12 @@ export class Datepicker extends Component { } } - // @todo wouldn't it be better defining opts class mapping and looping trough opts? - if (opts.isDisabled) { - arr.push('is-disabled'); - } - - if (opts.isToday) { - arr.push('is-today'); - } - - if (opts.isSelected) { - arr.push('is-selected'); - ariaSelected = 'true'; - } - - // @todo should we create this additional css class? - if (opts.hasEvent) { - arr.push('has-event'); - } - - // @todo create additional css class - if (opts.isInRange) { - arr.push('is-inrange'); - ariaSelected = 'true'; - } - - // @todo should we create this additional css class? - if (opts.isStartRange) { - arr.push('is-startrange'); - } - - // @todo should we create this additional css class? - if (opts.isEndRange) { - arr.push('is-endrange'); - } - - if (opts.isDateRangeStart) { - arr.push('is-daterange-start'); - } - - if (opts.isDateRangeEnd) { - arr.push('is-daterange-end'); + for (const [property, className] of Object.entries(classMap)) { + if (opts.hasOwnProperty(property) && opts[property]) { + arr.push(className); + } } - if (opts.isDateRange) { - arr.push('is-daterange'); - } return ( `` + `` + From 2c4e2aece9b1490fd1761d1406a4003979859ed2 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Sat, 28 Dec 2024 13:27:55 +0100 Subject: [PATCH 15/43] enhancement(Datepicker) add implemented day selector classes without styling as todos in scss file --- sass/components/_datepicker.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sass/components/_datepicker.scss b/sass/components/_datepicker.scss index cb3eac879b..c8cadfca8e 100644 --- a/sass/components/_datepicker.scss +++ b/sass/components/_datepicker.scss @@ -157,6 +157,15 @@ } } +// @todo +.datepicker-day.has-event {} +// @todo +.datepicker-day.is-inrange {} +// @todo +.datepicker-day.is-startrange {} +// @todo +.datepicker-day.is-endrange {} + .datepicker-day.is-daterange-start, .datepicker-day.is-daterange-end, .datepicker-day.is-daterange { From 268ae1e540b4645bff4f29037c9353f597e9839c Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Sun, 29 Dec 2024 22:54:32 +0100 Subject: [PATCH 16/43] fix(Timepicker) compatibility with no modal #525 #476 --- src/timepicker.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/timepicker.ts b/src/timepicker.ts index c55d6066bd..e06a74321a 100644 --- a/src/timepicker.ts +++ b/src/timepicker.ts @@ -236,13 +236,13 @@ export class Timepicker extends Component { } _handleInputClick = () => { - this.open(); + this.inputHours.focus(); }; _handleInputKeydown = (e: KeyboardEvent) => { if (Utils.keys.ENTER.includes(e.key)) { e.preventDefault(); - this.open(); + this.inputHours.focus(); } }; @@ -296,9 +296,10 @@ export class Timepicker extends Component { } if (this.currentView === 'hours') { + this.inputMinutes.focus(); this.showView('minutes', this.options.duration / 2); } else { - this.minutesView.classList.add('timepicker-dial-out'); + // this.minutesView.classList.add('timepicker-dial-out'); setTimeout(() => { this.done(); }, this.options.duration / 2); @@ -381,6 +382,8 @@ export class Timepicker extends Component { doneButton.classList.add('timepicker-close'); //doneButton.addEventListener('click', this._finishSelection); confirmationBtnsContainer.appendChild(doneButton); + this._updateTimeFromInput(); + this.showView('hours'); } _clockSetup() { @@ -726,8 +729,7 @@ export class Timepicker extends Component { this.inputHours.value = (this.hours % (this.options.twelveHour ? 12 : 24)).toString(); } - // todo: remove e - done = (e = null, clearValue = null) => { + done = (clearValue = null) => { // Set input value const last = this.el.value; let value = clearValue @@ -744,18 +746,14 @@ export class Timepicker extends Component { new Event('change', { bubbles: true, cancelable: true, composed: true }) ); } - //this.el.focus(); - return e; // just for passing linter, can be removed }; clear = () => { - this.done(null, true); + this.done(true); }; // deprecated open() { - // this._updateTimeFromInput(); - // this.showView('hours'); console.warn( 'Timepicker.close() is deprecated. Remove this method and wrap in modal yourself.' ); @@ -777,7 +775,7 @@ export class Timepicker extends Component {
- +
: From 076c4dfb97f4e6303f7318361d8a458bab395b81 Mon Sep 17 00:00:00 2001 From: Geert Selderslaghs Date: Mon, 30 Dec 2024 00:30:43 +0100 Subject: [PATCH 17/43] refactor(Timepicker) remove modal related selectors #525 --- sass/components/_timepicker.scss | 28 +++++++++++++++------------- src/timepicker.ts | 7 ++----- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/sass/components/_timepicker.scss b/sass/components/_timepicker.scss index adb2b4cc0c..b12582694c 100644 --- a/sass/components/_timepicker.scss +++ b/sass/components/_timepicker.scss @@ -1,12 +1,14 @@ /* Timepicker Containers */ -.timepicker-modal { +/* container removed as of v2.2.1 */ +/* .timepicker-container { max-width: 325px; max-height: none; -} +}*/ -.timepicker-container.modal-content { +.timepicker-container { display: flex; flex-direction: column; + max-width: 325px; padding: 0; } @@ -88,8 +90,8 @@ input[type=text].text-primary { max-width: 3.5rem; } -.timepicker-modal .am-btn, -.timepicker-modal .pm-btn { +.timepicker-container .am-btn, +.timepicker-container .pm-btn { width: 3.6rem; height: 50%; padding-left: calc(var(--btn-padding) / 1.6); @@ -101,12 +103,12 @@ input[type=text].text-primary { border: 1px solid var(--md-sys-color-outline); } -.timepicker-modal .am-btn { +.timepicker-container .am-btn { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } -.timepicker-modal .pm-btn { +.timepicker-container .pm-btn { border-top: 0; border-top-left-radius: 0; border-top-right-radius: 0; @@ -224,12 +226,12 @@ input[type=text].text-primary { /* Media Queries */ @media #{$large-and-up} { - .timepicker-modal { + .timepicker-container { width: auto; max-width: 620px; } - .timepicker-container.modal-content { + .timepicker-container { flex-direction: row; } @@ -254,8 +256,8 @@ input[type=text].text-primary { max-width: unset; } - .timepicker-modal .am-btn, - .timepicker-modal .pm-btn { + .timepicker-container .am-btn, + .timepicker-container .pm-btn { width: auto; padding-left: var(--btn-padding); padding-right: var(--btn-padding); @@ -266,12 +268,12 @@ input[type=text].text-primary { text-align: inherit; } - .timepicker-modal .am-btn { + .timepicker-container .am-btn { border-top-right-radius: 0; border-bottom-right-radius: 0; } - .timepicker-modal .pm-btn { + .timepicker-container .pm-btn { border-left: 0; border-bottom-left-radius: 0; border-top-left-radius: 0; diff --git a/src/timepicker.ts b/src/timepicker.ts index e06a74321a..2449a7eb17 100644 --- a/src/timepicker.ts +++ b/src/timepicker.ts @@ -768,9 +768,7 @@ export class Timepicker extends Component { } static { - Timepicker._template = ` -