@digital-realty/ix-grid 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/demo/columns.js +65 -0
  4. package/demo/contacts.js +292 -0
  5. package/demo/index.html +92 -0
  6. package/dist/IxGrid.d.ts +46 -0
  7. package/dist/IxGrid.js +227 -0
  8. package/dist/IxGrid.js.map +1 -0
  9. package/dist/components/IxGridColumnFilter.d.ts +20 -0
  10. package/dist/components/IxGridColumnFilter.js +133 -0
  11. package/dist/components/IxGridColumnFilter.js.map +1 -0
  12. package/dist/components/IxGridRowFilter.d.ts +37 -0
  13. package/dist/components/IxGridRowFilter.js +317 -0
  14. package/dist/components/IxGridRowFilter.js.map +1 -0
  15. package/dist/components/IxPagination.d.ts +13 -0
  16. package/dist/components/IxPagination.js +93 -0
  17. package/dist/components/IxPagination.js.map +1 -0
  18. package/dist/components/grid-column-filter-styles.d.ts +1 -0
  19. package/dist/components/grid-column-filter-styles.js +71 -0
  20. package/dist/components/grid-column-filter-styles.js.map +1 -0
  21. package/dist/components/grid-row-filter-styles.d.ts +1 -0
  22. package/dist/components/grid-row-filter-styles.js +414 -0
  23. package/dist/components/grid-row-filter-styles.js.map +1 -0
  24. package/dist/components/pagination-styles.d.ts +1 -0
  25. package/dist/components/pagination-styles.js +33 -0
  26. package/dist/components/pagination-styles.js.map +1 -0
  27. package/dist/grid-view-styles.d.ts +1 -0
  28. package/dist/grid-view-styles.js +265 -0
  29. package/dist/grid-view-styles.js.map +1 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +2 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/ix-grid-copy.d.ts +10 -0
  34. package/dist/ix-grid-copy.js +11 -0
  35. package/dist/ix-grid-copy.js.map +1 -0
  36. package/dist/ix-grid.d.ts +1 -0
  37. package/dist/ix-grid.js +3 -0
  38. package/dist/ix-grid.js.map +1 -0
  39. package/dist/test/ix-grid-column-filter.test.d.ts +1 -0
  40. package/dist/test/ix-grid-column-filter.test.js +36 -0
  41. package/dist/test/ix-grid-column-filter.test.js.map +1 -0
  42. package/dist/test/ix-grid-row-filter.test.d.ts +1 -0
  43. package/dist/test/ix-grid-row-filter.test.js +37 -0
  44. package/dist/test/ix-grid-row-filter.test.js.map +1 -0
  45. package/dist/test/ix-grid.test.d.ts +1 -0
  46. package/dist/test/ix-grid.test.js +43 -0
  47. package/dist/test/ix-grid.test.js.map +1 -0
  48. package/dist/test/ix-pagination.test.d.ts +1 -0
  49. package/dist/test/ix-pagination.test.js +28 -0
  50. package/dist/test/ix-pagination.test.js.map +1 -0
  51. package/package.json +95 -0
  52. package/src/IxGrid.ts +229 -0
  53. package/src/components/IxGridColumnFilter.ts +144 -0
  54. package/src/components/IxGridRowFilter.ts +352 -0
  55. package/src/components/IxPagination.ts +85 -0
  56. package/src/components/grid-column-filter-styles.ts +71 -0
  57. package/src/components/grid-row-filter-styles.ts +414 -0
  58. package/src/components/pagination-styles.ts +33 -0
  59. package/src/grid-view-styles.ts +265 -0
  60. package/src/index.ts +1 -0
  61. package/src/ix-grid-copy.ts +10 -0
  62. package/src/ix-grid.ts +3 -0
  63. package/src/test/ix-grid-column-filter.test.ts +47 -0
  64. package/src/test/ix-grid-row-filter.test.ts +48 -0
  65. package/src/test/ix-grid.test.ts +50 -0
  66. package/src/test/ix-pagination.test.ts +33 -0
  67. package/tsconfig.json +22 -0
  68. package/web-dev-server.config.mjs +27 -0
  69. package/web-test-runner.config.mjs +43 -0
