Skip to content

Commit

Permalink
refactor: extract vaadin-grid-sorter logic into a reusable mixin (#6542)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomivirkki authored Sep 27, 2023
1 parent b32adb8 commit e479d77
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 213 deletions.
44 changes: 44 additions & 0 deletions packages/grid/src/vaadin-grid-sorter-mixin.d.ts
Original file line number Diff line number Diff line change
@@ -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<T extends Constructor<HTMLElement>>(
base: T,
): Constructor<GridSorterMixinClass> & 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;
}
198 changes: 198 additions & 0 deletions packages/grid/src/vaadin-grid-sorter-mixin.js
Original file line number Diff line number Diff line change
@@ -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 = `
<style>
@font-face {
font-family: 'vaadin-grid-sorter-icons';
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAQwAA0AAAAABuwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEFAAAABkAAAAcfep+mUdERUYAAAP4AAAAHAAAAB4AJwAOT1MvMgAAAZgAAAA/AAAAYA8TBPpjbWFwAAAB7AAAAFUAAAFeF1fZ4mdhc3AAAAPwAAAACAAAAAgAAAAQZ2x5ZgAAAlgAAABcAAAAnMvguMloZWFkAAABMAAAAC8AAAA2C5Ap72hoZWEAAAFgAAAAHQAAACQGbQPHaG10eAAAAdgAAAAUAAAAHAoAAABsb2NhAAACRAAAABIAAAASAIwAYG1heHAAAAGAAAAAFgAAACAACwAKbmFtZQAAArQAAAECAAACZxWCgKhwb3N0AAADuAAAADUAAABZCrApUXicY2BkYGAA4rDECVrx/DZfGbhZGEDgyqNPOxH0/wNMq5kPALkcDEwgUQBWRA0dAHicY2BkYGA+8P8AAwMLAwgwrWZgZEAFbABY4QM8AAAAeJxjYGRgYOAAQiYGEICQSAAAAi8AFgAAeJxjYGY6yziBgZWBgWkm0xkGBoZ+CM34msGYkZMBFTAKoAkwODAwvmRiPvD/AIMDMxCD1CDJKjAwAgBktQsXAHicY2GAAMZQCM0EwqshbAALxAEKeJxjYGBgZoBgGQZGBhCIAPIYwXwWBhsgzcXAwcAEhIwMCi+Z/v/9/x+sSuElA4T9/4k4K1gHFwMMMILMY2QDYmaoABOQYGJABUA7WBiGNwAAJd4NIQAAAAAAAAAACAAIABAAGAAmAEAATgAAeJyNjLENgDAMBP9tIURJwQCMQccSZgk2i5fIYBDAidJjycXr7x5EPwE2wY8si7jmyBNXGo/bNBerxJNrpxhbO3/fEFpx8ZICpV+ghxJ74fAMe+h7Ox14AbrsHB14nK2QQWrDMBRER4mTkhQK3ZRQKOgCNk7oGQqhhEIX2WSlWEI1BAlkJ5CDdNsj5Ey9Rncdi38ES+jzNJo/HwTgATcoDEthhY3wBHc4CE+pfwsX5F/hGe7Vo/AcK/UhvMSz+mGXKhZU6pww8ISz3oWn1BvhgnwTnuEJf8Jz1OpFeIlX9YULDLdFi4ASHolkSR0iuYdjLak1vAequBhj21D61Nqyi6l3qWybGPjySbPHGScGJl6dP58MYcQRI0bts7mjebBqrFENH7t3qWtj0OuqHnXcW7b0HOTZFnKryRGW2hFX1m0O2vEM3opNMfTau+CS6Z3Vx6veNnEXY6jwDxhsc2gAAHicY2BiwA84GBgYmRiYGJkZmBlZGFkZ2djScyoLMgzZS/MyDQwMwLSrpYEBlIbxjQDrzgsuAAAAAAEAAf//AA94nGNgZGBg4AFiMSBmYmAEQnYgZgHzGAAD6wA2eJxjYGBgZACCKyoz1cD0o087YTQATOcIewAAAA==) format('woff');
font-weight: normal;
font-style: normal;
}
</style>
`;

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';
}
}
};
35 changes: 3 additions & 32 deletions packages/grid/src/vaadin-grid-sorter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
* `<vaadin-grid-sorter>` is a helper element for the `<vaadin-grid>` that provides out-of-the-box UI controls,
Expand Down Expand Up @@ -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<K extends keyof GridSorterEventMap>(
type: K,
listener: (this: GridSorter, ev: GridSorterEventMap[K]) => void,
Expand Down
Loading

0 comments on commit e479d77

Please sign in to comment.