@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.
Files changed (51) hide show
  1. package/dist/classes/DisposibleElement.d.ts +4 -2
  2. package/dist/components/Bitdisplay.d.ts +4 -4
  3. package/dist/components/Button.d.ts +1 -1
  4. package/dist/components/Header.d.ts +1 -1
  5. package/dist/components/input.d.ts +13 -12
  6. package/dist/components/pagination/pagination.d.ts +5 -5
  7. package/dist/components/splitview/splitview.d.ts +5 -5
  8. package/dist/components/table/body.d.ts +2 -1
  9. package/dist/components/table/cell.d.ts +2 -0
  10. package/dist/components/table/header.d.ts +2 -1
  11. package/dist/components/table/sorting.d.ts +5 -0
  12. package/dist/components/table/table.d.ts +51 -14
  13. package/dist/components/table/virtualBody.d.ts +49 -0
  14. package/dist/components/tooltip/tooltip.d.ts +17 -12
  15. package/dist/custom-elements.json +50 -16
  16. package/dist/index.es.js +2099 -1867
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.umd.js +128 -93
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/style.css +1 -1
  21. package/dist/utils/once.d.ts +1 -0
  22. package/dist/utils/spread.d.ts +1 -1
  23. package/package.json +1 -1
  24. package/src/classes/DisposibleElement.ts +15 -9
  25. package/src/components/Bitdisplay.ts +7 -7
  26. package/src/components/Button.ts +1 -1
  27. package/src/components/Header.ts +1 -1
  28. package/src/components/input.ts +18 -15
  29. package/src/components/pagination/pagination.ts +7 -7
  30. package/src/components/query_bar/QueryBar.ts +26 -21
  31. package/src/components/splitview/splitview.ts +5 -5
  32. package/src/components/table/body.ts +13 -5
  33. package/src/components/table/cell.ts +9 -7
  34. package/src/components/table/header.ts +28 -4
  35. package/src/components/table/sorting.ts +34 -0
  36. package/src/components/table/table.css +60 -4
  37. package/src/components/table/table.ts +149 -33
  38. package/src/components/table/virtualBody.css +13 -0
  39. package/src/components/table/virtualBody.ts +127 -0
  40. package/src/components/tooltip/tooltip.ts +38 -32
  41. package/src/docs/HTML-Vue-Python Integration.mdx +3 -3
  42. package/src/docs/html-include.png +0 -0
  43. package/src/docs/vue-example.png +0 -0
  44. package/src/docs/vue-include.png +0 -0
  45. package/src/stories/BitDisplay.stories.ts +2 -0
  46. package/src/stories/fixtures/ExampleContent.ts +15 -8
  47. package/src/stories/fixtures/data.ts +21 -10
  48. package/src/stories/pagination.stories.ts +2 -1
  49. package/src/stories/table.stories.ts +27 -5
  50. package/src/utils/once.ts +12 -0
  51. 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-body tr:hover{
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
- spectric-table-body {
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
- render?: (row: T, table: TableElement<T>) => DomRenderable
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 TableSelectOptions = "multi" | "single"
27
- interface TableProps<T> {
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?: TableSelectOptions
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 | undefined;
81
+ pagination?: PaginationProps;
48
82
  @property({ attribute: false })
49
83
  columns: ColumnSettings<T>[] = [];
50
- @property({ type: String, reflect: false })
51
- select?: TableSelectOptions;
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.size
57
- this.pagination = { ...this.pagination, ...e.detail }
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<{ pagination?: PaginationChangeProps }>("change", { detail: { pagination } }))
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("select", { detail: this.selected }))
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
- return html`<spectric-input variant="checkbox" .checked=${this.selected.includes(row)} @change=${(e: DomEvent<HTMLInputElement>) => {
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
- if (this.select === "single") {
93
- this.selected = []
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.selected.push(row)
97
- this.dispatchEvent(new CustomEvent("select", { detail: this.selected }))
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 role="table">
112
- <spectric-table-header .columns=${columns}></spectric-table-header>
113
- <spectric-table-body .columns=${columns} .data=${this.data} .table=${this}></spectric-table-body>
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<{ pagination: PaginationChangeProps }>) => void;
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 DialogElement}
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 DialogElement}
258
+ * @see {@link TableElement}
143
259
  */
144
260
  [TableElementTag]: ReactElementWithPropsAndEvents<TableElement<any>, TableProps<any>, TableEvents>
145
261
  }
@@ -0,0 +1,13 @@
1
+ spectric-table-body {
2
+ display: table-row-group;
3
+ }
4
+ spectric-table-virtual-body {
5
+ display: contents;
6
+ }
7
+
8
+
9
+
10
+ spectric-table-virtual-body .virtual-scroll-spacer td {
11
+ padding: 0;
12
+ border: 0px;
13
+ }
@@ -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, LitElement, render } from "lit-element";
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: number;
23
+ delay?: number;
23
24
  /**
24
25
  * How long the fade in animation should run
25
26
  */
26
- animationDuration: number;
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: TooltipPostionsTypes
35
+ position?: TooltipPostionsTypes
35
36
  /**
36
- * Sets a max width for the contents (default:300) you can disable this by setting to 0 or -1
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. (default:document.body)
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. (default:node.parentElement) 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
+ * 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 LitElement implements TooltipProps {
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?: HTMLElement;
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.connectedCallback()
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
- const location = { left: this.mouseLocation.left + 10 + "px", top: this.mouseLocation.top - (portalBounds.height / 2) + "px" }
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
- const location = {
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
- const location = {
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
- const location = {
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
- const location = {
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
- this.applyStyle({ ...styles, ...location })
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 DialogElement}
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 DialogElement}
228
+ * @see {@link TooltipElement}
223
229
  */
224
230
  [TooltipElementTag]: ReactElementWithPropsAndEvents<TooltipElement, TooltipProps, TooltipEvents>
225
231
  }