package/src/IxGrid.ts ADDED
@@ -0,0 +1,229 @@
1
+ import { html, LitElement, nothing } from 'lit';
2
+ import { property, state } from 'lit/decorators.js';
3
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
+ import '@vaadin/grid';
5
+ import { columnBodyRenderer, columnHeaderRenderer } from '@vaadin/grid/lit.js';
6
+ import '@digital-realty/ix-icon-button/ix-icon-button.js';
7
+ import '@digital-realty/ix-icon/ix-icon.js';
8
+ import './components/IxPagination.js';
9
+ import './components/IxGridColumnFilter.js';
10
+ import './components/IxGridRowFilter.js';
11
+ import type { Filter } from './components/IxGridRowFilter.js';
12
+ import { IxGridViewStyles } from './grid-view-styles.js';
13
+ import { copy } from './ix-grid-copy.js';
14
+
15
+ export interface Row {
16
+ [key: string]: string;
17
+ }
18
+
19
+ export interface Column {
20
+ name: string;
21
+ header: string;
22
+ bodyRenderer: (row: Row) => any;
23
+ width?: string;
24
+ sortable?: boolean;
25
+ filterable?: boolean;
26
+ hidden?: boolean;
27
+ frozenToEnd?: boolean;
28
+ }
29
+
30
+ export class IxGrid extends LitElement {
31
+ static readonly styles = [IxGridViewStyles];
32
+
33
+ @property({ type: Array }) columns: Column[] = [];
34
+
35
+ @property({ type: Array }) rows: Row[] = [];
36
+
37
+ @property({ type: String }) defaultEmptyText = 'No data to display';
38
+
39
+ @property({ type: String }) sortedColumn = '';
40
+
41
+ @property({ type: String }) sortDirection = '';
42
+
43
+ @property({ type: Boolean }) hideHeader = false;
44
+
45
+ @property({ type: Number }) rowLimit: number = 0;
46
+
47
+ @property({ type: Number }) page = 1;
48
+
49
+ @property({ type: Number }) pageSize = 10;
50
+
51
+ @property({ type: Array }) pageSizes: number[] = [5, 10, 25, 100];
52
+
53
+ @property({ type: Number }) recordCount = 0;
54
+
55
+ @property({ type: String }) localStorageID: string | undefined = undefined;
56
+
57
+ @state() private filters: Filter[] = [];
58
+
59
+ @state() isLoading = false;
60
+
61
+ @state() isExpanded = false;
62
+
63
+ get columnNames() {
64
+ return this.columns.map((column: Column) => column.name);
65
+ }
66
+
67
+ private async updatePage() {
68
+ const filters = this.filters.reduce(
69
+ (columnFilters: { [key: string]: string }, { columnField, value }) => ({
70
+ ...columnFilters,
71
+ [columnField]: value,
72
+ }),
73
+ {}
74
+ );
75
+
76
+ const urlParams: { [key: string]: string } = {
77
+ sort: this.sortedColumn,
78
+ order: this.sortDirection,
79
+ page: this.page.toString(),
80
+ size: this.pageSize.toString(),
81
+ ...filters,
82
+ };
83
+
84
+ const url = new URL(window.location.href);
85
+ const searchParams = new URLSearchParams(urlParams);
86
+ url.search = searchParams.toString();
87
+ window.history.replaceState(null, '', url.toString());
88
+
89
+ this.dispatchEvent(
90
+ new CustomEvent('change', {
91
+ detail: {
92
+ columnName: this.sortedColumn,
93
+ sortOrder: this.sortDirection,
94
+ page: this.page,
95
+ pageSize: this.pageSize,
96
+ filters,
97
+ },
98
+ bubbles: true,
99
+ composed: true,
100
+ })
101
+ );
102
+ }
103
+
104
+ handleSort(column: string = '') {
105
+ if (this.sortedColumn !== column) {
106
+ this.sortDirection = 'asc';
107
+ } else {
108
+ this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
109
+ }
110
+ this.sortedColumn = column;
111
+
112
+ this.updatePage();
113
+ }
114
+
115
+ private renderColumnHeader = (column: Column) => html`
116
+ <div
117
+ @click=${() => column.sortable && this.handleSort(column.name)}
118
+ @keyDown=${() => column.sortable && this.handleSort(column.name)}
119
+ class="header"
120
+ >
121
+ ${column.header}
122
+ ${column.sortable
123
+ ? html`<ix-icon title="Sort" class="header-sort-icon"
124
+ >${this.sortDirection === 'desc' &&
125
+ this.sortedColumn === column.name
126
+ ? `arrow_upward`
127
+ : `arrow_downward`}</ix-icon
128
+ >`
129
+ : nothing}
130
+ </div>
131
+ `;
132
+
133
+ private renderHeader = () => html`
134
+ <div class="grid-header">
135
+ <h2>
136
+ ${html`${this.recordCount}
137
+ ${copy.user}${this.recordCount === 1 ? '' : 's'}`}
138
+ </h2>
139
+ <div class="grid-menu">
140
+ <ix-grid-column-filter
141
+ .columns=${this.columns}
142
+ localStorageID=${ifDefined(this.localStorageID)}
143
+ @columnFilter=${(e: CustomEvent) => {
144
+ e.detail.columns.forEach((column: Column, id: number) => {
145
+ this.columns[id].hidden = column.hidden;
146
+ });
147
+ this.updatePage();
148
+ }}
149
+ ></ix-grid-column-filter>
150
+ <ix-icon-button icon="download"></ix-icon-button>
151
+ <ix-grid-row-filter
152
+ .columns=${this.columns}
153
+ @rowFilter=${(e: CustomEvent) => {
154
+ this.filters = e.detail.filters;
155
+ this.updatePage();
156
+ }}
157
+ ></ix-grid-row-filter>
158
+ </div>
159
+ </div>
160
+ `;
161
+
162
+ private renderRowLimitControls = () => {
163
+ if (this.rows.length <= this.rowLimit) return nothing;
164
+
165
+ return html`
166
+ <div class="row-limit">
167
+ <ix-button
168
+ appearance="text"
169
+ @click=${() => {
170
+ this.isExpanded = !this.isExpanded;
171
+ }}
172
+ has-icon
173
+ >
174
+ ${this.isExpanded ? copy.viewLess : copy.viewMore}
175
+ <ix-icon slot="icon">${this.isExpanded ? 'remove' : 'add'}</ix-icon>
176
+ </ix-button>
177
+ </div>
178
+ `;
179
+ };
180
+
181
+ private renderPaginationControls = () => html`
182
+ <ix-pagination
183
+ .page=${this.page}
184
+ .pageSize=${this.pageSize}
185
+ .pageSizes=${this.pageSizes}
186
+ .recordCount=${this.recordCount}
187
+ @updatePagination=${(e: CustomEvent) => {
188
+ this.page = e.detail.page;
189
+ this.pageSize = e.detail.pageSize;
190
+ this.updatePage();
191
+ }}
192
+ ></ix-pagination>
193
+ `;
194
+
195
+ render() {
196
+ return html`
197
+ <div class=${`grid-container ${this.isLoading ? 'loading' : ''}`}>
198
+ ${this.hideHeader ? nothing : this.renderHeader()}
199
+ <vaadin-grid
200
+ .items=${this.rowLimit > 0 && !this.isExpanded
201
+ ? this.rows.slice(0, this.rowLimit)
202
+ : this.rows}
203
+ all-rows-visible
204
+ column-reordering-allowed
205
+ theme="no-border"
206
+ >
207
+ ${this.columns?.map((column: Column) =>
208
+ column.name
209
+ ? html`<vaadin-grid-column
210
+ ${columnHeaderRenderer(
211
+ () => this.renderColumnHeader(column),
212
+ this.sortDirection
213
+ )}
214
+ ${columnBodyRenderer(column.bodyRenderer, [])}
215
+ resizable
216
+ width=${ifDefined(column.width)}
217
+ ?hidden=${column.hidden}
218
+ ?frozen-to-end=${column.frozenToEnd}
219
+ ></vaadin-grid-column>`
220
+ : nothing
221
+ )}
222
+ </vaadin-grid>
223
+ ${this.rowLimit > 0
224
+ ? this.renderRowLimitControls()
225
+ : this.renderPaginationControls()}
226
+ </div>
227
+ `;
228
+ }
229
+ }
@@ -0,0 +1,144 @@
1
+ import { LitElement, html, nothing } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import '@digital-realty/ix-icon-button/ix-icon-button.js';
4
+ import '@digital-realty/ix-select/ix-select.js';
5
+ import '@digital-realty/ix-switch/ix-switch.js';
6
+ import { IxGridViewStyles } from '../grid-view-styles.js';
7
+ import { IxGridColumnFilterStyles } from './grid-column-filter-styles.js';
8
+ import type { Column } from '../IxGrid.js';
9
+
10
+ const triggerKeys = [' ', 'Enter'];
11
+
12
+ @customElement('ix-grid-column-filter')
13
+ export class IxGridColumnFilter extends LitElement {
14
+ static readonly styles = [IxGridViewStyles, IxGridColumnFilterStyles];
15
+
16
+ @property({ type: Array }) columns: Column[] = [];
17
+
18
+ @property({ type: String }) localStorageID: string | undefined = undefined;
19
+
20
+ @state() private isDropdownVisible: boolean = false;
21
+
22
+ @state() disabledColumns: string[] = [];
23
+
24
+ connectedCallback() {
25
+ super.connectedCallback();
26
+ document.addEventListener('click', this.outerInteraction);
27
+ this.initializeLocalStorage();
28
+ }
29
+
30
+ disconnectedCallback() {
31
+ super.disconnectedCallback();
32
+ document.removeEventListener('click', this.outerInteraction);
33
+ }
34
+
35
+ outerInteraction = (e: Event) => {
36
+ if (!e.composedPath().includes(this)) {
37
+ this.isDropdownVisible = false;
38
+ }
39
+ };
40
+
41
+ initializeLocalStorage() {
42
+ if (this.localStorageID) {
43
+ const storedDisabledColumns = localStorage.getItem(this.localStorageID);
44
+
45
+ if (storedDisabledColumns) {
46
+ const disabledColumns = JSON.parse(storedDisabledColumns);
47
+ this.columns.forEach((column, id) => {
48
+ if (disabledColumns.includes(column.name)) {
49
+ this.columns[id].hidden = true;
50
+ }
51
+ });
52
+ }
53
+ this.dispatchUpdate();
54
+ }
55
+ }
56
+
57
+ toggleColumn(id: number) {
58
+ this.columns[id].hidden = !this.columns[id].hidden;
59
+
60
+ this.disabledColumns = this.columns
61
+ .filter((column: Column) => column.hidden)
62
+ .map((column: Column) => column.name);
63
+
64
+ if (this.localStorageID !== undefined) {
65
+ localStorage.setItem(
66
+ this.localStorageID,
67
+ JSON.stringify(this.disabledColumns)
68
+ );
69
+ }
70
+
71
+ this.dispatchUpdate();
72
+ }
73
+
74
+ updateColumn(e: Event, id: number) {
75
+ const input = e.target as HTMLElement;
76
+ const el = input.shadowRoot?.querySelector('input');
77
+
78
+ this.columns[id].hidden = !el?.checked;
79
+
80
+ this.disabledColumns = this.columns
81
+ .filter((column: Column) => column.hidden)
82
+ .map((column: Column) => column.name);
83
+
84
+ if (this.localStorageID !== undefined) {
85
+ localStorage.setItem(
86
+ this.localStorageID,
87
+ JSON.stringify(this.disabledColumns)
88
+ );
89
+ }
90
+
91
+ this.dispatchUpdate();
92
+ }
93
+
94
+ dispatchUpdate(columns = this.columns) {
95
+ this.dispatchEvent(
96
+ new CustomEvent('columnFilter', {
97
+ detail: {
98
+ columns,
99
+ },
100
+ bubbles: true,
101
+ composed: true,
102
+ })
103
+ );
104
+ }
105
+
106
+ render() {
107
+ return html` <div class="grid-menu">
108
+ <span
109
+ @click=${() => {
110
+ this.isDropdownVisible = true;
111
+ }}
112
+ @keyDown=${(e: KeyboardEvent) => {
113
+ if (triggerKeys.includes(e.key)) {
114
+ this.isDropdownVisible = true;
115
+ }
116
+ }}
117
+ class="list list-dropdown"
118
+ >
119
+ <ix-icon-button icon="list"></ix-icon-button>
120
+ ${this.disabledColumns.length > 0
121
+ ? html`<div class="active"></div>`
122
+ : nothing}
123
+ ${this.isDropdownVisible
124
+ ? html` <div class="dropdown-content">
125
+ ${this.columns?.map(
126
+ (col: Column, id: number) => html`<div
127
+ class=${col.hidden ? 'disabled' : ''}
128
+ >
129
+ <label class="ix-switch-label">
130
+ <ix-switch
131
+ .selected=${!col.hidden}
132
+ @change=${(e: Event) => this.updateColumn(e, id)}
133
+ >
134
+ </ix-switch>
135
+ <p>${col.header}</p>
136
+ </label>
137
+ </div>`
138
+ )}
139
+ </div>`
140
+ : nothing}
141
+ </span>
142
+ </div>`;
143
+ }
144
+ }