@spectric/ui 0.0.10 → 0.0.12
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.
- package/dist/classes/DisposibleElement.d.ts +4 -2
- package/dist/components/Bitdisplay.d.ts +4 -4
- package/dist/components/Button.d.ts +1 -1
- package/dist/components/Header.d.ts +1 -1
- package/dist/components/input.d.ts +13 -12
- package/dist/components/pagination/pagination.d.ts +5 -5
- package/dist/components/splitview/splitview.d.ts +5 -5
- package/dist/components/table/body.d.ts +2 -1
- package/dist/components/table/cell.d.ts +2 -0
- package/dist/components/table/header.d.ts +2 -1
- package/dist/components/table/sorting.d.ts +5 -0
- package/dist/components/table/table.d.ts +51 -14
- package/dist/components/table/virtualBody.d.ts +49 -0
- package/dist/components/tooltip/tooltip.d.ts +17 -12
- package/dist/custom-elements.json +50 -16
- package/dist/index.es.js +2099 -1867
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +128 -93
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/utils/once.d.ts +1 -0
- package/dist/utils/spread.d.ts +1 -1
- package/package.json +1 -1
- package/src/classes/DisposibleElement.ts +15 -9
- package/src/components/Bitdisplay.ts +7 -7
- package/src/components/Button.ts +1 -1
- package/src/components/Header.ts +1 -1
- package/src/components/input.ts +18 -15
- package/src/components/pagination/pagination.ts +7 -7
- package/src/components/query_bar/QueryBar.ts +26 -21
- package/src/components/splitview/splitview.ts +5 -5
- package/src/components/table/body.ts +13 -5
- package/src/components/table/cell.ts +9 -7
- package/src/components/table/header.ts +28 -4
- package/src/components/table/sorting.ts +34 -0
- package/src/components/table/table.css +60 -4
- package/src/components/table/table.ts +149 -33
- package/src/components/table/virtualBody.css +13 -0
- package/src/components/table/virtualBody.ts +127 -0
- package/src/components/tooltip/tooltip.ts +38 -32
- package/src/docs/HTML-Vue-Python Integration.mdx +3 -3
- package/src/docs/html-include.png +0 -0
- package/src/docs/vue-example.png +0 -0
- package/src/docs/vue-include.png +0 -0
- package/src/stories/BitDisplay.stories.ts +2 -0
- package/src/stories/fixtures/ExampleContent.ts +15 -8
- package/src/stories/fixtures/data.ts +21 -10
- package/src/stories/pagination.stories.ts +2 -1
- package/src/stories/table.stories.ts +27 -5
- package/src/utils/once.ts +12 -0
- package/src/utils/spread.ts +3 -3
|
@@ -1,24 +1,61 @@
|
|
|
1
1
|
spectric-table{
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: column;
|
|
4
|
+
overflow: hidden;
|
|
5
|
+
}
|
|
6
|
+
spectric-table .table-wrapper{
|
|
7
|
+
overflow: auto;
|
|
8
|
+
flex-grow: 1;
|
|
9
|
+
position: relative;
|
|
4
10
|
}
|
|
5
11
|
spectric-table tr{
|
|
6
12
|
text-align: center;
|
|
7
13
|
}
|
|
8
14
|
|
|
9
|
-
spectric-table
|
|
15
|
+
spectric-table tr.odd{
|
|
16
|
+
background-color: color-mix(in srgb, var(--spectric-primary, #1ea7fd), transparent 90%);
|
|
17
|
+
}
|
|
18
|
+
spectric-table tr:hover{
|
|
10
19
|
background-color: color-mix(in srgb, var(--spectric-primary, #1ea7fd), transparent 70%)
|
|
11
20
|
}
|
|
12
21
|
spectric-table-header{
|
|
13
22
|
display: table-header-group;
|
|
14
23
|
font-weight: bold;
|
|
24
|
+
position: sticky;
|
|
25
|
+
top: 0px;
|
|
26
|
+
left: 0px;
|
|
27
|
+
z-index: 1;
|
|
28
|
+
background: var(--spectric-background, #ffffff);
|
|
29
|
+
}
|
|
30
|
+
spectric-table-header td {
|
|
31
|
+
vertical-align: middle;
|
|
32
|
+
}
|
|
33
|
+
spectric-table tr {
|
|
34
|
+
line-height: var(--rowHeight);
|
|
35
|
+
}
|
|
36
|
+
spectric-table td{
|
|
37
|
+
height: var(--rowHeight);
|
|
38
|
+
}
|
|
39
|
+
spectric-table-header .header-contents {
|
|
40
|
+
position: relative;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
spectric-table-header .header-contents .sort-direction {
|
|
44
|
+
position: absolute;
|
|
45
|
+
right: 0;
|
|
46
|
+
}
|
|
47
|
+
spectric-table-header .header-contents.sortable {
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
spectric-table-header .header-contents.sortable:hover .sort-direction.none::before
|
|
51
|
+
{
|
|
52
|
+
content: "\2B81";
|
|
15
53
|
}
|
|
16
54
|
spectric-table div[role="table"]{
|
|
17
55
|
display: table;
|
|
56
|
+
min-width: 100%;
|
|
18
57
|
}
|
|
19
|
-
|
|
20
|
-
display: table-row-group;
|
|
21
|
-
}
|
|
58
|
+
|
|
22
59
|
spectric-table-cell{
|
|
23
60
|
display: contents;
|
|
24
61
|
vertical-align: middle;
|
|
@@ -43,4 +80,23 @@ spectric-table-cell .table-cell-actions{
|
|
|
43
80
|
}
|
|
44
81
|
spectric-table-cell td:hover .table-cell-actions{
|
|
45
82
|
visibility: unset;
|
|
83
|
+
}
|
|
84
|
+
spectric-table .table-checkbox-single spectric-button{
|
|
85
|
+
--button-border-radius: 50%;
|
|
86
|
+
}
|
|
87
|
+
spectric-input.table-checkbox-single[checked] spectric-button{
|
|
88
|
+
--text-on-color: transparent;
|
|
89
|
+
border-radius: 50%;
|
|
90
|
+
position: relative;
|
|
91
|
+
}
|
|
92
|
+
spectric-input.table-checkbox-single[checked] spectric-button::before {
|
|
93
|
+
position: absolute;
|
|
94
|
+
content: " ";
|
|
95
|
+
height: 50%;
|
|
96
|
+
width: 50%;
|
|
97
|
+
left: 25%;
|
|
98
|
+
top: 25%;
|
|
99
|
+
border-radius: 50%;
|
|
100
|
+
z-index: 1;
|
|
101
|
+
box-shadow: 0px 0px 0px 4px var(--input-color);
|
|
46
102
|
}
|
|
@@ -1,34 +1,68 @@
|
|
|
1
|
-
import { html, LitElement, TemplateResult } from 'lit';
|
|
1
|
+
import { html, LitElement, PropertyValues, render, TemplateResult } from 'lit';
|
|
2
2
|
import "../pagination";
|
|
3
3
|
import "./header"
|
|
4
4
|
import "./body"
|
|
5
5
|
import { customElement, property, state, } from 'lit/decorators.js';
|
|
6
6
|
import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
|
|
7
|
+
import "./virtualBody"
|
|
7
8
|
import "./table.css"
|
|
8
9
|
export const TableElementTag = "spectric-table"
|
|
9
10
|
import { spreadProps } from '../../utils/spread';
|
|
10
11
|
import { PaginationChangeProps, PaginationProps } from '../pagination';
|
|
11
12
|
import { FilterEvent } from './cell';
|
|
13
|
+
import { createSortChain } from './sorting';
|
|
14
|
+
|
|
12
15
|
export type { TableProps, TableEvents }
|
|
13
16
|
|
|
14
17
|
export type DomRenderable = HTMLElement | TemplateResult | string | number | null
|
|
18
|
+
export enum TableSelectOptions {
|
|
19
|
+
multi = "multi",
|
|
20
|
+
single = "single",
|
|
21
|
+
none = "none"
|
|
22
|
+
}
|
|
15
23
|
|
|
24
|
+
export enum TableSortOption {
|
|
25
|
+
multi = "multi",
|
|
26
|
+
single = "single",
|
|
27
|
+
}
|
|
28
|
+
export type TableSortOptionTypes = `${TableSortOption}`
|
|
29
|
+
export enum TableSortDirection {
|
|
30
|
+
ascending = "ascending",
|
|
31
|
+
decending = "decending",
|
|
32
|
+
none = "none"
|
|
33
|
+
}
|
|
34
|
+
export type TableSortDirectionTypes = `${TableSortDirection}`
|
|
16
35
|
export type ColumnSettings<T> = {
|
|
17
36
|
width?: number
|
|
18
37
|
whiteSpace?: "nowrap";
|
|
19
38
|
hidden?: boolean
|
|
20
39
|
sortable?: boolean
|
|
40
|
+
sortDirection?: TableSortDirectionTypes
|
|
21
41
|
filterable?: boolean
|
|
22
42
|
title?: DomRenderable
|
|
43
|
+
/**
|
|
44
|
+
* Key to used for getting data from an object for a cell
|
|
45
|
+
*/
|
|
23
46
|
key?: string
|
|
24
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Render function to render a table cell for displaying custom html
|
|
49
|
+
*/
|
|
50
|
+
render?: (row: T, index: number, table: TableElement<T>) => DomRenderable
|
|
51
|
+
/**
|
|
52
|
+
* Custom comparator function for sorting
|
|
53
|
+
*/
|
|
54
|
+
compareFn?: ((a: T, b: T) => number) | undefined
|
|
25
55
|
}
|
|
26
|
-
type
|
|
27
|
-
interface
|
|
56
|
+
export type TableSelectOptionsTypes = `${TableSelectOptions}`
|
|
57
|
+
export interface TableDataOptions<T> {
|
|
28
58
|
pagination?: PaginationProps
|
|
29
59
|
columns: ColumnSettings<T>[]
|
|
60
|
+
}
|
|
61
|
+
interface TableProps<T> extends TableDataOptions<T> {
|
|
30
62
|
data: T[]
|
|
31
|
-
select
|
|
63
|
+
select: TableSelectOptionsTypes
|
|
64
|
+
sort?: TableSortOptionTypes
|
|
65
|
+
rowHeight?: number
|
|
32
66
|
}
|
|
33
67
|
|
|
34
68
|
type DomEvent<T> = Event & {
|
|
@@ -37,30 +71,90 @@ type DomEvent<T> = Event & {
|
|
|
37
71
|
/**
|
|
38
72
|
* React example
|
|
39
73
|
* <iframe width="100%" height="400px" src="https://stackblitz.com/edit/react-ts-2ue7azag?ctl=1&embed=1&file=App.tsx&hideExplorer=1&hideNavigation=1"/>
|
|
40
|
-
*
|
|
74
|
+
*
|
|
41
75
|
*/
|
|
42
76
|
@customElement(TableElementTag)
|
|
43
77
|
export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
44
78
|
@property({ type: Array, attribute: false })
|
|
45
79
|
data: T[] = [];
|
|
46
80
|
@property({ type: Object, attribute: false })
|
|
47
|
-
pagination?: PaginationProps
|
|
81
|
+
pagination?: PaginationProps;
|
|
48
82
|
@property({ attribute: false })
|
|
49
83
|
columns: ColumnSettings<T>[] = [];
|
|
50
|
-
@property({ type: String, reflect:
|
|
51
|
-
select
|
|
84
|
+
@property({ type: String, reflect: true })
|
|
85
|
+
select: TableSelectOptionsTypes = TableSelectOptions.none;
|
|
86
|
+
@property({ type: String, reflect: true })
|
|
87
|
+
sort: TableSortOptionTypes = TableSortOption.single;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Needed for virtualization
|
|
91
|
+
*/
|
|
92
|
+
@property({ type: Number, reflect: true })
|
|
93
|
+
rowHeight: number = 25;
|
|
94
|
+
|
|
95
|
+
static getDefaultDataSorterAndPaginatior<T>(data: T[]) {
|
|
96
|
+
return (props: TableDataOptions<T>) => {
|
|
97
|
+
let sorts = props.columns.filter(column => column.sortable && column.sortDirection && column.sortDirection !== TableSortDirection.none)
|
|
98
|
+
let rows = [...data] // Need to copy the array to prevent mutating the ordering of the original data
|
|
99
|
+
if (sorts.length) {
|
|
100
|
+
let sortChain = createSortChain(sorts)
|
|
101
|
+
rows.sort((a, b) => {
|
|
102
|
+
for (let sort of sortChain) {
|
|
103
|
+
let result = sort(a, b)
|
|
104
|
+
if (result) {
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return 0
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!props.pagination) {
|
|
113
|
+
return rows
|
|
114
|
+
}
|
|
115
|
+
let { page, pageSize } = props.pagination
|
|
116
|
+
if (!page || !pageSize) {
|
|
117
|
+
return rows
|
|
118
|
+
}
|
|
119
|
+
return !props.pagination ? rows : rows.slice((page - 1) * pageSize, (page) * pageSize)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
52
123
|
private _handlePaginationChange = (e: CustomEvent<PaginationChangeProps>) => {
|
|
53
124
|
e.preventDefault()
|
|
54
125
|
e.stopPropagation()
|
|
55
126
|
if (this.pagination) {
|
|
56
|
-
this.pagination.
|
|
57
|
-
|
|
127
|
+
let pagination = { ...this.pagination, ...e.detail }
|
|
128
|
+
|
|
129
|
+
let { totalItems, page, pageSize } = pagination
|
|
130
|
+
if (totalItems && page && pageSize && ((page - 1) * pageSize) > totalItems) {
|
|
131
|
+
pagination.page = 1
|
|
132
|
+
}
|
|
133
|
+
this.pagination = pagination
|
|
58
134
|
}
|
|
59
135
|
this._emitChange()
|
|
60
136
|
};
|
|
137
|
+
private _handleSortChange = (e: CustomEvent<ColumnSettings<T>>) => {
|
|
138
|
+
let columnSetting = e.detail
|
|
139
|
+
let column = this.columns.find(col => col.key == columnSetting.key)
|
|
140
|
+
if (!column) {
|
|
141
|
+
console.warn("Unable to find sort column")
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if (this.sort == TableSortOption.single) {
|
|
145
|
+
//Single column sort so we reset the sort direction for all columns
|
|
146
|
+
this.columns.forEach(col => {
|
|
147
|
+
col.sortDirection = TableSortDirection.none
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
column.sortDirection = columnSetting.sortDirection;
|
|
151
|
+
this.columns = [...this.columns]
|
|
152
|
+
this._emitChange()
|
|
153
|
+
}
|
|
154
|
+
|
|
61
155
|
private _emitChange = () => {
|
|
62
|
-
let { pagination } = this
|
|
63
|
-
this.dispatchEvent(new CustomEvent<
|
|
156
|
+
let { pagination, columns } = this
|
|
157
|
+
this.dispatchEvent(new CustomEvent<TableDataOptions<T>>("change", { detail: { pagination, columns } }))
|
|
64
158
|
}
|
|
65
159
|
//@ts-ignore
|
|
66
160
|
private __DO_NOT_USE_filter = () => {
|
|
@@ -68,7 +162,7 @@ export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
|
68
162
|
this.dispatchEvent(new CustomEvent<FilterEvent<T>>("filter"))
|
|
69
163
|
}
|
|
70
164
|
@state()
|
|
71
|
-
selected: T[] = [];
|
|
165
|
+
private selected: T[] = [];
|
|
72
166
|
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
73
167
|
return this
|
|
74
168
|
}
|
|
@@ -79,38 +173,60 @@ export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
|
79
173
|
} else {
|
|
80
174
|
this.selected = []
|
|
81
175
|
}
|
|
82
|
-
this.dispatchEvent(new CustomEvent("
|
|
176
|
+
this.dispatchEvent(new CustomEvent("selected", { detail: this.selected }))
|
|
177
|
+
}
|
|
178
|
+
protected update(changedProperties: PropertyValues): void {
|
|
179
|
+
if (changedProperties.has("select")) {
|
|
180
|
+
if (this.select === "single" && this.selected.length > 1) {
|
|
181
|
+
this.selected = [this.selected[0]]
|
|
182
|
+
this.dispatchEvent(new CustomEvent("selected", { detail: [...this.selected] }))
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
super.update(changedProperties)
|
|
83
186
|
}
|
|
84
187
|
protected render(): unknown {
|
|
85
188
|
let columns = this.columns.filter(column => !column.hidden)
|
|
86
|
-
if (this.select) {
|
|
189
|
+
if (this.select !== TableSelectOptions.none) {
|
|
87
190
|
columns.unshift({
|
|
88
191
|
title: this.select === "multi" ? html`<spectric-input variant="checkbox" @change=${this._handleSelectAllChange} .helperText=${"Select All"}></spectric-input>` : null,
|
|
89
192
|
render: (row) => {
|
|
90
|
-
|
|
193
|
+
let container = document.createElement("div")
|
|
194
|
+
let checked = this.selected.includes(row)
|
|
195
|
+
let template = html`<spectric-input variant="checkbox" class="table-checkbox-${this.select}" ${spreadProps({ checked })} @change=${(e: DomEvent<HTMLInputElement>) => {
|
|
91
196
|
e.stopPropagation()
|
|
92
|
-
|
|
93
|
-
|
|
197
|
+
let index = this.selected.findIndex(value => value === row)
|
|
198
|
+
if (e.target.checked && index !== -1) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
else if (!e.target.checked && index === -1) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
else if (!e.target.checked && index !== -1) {
|
|
205
|
+
this.selected.splice(index, 1)
|
|
94
206
|
}
|
|
95
207
|
if (e.target.checked) {
|
|
96
|
-
this.
|
|
97
|
-
|
|
98
|
-
} else {
|
|
99
|
-
let index = this.selected.findIndex(value => value === row)
|
|
100
|
-
if (index !== -1) {
|
|
101
|
-
this.selected.splice(index, 1)
|
|
102
|
-
this.dispatchEvent(new CustomEvent("select", { detail: this.selected }))
|
|
208
|
+
if (this.select === "single") {
|
|
209
|
+
this.selected = []
|
|
103
210
|
}
|
|
211
|
+
this.selected.push(row)
|
|
104
212
|
}
|
|
213
|
+
this.dispatchEvent(new CustomEvent("selected", { detail: [...this.selected] }))
|
|
105
214
|
}}></spectric-input>`
|
|
215
|
+
render(template, container)
|
|
216
|
+
return template
|
|
106
217
|
}
|
|
107
218
|
})
|
|
108
219
|
}
|
|
109
|
-
|
|
220
|
+
const tdBorderAndPadding = 4;
|
|
110
221
|
return html`
|
|
111
|
-
<div
|
|
112
|
-
<
|
|
113
|
-
|
|
222
|
+
<div class="table-wrapper" style="--rowHeight:${this.rowHeight - tdBorderAndPadding}px">
|
|
223
|
+
<div role="table">
|
|
224
|
+
<spectric-table-header .columns=${columns} @sortChange=${this._handleSortChange}></spectric-table-header>
|
|
225
|
+
${this.rowHeight >= 25 && this.data.length >= 100 ?
|
|
226
|
+
html`<spectric-table-virtual-body .columns=${columns} .data=${this.data} .table=${this} .rowHeight=${this.rowHeight}></spectric-table-virtual-body>` :
|
|
227
|
+
html`<spectric-table-body .columns=${columns} .data=${this.data} .table=${this}></spectric-table-body>`
|
|
228
|
+
}
|
|
229
|
+
</div>
|
|
114
230
|
</div>
|
|
115
231
|
${this.pagination ? html`<spectric-pagination ${spreadProps(this.pagination)} @change=${this._handlePaginationChange}></spectric-pagination>` : null}
|
|
116
232
|
`;
|
|
@@ -118,7 +234,7 @@ export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
|
118
234
|
}
|
|
119
235
|
|
|
120
236
|
interface TableEvents {
|
|
121
|
-
'change': (event: CustomEvent<
|
|
237
|
+
'change': (event: CustomEvent<TableDataOptions<any>>) => void;
|
|
122
238
|
'filter': (event: CustomEvent<FilterEvent<any>>) => void;
|
|
123
239
|
}
|
|
124
240
|
|
|
@@ -130,7 +246,7 @@ declare global {
|
|
|
130
246
|
namespace JSX {
|
|
131
247
|
interface IntrinsicElements {
|
|
132
248
|
/**
|
|
133
|
-
* @see {@link
|
|
249
|
+
* @see {@link TableElement}
|
|
134
250
|
*/
|
|
135
251
|
[TableElementTag]: ReactElementWithPropsAndEvents<TableElement<any>, TableProps<any>, TableEvents>;
|
|
136
252
|
}
|
|
@@ -139,7 +255,7 @@ declare global {
|
|
|
139
255
|
namespace JSX {
|
|
140
256
|
interface IntrinsicElements {
|
|
141
257
|
/**
|
|
142
|
-
* @see {@link
|
|
258
|
+
* @see {@link TableElement}
|
|
143
259
|
*/
|
|
144
260
|
[TableElementTag]: ReactElementWithPropsAndEvents<TableElement<any>, TableProps<any>, TableEvents>
|
|
145
261
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { customElement, property, } from 'lit/decorators.js';
|
|
3
|
+
import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
|
|
4
|
+
export const TableBodyElementTag = "spectric-table-virtual-body"
|
|
5
|
+
import "./cell"
|
|
6
|
+
import { ColumnSettings, TableElement } from './table';
|
|
7
|
+
import { repeat } from 'lit/directives/repeat.js';
|
|
8
|
+
import { DisposableElement } from '../../classes/DisposibleElement';
|
|
9
|
+
import "./virtualBody.css"
|
|
10
|
+
interface BodyProps<T> {
|
|
11
|
+
columns: ColumnSettings<T>[]
|
|
12
|
+
data: T[]
|
|
13
|
+
rowHeight: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Table Body Element
|
|
19
|
+
*/
|
|
20
|
+
@customElement(TableBodyElementTag)
|
|
21
|
+
export class TableVirtualBodyElement<T> extends DisposableElement implements BodyProps<T> {
|
|
22
|
+
@property({ type: Array, attribute: false })
|
|
23
|
+
data: T[] = [];
|
|
24
|
+
@property({ type: Array, attribute: false })
|
|
25
|
+
columns: ColumnSettings<T>[] = [];
|
|
26
|
+
@property({ type: Number, attribute: false })
|
|
27
|
+
rowHeight: number = 30;
|
|
28
|
+
|
|
29
|
+
@property({ type: Number, state: true })
|
|
30
|
+
startIndex: number = 0
|
|
31
|
+
|
|
32
|
+
table!: TableElement<T>
|
|
33
|
+
constructor() {
|
|
34
|
+
super()
|
|
35
|
+
this.addDisposableListener(() => this.table.querySelector(".table-wrapper")!, "scroll", () => {
|
|
36
|
+
const scrollTop = this.table.querySelector(".table-wrapper")!.scrollTop;
|
|
37
|
+
requestAnimationFrame(() => {
|
|
38
|
+
this.startIndex = Math.floor(scrollTop / this.rowHeight);
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
protected firstUpdated(): void {
|
|
43
|
+
this.columns.forEach(col => {
|
|
44
|
+
if (!col.width) {
|
|
45
|
+
console.warn("When using virtual scrolling it is recomended to set the width of the columns to prevent columns widths from jittering during scroll")
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
50
|
+
return this
|
|
51
|
+
}
|
|
52
|
+
protected render(): unknown {
|
|
53
|
+
let totalRows = this.data.length
|
|
54
|
+
let buffer = 10 // Have a buffer of rows to prevent column jitter
|
|
55
|
+
let headerHeight = this.table.querySelector("spectric-table-header")!.clientHeight
|
|
56
|
+
let viewport = this.table.querySelector(".table-wrapper")!
|
|
57
|
+
let startIndex = Math.max(this.startIndex - buffer, 0) //pad the front with 10 rows
|
|
58
|
+
const rowsThatFit = Math.ceil((viewport.clientHeight - headerHeight) / this.rowHeight);
|
|
59
|
+
const endIndex = Math.min(startIndex + rowsThatFit + buffer, totalRows);
|
|
60
|
+
const visibleRows = endIndex - startIndex
|
|
61
|
+
let totalHeight = totalRows * this.rowHeight
|
|
62
|
+
let beforeHeight = (startIndex * this.rowHeight) + (viewport.scrollTop - (startIndex * this.rowHeight))
|
|
63
|
+
if (endIndex === totalRows) {
|
|
64
|
+
beforeHeight = (startIndex * this.rowHeight)
|
|
65
|
+
}
|
|
66
|
+
let visibleHeight = (visibleRows * this.rowHeight)
|
|
67
|
+
let afterHeight = totalHeight - beforeHeight - visibleHeight
|
|
68
|
+
let spacerElementBefore = this.startIndex != 0 ? html`
|
|
69
|
+
<tr style="height:${beforeHeight}px" class="virtual-scroll-spacer">
|
|
70
|
+
<td colspan="${this.columns.length}"></td></tr>` : null
|
|
71
|
+
|
|
72
|
+
let spacerElementAfter = html`
|
|
73
|
+
<tr style="height:${afterHeight}px" class="virtual-scroll-spacer">
|
|
74
|
+
<td colspan="${this.columns.length}"></td></tr>`
|
|
75
|
+
return html`
|
|
76
|
+
<div style="height:${totalHeight}px;position: absolute;overflow-x: hidden;overflow-y: hidden;z-index:-1;width:1px;"></div>
|
|
77
|
+
<tbody>
|
|
78
|
+
${spacerElementBefore}
|
|
79
|
+
</tbody>
|
|
80
|
+
<div style="display:table-row-group;max-height:${visibleHeight}px; overflow:hidden;">
|
|
81
|
+
|
|
82
|
+
${repeat(this.data.slice(Math.max(startIndex, 0), endIndex), (row, index) => html`
|
|
83
|
+
<tr class="${(index + startIndex) % 2 === 0 ? "odd" : ""}">
|
|
84
|
+
${repeat(this.columns, (col) => {
|
|
85
|
+
return html`<spectric-table-cell .column=${col} .row=${row} .index=${index + startIndex} .columns=${this.columns}></spectric-table-cell>`
|
|
86
|
+
})}
|
|
87
|
+
</tr>`)
|
|
88
|
+
}
|
|
89
|
+
</div>
|
|
90
|
+
<tbody>
|
|
91
|
+
${afterHeight > 0 ? spacerElementAfter : null}
|
|
92
|
+
</tbody>
|
|
93
|
+
|
|
94
|
+
`
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface TableBodyEvents {
|
|
101
|
+
//'sort': (event: CustomEvent<ColumnSettings<any>>) => void; //TODO sort events
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
declare global {
|
|
105
|
+
interface HTMLElementTagNameMap {
|
|
106
|
+
[TableBodyElementTag]: HTMLElementTagWithEvents<TableVirtualBodyElement<any>, TableBodyEvents>
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
namespace JSX {
|
|
110
|
+
interface IntrinsicElements {
|
|
111
|
+
/**
|
|
112
|
+
* @see {@link DialogElement}
|
|
113
|
+
*/
|
|
114
|
+
[TableBodyElementTag]: ReactElementWithPropsAndEvents<TableVirtualBodyElement<any>, BodyProps<any>, TableBodyEvents>;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
namespace React {
|
|
118
|
+
namespace JSX {
|
|
119
|
+
interface IntrinsicElements {
|
|
120
|
+
/**
|
|
121
|
+
* @see {@link DialogElement}
|
|
122
|
+
*/
|
|
123
|
+
[TableBodyElementTag]: ReactElementWithPropsAndEvents<TableVirtualBodyElement<any>, BodyProps<any>, TableBodyEvents>
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -3,8 +3,9 @@ import { customElement, property } from 'lit/decorators.js';
|
|
|
3
3
|
import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
|
|
4
4
|
import "./tooltip.css"
|
|
5
5
|
export const TooltipElementTag = "spectric-tooltip"
|
|
6
|
-
import { css, CSSResultGroup, html,
|
|
6
|
+
import { css, CSSResultGroup, html, render } from "lit-element";
|
|
7
7
|
import { DomRenderable } from "../table";
|
|
8
|
+
import { DisposableElement } from '../../classes/DisposibleElement';
|
|
8
9
|
export type { TooltipProps, TooltipEvents }
|
|
9
10
|
export enum TooltipPostions {
|
|
10
11
|
top = "top",
|
|
@@ -19,11 +20,11 @@ interface TooltipProps {
|
|
|
19
20
|
/**
|
|
20
21
|
* How long you need to hover before the tooltip displays
|
|
21
22
|
*/
|
|
22
|
-
delay
|
|
23
|
+
delay?: number;
|
|
23
24
|
/**
|
|
24
25
|
* How long the fade in animation should run
|
|
25
26
|
*/
|
|
26
|
-
animationDuration
|
|
27
|
+
animationDuration?: number;
|
|
27
28
|
/**
|
|
28
29
|
* Tooltip contents
|
|
29
30
|
*/
|
|
@@ -31,18 +32,18 @@ interface TooltipProps {
|
|
|
31
32
|
/**
|
|
32
33
|
* Where to anchor the tooltip
|
|
33
34
|
*/
|
|
34
|
-
position
|
|
35
|
+
position?: TooltipPostionsTypes
|
|
35
36
|
/**
|
|
36
|
-
* Sets a max width for the contents
|
|
37
|
+
* Sets a max width for the contents you can disable this by setting to 0 or -1
|
|
37
38
|
*/
|
|
38
39
|
maxWidth?: number
|
|
39
40
|
/**
|
|
40
|
-
* Container the tool tip will be attached to.
|
|
41
|
+
* Container the tool tip will be attached to.
|
|
41
42
|
*/
|
|
42
43
|
portalTarget?: HTMLElement
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
* The element that triggers the tooltip.
|
|
46
|
+
* The element that triggers the tooltip. This is used for special cases like in the shadow dom if you want to target a host element instead of the immediate parent element
|
|
46
47
|
*/
|
|
47
48
|
triggerTarget?: HTMLElement
|
|
48
49
|
}
|
|
@@ -51,7 +52,7 @@ interface TooltipProps {
|
|
|
51
52
|
* Spectric tooltip will add a tooltip to any container
|
|
52
53
|
*/
|
|
53
54
|
@customElement(TooltipElementTag)
|
|
54
|
-
export class TooltipElement extends
|
|
55
|
+
export class TooltipElement extends DisposableElement implements TooltipProps {
|
|
55
56
|
@property({ type: Number, reflect: true })
|
|
56
57
|
delay: number = 100;
|
|
57
58
|
@property({ type: Number, reflect: true })
|
|
@@ -72,25 +73,26 @@ export class TooltipElement extends LitElement implements TooltipProps {
|
|
|
72
73
|
portalTarget: HTMLElement = document.body
|
|
73
74
|
private timer?: number;
|
|
74
75
|
private open: boolean = false;
|
|
75
|
-
mouseframe?: number;
|
|
76
|
+
private mouseframe?: number;
|
|
77
|
+
/**
|
|
78
|
+
* @default parentElement
|
|
79
|
+
*/
|
|
76
80
|
@property({ attribute: false })
|
|
77
|
-
triggerTarget
|
|
81
|
+
triggerTarget!: HTMLElement;
|
|
78
82
|
private get target() {
|
|
79
83
|
return this.triggerTarget || this.parentElement
|
|
80
84
|
}
|
|
85
|
+
constructor() {
|
|
86
|
+
super()
|
|
87
|
+
this.addDisposableListener(() => this.target, "mousemove", this._getMousePosition)
|
|
88
|
+
this.addDisposableListener(() => this.target, "mouseover", this.showToolTip)
|
|
89
|
+
this.addDisposableListener(() => this.target, "mouseleave", this._hideTooltip)
|
|
90
|
+
}
|
|
81
91
|
connectedCallback(): void {
|
|
82
92
|
super.connectedCallback()
|
|
83
|
-
if (this.target) {
|
|
84
|
-
this.target.addEventListener("mousemove", this._getMousePosition)
|
|
85
|
-
this.target.addEventListener("mouseover", this.showToolTip)
|
|
86
|
-
this.target.addEventListener("mouseleave", this._hideTooltip)
|
|
87
|
-
}
|
|
88
93
|
}
|
|
89
94
|
disconnectedCallback(): void {
|
|
90
|
-
super.
|
|
91
|
-
this.target?.removeEventListener("mousemove", this._getMousePosition)
|
|
92
|
-
this.target?.removeEventListener("mouseover", this.showToolTip)
|
|
93
|
-
this.target?.removeEventListener("mouseleave", this._hideTooltip)
|
|
95
|
+
super.disconnectedCallback()
|
|
94
96
|
this.portalElement.remove()
|
|
95
97
|
}
|
|
96
98
|
private _getMousePosition = (ev: MouseEvent) => {
|
|
@@ -153,39 +155,43 @@ export class TooltipElement extends LitElement implements TooltipProps {
|
|
|
153
155
|
}
|
|
154
156
|
const bounds = this.target.getBoundingClientRect()
|
|
155
157
|
const portalBounds = this.portalElement.getBoundingClientRect()
|
|
156
|
-
if (this.target !== document.body)
|
|
158
|
+
if (this.target !== document.body) {
|
|
157
159
|
if (this.maxWidth && this.maxWidth > 0) {
|
|
158
160
|
portalBounds.width = Math.min(portalBounds.width, this.maxWidth)
|
|
159
161
|
}
|
|
162
|
+
}
|
|
163
|
+
let location: {
|
|
164
|
+
left: string;
|
|
165
|
+
top: string;
|
|
166
|
+
};
|
|
160
167
|
if (this.position === "mouse" && this.mouseLocation) {
|
|
161
168
|
this.mouseframe = undefined
|
|
162
|
-
|
|
163
|
-
this.applyStyle({ ...styles, ...location })
|
|
169
|
+
location = { left: this.mouseLocation.left + 10 + "px", top: this.mouseLocation.top - (portalBounds.height / 2) + "px" }
|
|
164
170
|
} else if (this.position === "top") {
|
|
165
|
-
|
|
171
|
+
location = {
|
|
166
172
|
top: bounds.top - portalBounds.height + "px",
|
|
167
173
|
left: (bounds.left + (bounds.width / 2)) - (portalBounds.width / 2) + "px"
|
|
168
174
|
}
|
|
169
|
-
this.applyStyle({ ...styles, ...location })
|
|
170
175
|
} else if (this.position === "bottom") {
|
|
171
|
-
|
|
176
|
+
location = {
|
|
172
177
|
top: bounds.bottom + "px",
|
|
173
178
|
left: (bounds.left + (bounds.width / 2)) - (portalBounds.width / 2) + "px"
|
|
174
179
|
}
|
|
175
|
-
this.applyStyle({ ...styles, ...location })
|
|
176
180
|
} else if (this.position === "left") {
|
|
177
|
-
|
|
181
|
+
location = {
|
|
178
182
|
top: Math.max(0, bounds.top + bounds.height / 2 - portalBounds.height / 2) + "px",
|
|
179
183
|
left: bounds.left - (portalBounds.width) + "px"
|
|
180
184
|
}
|
|
181
|
-
this.applyStyle({ ...styles, ...location })
|
|
182
185
|
} else if (this.position === "right") {
|
|
183
|
-
|
|
186
|
+
location = {
|
|
184
187
|
top: Math.max(0, bounds.top + bounds.height / 2 - portalBounds.height / 2) + "px",
|
|
185
188
|
left: bounds.right + "px"
|
|
186
189
|
}
|
|
187
|
-
|
|
190
|
+
} else {
|
|
191
|
+
location = { left: "0px", top: "0px" };
|
|
192
|
+
console.error("Unknown position... Maybe we sould implement auto?")
|
|
188
193
|
}
|
|
194
|
+
this.applyStyle({ ...styles, ...location })
|
|
189
195
|
if (this.position !== "mouse") {
|
|
190
196
|
this.portalElement.animate({ opacity: [0, 1] }, { duration: this.animationDuration })
|
|
191
197
|
}
|
|
@@ -210,7 +216,7 @@ declare global {
|
|
|
210
216
|
namespace JSX {
|
|
211
217
|
interface IntrinsicElements {
|
|
212
218
|
/**
|
|
213
|
-
* @see {@link
|
|
219
|
+
* @see {@link TooltipElement}
|
|
214
220
|
*/
|
|
215
221
|
[TooltipElementTag]: ReactElementWithPropsAndEvents<TooltipElement, TooltipProps, TooltipEvents>;
|
|
216
222
|
}
|
|
@@ -219,7 +225,7 @@ declare global {
|
|
|
219
225
|
namespace JSX {
|
|
220
226
|
interface IntrinsicElements {
|
|
221
227
|
/**
|
|
222
|
-
* @see {@link
|
|
228
|
+
* @see {@link TooltipElement}
|
|
223
229
|
*/
|
|
224
230
|
[TooltipElementTag]: ReactElementWithPropsAndEvents<TooltipElement, TooltipProps, TooltipEvents>
|
|
225
231
|
}
|