From e479d7717002fdaacb132ec54c6223e3ce391502 Mon Sep 17 00:00:00 2001 From: Tomi Virkki Date: Wed, 27 Sep 2023 10:29:03 +0300 Subject: [PATCH] refactor: extract vaadin-grid-sorter logic into a reusable mixin (#6542) --- .../grid/src/vaadin-grid-sorter-mixin.d.ts | 44 ++++ packages/grid/src/vaadin-grid-sorter-mixin.js | 198 ++++++++++++++++++ packages/grid/src/vaadin-grid-sorter.d.ts | 35 +--- packages/grid/src/vaadin-grid-sorter.js | 186 +--------------- 4 files changed, 250 insertions(+), 213 deletions(-) create mode 100644 packages/grid/src/vaadin-grid-sorter-mixin.d.ts create mode 100644 packages/grid/src/vaadin-grid-sorter-mixin.js diff --git a/packages/grid/src/vaadin-grid-sorter-mixin.d.ts b/packages/grid/src/vaadin-grid-sorter-mixin.d.ts new file mode 100644 index 0000000000..5a239d2f5b --- /dev/null +++ b/packages/grid/src/vaadin-grid-sorter-mixin.d.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright (c) 2016 - 2023 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import type { Constructor } from '@open-wc/dedupe-mixin'; + +export type GridSorterDirection = 'asc' | 'desc' | null; + +/** + * Fired when the `path` or `direction` property changes. + */ +export type GridSorterChangedEvent = CustomEvent<{ shiftClick: boolean; fromSorterClick: boolean }>; + +/** + * Fired when the `direction` property changes. + */ +export type GridSorterDirectionChangedEvent = CustomEvent<{ value: GridSorterDirection }>; + +export interface GridSorterCustomEventMap { + 'sorter-changed': GridSorterChangedEvent; + + 'direction-changed': GridSorterDirectionChangedEvent; +} + +export interface GridSorterEventMap extends HTMLElementEventMap, GridSorterCustomEventMap {} + +export declare function GridSorterMixin>( + base: T, +): Constructor & T; + +declare class GridSorterMixinClass { + /** + * JS Path of the property in the item used for sorting the data. + */ + path: string | null | undefined; + + /** + * How to sort the data. + * Possible values are `asc` to use an ascending algorithm, `desc` to sort the data in + * descending direction, or `null` for not sorting the data. + */ + direction: GridSorterDirection | null | undefined; +} diff --git a/packages/grid/src/vaadin-grid-sorter-mixin.js b/packages/grid/src/vaadin-grid-sorter-mixin.js new file mode 100644 index 0000000000..ca1f6a6d01 --- /dev/null +++ b/packages/grid/src/vaadin-grid-sorter-mixin.js @@ -0,0 +1,198 @@ +/** + * @license + * Copyright (c) 2016 - 2023 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +const template = document.createElement('template'); + +template.innerHTML = ` + +`; + +document.head.appendChild(template.content); + +registerStyles( + 'vaadin-grid-sorter', + css` + :host { + display: inline-flex; + cursor: pointer; + max-width: 100%; + } + + [part='content'] { + flex: 1 1 auto; + } + + [part='indicators'] { + position: relative; + align-self: center; + flex: none; + } + + [part='order'] { + display: inline; + vertical-align: super; + } + + [part='indicators']::before { + font-family: 'vaadin-grid-sorter-icons'; + display: inline-block; + } + + :host(:not([direction])) [part='indicators']::before { + content: '\\e901'; + } + + :host([direction='asc']) [part='indicators']::before { + content: '\\e900'; + } + + :host([direction='desc']) [part='indicators']::before { + content: '\\e902'; + } + `, + { moduleId: 'vaadin-grid-sorter-styles' }, +); + +/** + * A mixin providing common sorter functionality. + * + * @polymerMixin + */ +export const GridSorterMixin = (superClass) => + class GridSorterMixinClass extends superClass { + static get properties() { + return { + /** + * JS Path of the property in the item used for sorting the data. + */ + path: String, + + /** + * How to sort the data. + * Possible values are `asc` to use an ascending algorithm, `desc` to sort the data in + * descending direction, or `null` for not sorting the data. + * @type {GridSorterDirection | undefined} + */ + direction: { + type: String, + reflectToAttribute: true, + notify: true, + value: null, + }, + + /** + * @type {number | null} + * @protected + */ + _order: { + type: Number, + value: null, + }, + + /** @private */ + _isConnected: { + type: Boolean, + observer: '__isConnectedChanged', + }, + }; + } + + static get observers() { + return ['_pathOrDirectionChanged(path, direction)']; + } + + /** @protected */ + ready() { + super.ready(); + this.addEventListener('click', this._onClick.bind(this)); + } + + /** @protected */ + connectedCallback() { + super.connectedCallback(); + this._isConnected = true; + } + + /** @protected */ + disconnectedCallback() { + super.disconnectedCallback(); + this._isConnected = false; + + if (!this.parentNode && this._grid) { + this._grid.__removeSorters([this]); + } + } + + /** @private */ + _pathOrDirectionChanged() { + this.__dispatchSorterChangedEvenIfPossible(); + } + + /** @private */ + __isConnectedChanged(newValue, oldValue) { + if (oldValue === false) { + return; + } + + this.__dispatchSorterChangedEvenIfPossible(); + } + + /** @private */ + __dispatchSorterChangedEvenIfPossible() { + if (this.path === undefined || this.direction === undefined || !this._isConnected) { + return; + } + + this.dispatchEvent( + new CustomEvent('sorter-changed', { + detail: { shiftClick: Boolean(this._shiftClick), fromSorterClick: Boolean(this._fromSorterClick) }, + bubbles: true, + composed: true, + }), + ); + // Cleaning up as a programatically sorting can be done after some user interaction + this._fromSorterClick = false; + this._shiftClick = false; + } + + /** @private */ + _getDisplayOrder(order) { + return order === null ? '' : order + 1; + } + + /** @private */ + _onClick(e) { + if (e.defaultPrevented) { + // Something else has already handled the click event, do nothing. + return; + } + + const activeElement = this.getRootNode().activeElement; + if (this !== activeElement && this.contains(activeElement)) { + // Some focusable content inside the sorter was clicked, do nothing. + return; + } + + e.preventDefault(); + this._shiftClick = e.shiftKey; + this._fromSorterClick = true; + if (this.direction === 'asc') { + this.direction = 'desc'; + } else if (this.direction === 'desc') { + this.direction = null; + } else { + this.direction = 'asc'; + } + } + }; diff --git a/packages/grid/src/vaadin-grid-sorter.d.ts b/packages/grid/src/vaadin-grid-sorter.d.ts index 97195e5aaf..d0a4e148e6 100644 --- a/packages/grid/src/vaadin-grid-sorter.d.ts +++ b/packages/grid/src/vaadin-grid-sorter.d.ts @@ -5,26 +5,9 @@ */ import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { type GridSorterEventMap, GridSorterMixin } from './vaadin-grid-sorter-mixin.js'; -export type GridSorterDirection = 'asc' | 'desc' | null; - -/** - * Fired when the `path` or `direction` property changes. - */ -export type GridSorterChangedEvent = CustomEvent<{ shiftClick: boolean; fromSorterClick: boolean }>; - -/** - * Fired when the `direction` property changes. - */ -export type GridSorterDirectionChangedEvent = CustomEvent<{ value: GridSorterDirection }>; - -export interface GridSorterCustomEventMap { - 'sorter-changed': GridSorterChangedEvent; - - 'direction-changed': GridSorterDirectionChangedEvent; -} - -export interface GridSorterEventMap extends HTMLElementEventMap, GridSorterCustomEventMap {} +export * from './vaadin-grid-sorter-mixin.js'; /** * `` is a helper element for the `` that provides out-of-the-box UI controls, @@ -65,19 +48,7 @@ export interface GridSorterEventMap extends HTMLElementEventMap, GridSorterCusto * @fires {CustomEvent} direction-changed - Fired when the `direction` property changes. * @fires {CustomEvent} sorter-changed - Fired when the `path` or `direction` property changes. */ -declare class GridSorter extends ThemableMixin(DirMixin(HTMLElement)) { - /** - * JS Path of the property in the item used for sorting the data. - */ - path: string | null | undefined; - - /** - * How to sort the data. - * Possible values are `asc` to use an ascending algorithm, `desc` to sort the data in - * descending direction, or `null` for not sorting the data. - */ - direction: GridSorterDirection | null | undefined; - +declare class GridSorter extends GridSorterMixin(ThemableMixin(DirMixin(HTMLElement))) { addEventListener( type: K, listener: (this: GridSorter, ev: GridSorterEventMap[K]) => void, diff --git a/packages/grid/src/vaadin-grid-sorter.js b/packages/grid/src/vaadin-grid-sorter.js index 00fc8bb7ae..762c2018de 100644 --- a/packages/grid/src/vaadin-grid-sorter.js +++ b/packages/grid/src/vaadin-grid-sorter.js @@ -7,21 +7,7 @@ import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; - -const template = document.createElement('template'); - -template.innerHTML = ` - -`; - -document.head.appendChild(template.content); +import { GridSorterMixin } from './vaadin-grid-sorter-mixin.js'; /** * `` is a helper element for the `` that provides out-of-the-box UI controls, @@ -64,50 +50,13 @@ document.head.appendChild(template.content); * * @customElement * @extends HTMLElement + * @mixes GridSorterMixin + * @mixes ThemableMixin + * @mixes DirMixin */ -class GridSorter extends ThemableMixin(DirMixin(PolymerElement)) { +class GridSorter extends GridSorterMixin(ThemableMixin(DirMixin(PolymerElement))) { static get template() { return html` - -
@@ -120,131 +69,6 @@ class GridSorter extends ThemableMixin(DirMixin(PolymerElement)) { static get is() { return 'vaadin-grid-sorter'; } - - static get properties() { - return { - /** - * JS Path of the property in the item used for sorting the data. - */ - path: String, - - /** - * How to sort the data. - * Possible values are `asc` to use an ascending algorithm, `desc` to sort the data in - * descending direction, or `null` for not sorting the data. - * @type {GridSorterDirection | undefined} - */ - direction: { - type: String, - reflectToAttribute: true, - notify: true, - value: null, - }, - - /** - * @type {number | null} - * @protected - */ - _order: { - type: Number, - value: null, - }, - - /** @private */ - _isConnected: { - type: Boolean, - observer: '__isConnectedChanged', - }, - }; - } - - static get observers() { - return ['_pathOrDirectionChanged(path, direction)']; - } - - /** @protected */ - ready() { - super.ready(); - this.addEventListener('click', this._onClick.bind(this)); - } - - /** @protected */ - connectedCallback() { - super.connectedCallback(); - this._isConnected = true; - } - - /** @protected */ - disconnectedCallback() { - super.disconnectedCallback(); - this._isConnected = false; - - if (!this.parentNode && this._grid) { - this._grid.__removeSorters([this]); - } - } - - /** @private */ - _pathOrDirectionChanged() { - this.__dispatchSorterChangedEvenIfPossible(); - } - - /** @private */ - __isConnectedChanged(newValue, oldValue) { - if (oldValue === false) { - return; - } - - this.__dispatchSorterChangedEvenIfPossible(); - } - - /** @private */ - __dispatchSorterChangedEvenIfPossible() { - if (this.path === undefined || this.direction === undefined || !this._isConnected) { - return; - } - - this.dispatchEvent( - new CustomEvent('sorter-changed', { - detail: { shiftClick: Boolean(this._shiftClick), fromSorterClick: Boolean(this._fromSorterClick) }, - bubbles: true, - composed: true, - }), - ); - // Cleaning up as a programatically sorting can be done after some user interaction - this._fromSorterClick = false; - this._shiftClick = false; - } - - /** @private */ - _getDisplayOrder(order) { - return order === null ? '' : order + 1; - } - - /** @private */ - _onClick(e) { - if (e.defaultPrevented) { - // Something else has already handled the click event, do nothing. - return; - } - - const activeElement = this.getRootNode().activeElement; - if (this !== activeElement && this.contains(activeElement)) { - // Some focusable content inside the sorter was clicked, do nothing. - return; - } - - e.preventDefault(); - this._shiftClick = e.shiftKey; - this._fromSorterClick = true; - if (this.direction === 'asc') { - this.direction = 'desc'; - } else if (this.direction === 'desc') { - this.direction = null; - } else { - this.direction = 'asc'; - } - } } defineCustomElement(GridSorter);