@spectric/ui 0.0.7 → 0.0.9
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/README.MD +5 -28
- package/dist/components/Button.d.ts +14 -3
- package/dist/components/index.d.ts +3 -0
- package/dist/components/input.d.ts +5 -1
- package/dist/components/pagination/index.d.ts +1 -0
- package/dist/components/pagination/pagination.d.ts +60 -0
- package/dist/components/table/body.d.ts +44 -0
- package/dist/components/table/cell.d.ts +58 -0
- package/dist/components/table/header.d.ts +42 -0
- package/dist/components/table/index.d.ts +1 -0
- package/dist/components/table/table.d.ts +100 -0
- package/dist/components/tooltip/index.d.ts +1 -0
- package/dist/components/tooltip/tooltip.d.ts +95 -0
- package/dist/custom-elements.json +201 -7
- package/dist/index.es.js +2104 -1612
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +222 -83
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Button.ts +23 -4
- package/src/components/button.css.ts +85 -2
- package/src/components/index.ts +4 -1
- package/src/components/input.css +6 -1
- package/src/components/input.ts +26 -5
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.css +10 -0
- package/src/components/pagination/pagination.ts +133 -0
- package/src/components/table/body.ts +69 -0
- package/src/components/table/cell.ts +133 -0
- package/src/components/table/header.ts +68 -0
- package/src/components/table/index.ts +1 -0
- package/src/components/table/table.css +46 -0
- package/src/components/table/table.ts +174 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.css +52 -0
- package/src/components/tooltip/tooltip.ts +228 -0
- package/src/docs/HTML-Vue-Python Integration.mdx +18 -0
- package/src/docs/React.mdx +20 -0
- package/src/docs/welcome.mdx +29 -0
- package/src/stories/Button.stories.ts +25 -2
- package/src/stories/fixtures/ExampleContent.ts +39 -4
- package/src/stories/fixtures/data.ts +20 -2
- package/src/stories/pagination.stories.ts +63 -0
- package/src/stories/table.stories.ts +88 -0
- package/src/stories/tooltip.stories.ts +68 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit';
|
|
2
|
+
import "../pagination";
|
|
3
|
+
import { customElement, property, } from 'lit/decorators.js';
|
|
4
|
+
import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
|
|
5
|
+
import "./table.css"
|
|
6
|
+
export const TableHeaderElementTag = "spectric-table-header"
|
|
7
|
+
import { ColumnSettings } from './table';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
interface HeaderProps<T> {
|
|
11
|
+
columns: ColumnSettings<T>[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pagination Element
|
|
16
|
+
*/
|
|
17
|
+
@customElement(TableHeaderElementTag)
|
|
18
|
+
export class TableHeaderElement<T> extends LitElement implements HeaderProps<T> {
|
|
19
|
+
@property({ type: Array, attribute: false })
|
|
20
|
+
columns: ColumnSettings<T>[] = [];
|
|
21
|
+
|
|
22
|
+
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
23
|
+
return this
|
|
24
|
+
}
|
|
25
|
+
protected render(): unknown {
|
|
26
|
+
|
|
27
|
+
return html`
|
|
28
|
+
<tr>
|
|
29
|
+
${this.columns.map(column => {
|
|
30
|
+
let classes = ["header-contents"]
|
|
31
|
+
if (column.filterable) {
|
|
32
|
+
//classes.push("filterable")
|
|
33
|
+
}
|
|
34
|
+
return html`<td><div class=${classes.join(" ")}>${column.title || column.key}</div></td>`
|
|
35
|
+
})}
|
|
36
|
+
</tr>
|
|
37
|
+
`
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface TableHeaderEvents {
|
|
42
|
+
'sort': (event: CustomEvent<ColumnSettings<any>>) => void; //TODO sort events
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare global {
|
|
46
|
+
interface HTMLElementTagNameMap {
|
|
47
|
+
[TableHeaderElementTag]: HTMLElementTagWithEvents<TableHeaderElement<any>, TableHeaderEvents>
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
namespace JSX {
|
|
51
|
+
interface IntrinsicElements {
|
|
52
|
+
/**
|
|
53
|
+
* @see {@link DialogElement}
|
|
54
|
+
*/
|
|
55
|
+
[TableHeaderElementTag]: ReactElementWithPropsAndEvents<TableHeaderElement<any>, HeaderProps<any>, TableHeaderEvents>;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
namespace React {
|
|
59
|
+
namespace JSX {
|
|
60
|
+
interface IntrinsicElements {
|
|
61
|
+
/**
|
|
62
|
+
* @see {@link DialogElement}
|
|
63
|
+
*/
|
|
64
|
+
[TableHeaderElementTag]: ReactElementWithPropsAndEvents<TableHeaderElement<any>, HeaderProps<any>, TableHeaderEvents>
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./table"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
spectric-table{
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
}
|
|
5
|
+
spectric-table tr{
|
|
6
|
+
text-align: center;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
spectric-table-body tr:hover{
|
|
10
|
+
background-color: color-mix(in srgb, var(--spectric-primary, #1ea7fd), transparent 70%)
|
|
11
|
+
}
|
|
12
|
+
spectric-table-header{
|
|
13
|
+
display: table-header-group;
|
|
14
|
+
font-weight: bold;
|
|
15
|
+
}
|
|
16
|
+
spectric-table div[role="table"]{
|
|
17
|
+
display: table;
|
|
18
|
+
}
|
|
19
|
+
spectric-table-body {
|
|
20
|
+
display: table-row-group;
|
|
21
|
+
}
|
|
22
|
+
spectric-table-cell{
|
|
23
|
+
display: contents;
|
|
24
|
+
vertical-align: middle;
|
|
25
|
+
}
|
|
26
|
+
spectric-table-cell td{
|
|
27
|
+
position: relative;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
spectric-table td:hover:has(.filterable) {
|
|
31
|
+
border: 1px solid var(--spectric-primary, #1ea7fd);
|
|
32
|
+
}
|
|
33
|
+
spectric-table td {
|
|
34
|
+
border: 1px solid transparent;
|
|
35
|
+
}
|
|
36
|
+
spectric-table-cell .table-cell-actions{
|
|
37
|
+
position: absolute;
|
|
38
|
+
display: flex;
|
|
39
|
+
width: 100%;
|
|
40
|
+
flex-direction: row-reverse;
|
|
41
|
+
visibility: hidden;
|
|
42
|
+
top: -10px;
|
|
43
|
+
}
|
|
44
|
+
spectric-table-cell td:hover .table-cell-actions{
|
|
45
|
+
visibility: unset;
|
|
46
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { html, LitElement, TemplateResult } from 'lit';
|
|
2
|
+
import "../pagination";
|
|
3
|
+
import "./header"
|
|
4
|
+
import "./body"
|
|
5
|
+
import { customElement, property, state, } from 'lit/decorators.js';
|
|
6
|
+
import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
|
|
7
|
+
import "./table.css"
|
|
8
|
+
export const TableElementTag = "spectric-table"
|
|
9
|
+
import { spreadProps } from '../../utils/spread';
|
|
10
|
+
import { PaginationChangeProps, PaginationProps } from '../pagination';
|
|
11
|
+
import { FilterEvent } from './cell';
|
|
12
|
+
export type { TableProps, TableEvents }
|
|
13
|
+
|
|
14
|
+
export type DomRenderable = HTMLElement | TemplateResult | string | number | null
|
|
15
|
+
|
|
16
|
+
export type ColumnSettings<T> = {
|
|
17
|
+
width?: number
|
|
18
|
+
whiteSpace?: "nowrap";
|
|
19
|
+
hidden?: boolean
|
|
20
|
+
sortable?: boolean
|
|
21
|
+
filterable?: boolean
|
|
22
|
+
title?: DomRenderable
|
|
23
|
+
key?: string
|
|
24
|
+
render?: (row: T, table: TableElement<T>) => DomRenderable
|
|
25
|
+
}
|
|
26
|
+
type TableSelectOptions = "multi" | "single"
|
|
27
|
+
interface TableProps<T> {
|
|
28
|
+
pagination?: PaginationProps
|
|
29
|
+
columns: ColumnSettings<T>[]
|
|
30
|
+
data: T[]
|
|
31
|
+
select?: TableSelectOptions
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type DomEvent<T> = Event & {
|
|
35
|
+
target: T
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Table Element
|
|
39
|
+
*
|
|
40
|
+
* The table element is a bit more complex and the column settings and data can only be set through the properties
|
|
41
|
+
*
|
|
42
|
+
*
|
|
43
|
+
* React
|
|
44
|
+
*
|
|
45
|
+
* ``` tsx
|
|
46
|
+
* <spectric-table data={[{col1:1}]} columns={[{key:"col1",}]} ></spectric-table>
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Javascript
|
|
50
|
+
*
|
|
51
|
+
* ``` js
|
|
52
|
+
* table = document.createElement("spectric-table")
|
|
53
|
+
* table.data = [{col1:1}]
|
|
54
|
+
* table.columns = [{key:"col1",}]
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* HTML
|
|
58
|
+
*
|
|
59
|
+
* ``` html
|
|
60
|
+
* <spectric-table id="table"></spectric-table>
|
|
61
|
+
* <script>
|
|
62
|
+
* document.querySelector("#table")
|
|
63
|
+
* table.data = [{col1:1}]
|
|
64
|
+
* table.columns = [{key:"col1",}]
|
|
65
|
+
* </script>
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
@customElement(TableElementTag)
|
|
69
|
+
export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
70
|
+
@property({ type: Array, attribute: false })
|
|
71
|
+
data: T[] = [];
|
|
72
|
+
@property({ type: Object, attribute: false })
|
|
73
|
+
pagination?: PaginationProps | undefined;
|
|
74
|
+
@property({ attribute: false })
|
|
75
|
+
columns: ColumnSettings<T>[] = [];
|
|
76
|
+
@property({ type: String, reflect: false })
|
|
77
|
+
select?: TableSelectOptions;
|
|
78
|
+
private _handlePaginationChange = (e: CustomEvent<PaginationChangeProps>) => {
|
|
79
|
+
e.preventDefault()
|
|
80
|
+
e.stopPropagation()
|
|
81
|
+
if (this.pagination) {
|
|
82
|
+
this.pagination.size
|
|
83
|
+
this.pagination = { ...this.pagination, ...e.detail }
|
|
84
|
+
}
|
|
85
|
+
this._emitChange()
|
|
86
|
+
};
|
|
87
|
+
private _emitChange = () => {
|
|
88
|
+
let { pagination } = this
|
|
89
|
+
this.dispatchEvent(new CustomEvent<{ pagination?: PaginationChangeProps }>("change", { detail: { pagination } }))
|
|
90
|
+
}
|
|
91
|
+
//@ts-ignore
|
|
92
|
+
private __DO_NOT_USE_filter = () => {
|
|
93
|
+
//This is only here to document events that bubble up from lower components
|
|
94
|
+
this.dispatchEvent(new CustomEvent<FilterEvent<T>>("filter"))
|
|
95
|
+
}
|
|
96
|
+
@state()
|
|
97
|
+
selected: T[] = [];
|
|
98
|
+
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
99
|
+
return this
|
|
100
|
+
}
|
|
101
|
+
_handleSelectAllChange = (e: DomEvent<HTMLInputElement>) => {
|
|
102
|
+
e.stopPropagation()
|
|
103
|
+
if (e.target.checked) {
|
|
104
|
+
this.selected = [...this.data]
|
|
105
|
+
} else {
|
|
106
|
+
this.selected = []
|
|
107
|
+
}
|
|
108
|
+
this.dispatchEvent(new CustomEvent("select", { detail: this.selected }))
|
|
109
|
+
}
|
|
110
|
+
protected render(): unknown {
|
|
111
|
+
let columns = this.columns.filter(column => !column.hidden)
|
|
112
|
+
if (this.select) {
|
|
113
|
+
columns.unshift({
|
|
114
|
+
title: this.select === "multi" ? html`<spectric-input variant="checkbox" @change=${this._handleSelectAllChange} .helperText=${"Select All"}></spectric-input>` : null,
|
|
115
|
+
render: (row) => {
|
|
116
|
+
return html`<spectric-input variant="checkbox" .checked=${this.selected.includes(row)} @change=${(e: DomEvent<HTMLInputElement>) => {
|
|
117
|
+
e.stopPropagation()
|
|
118
|
+
if (this.select === "single") {
|
|
119
|
+
this.selected = []
|
|
120
|
+
}
|
|
121
|
+
if (e.target.checked) {
|
|
122
|
+
this.selected.push(row)
|
|
123
|
+
this.dispatchEvent(new CustomEvent("select", { detail: this.selected }))
|
|
124
|
+
} else {
|
|
125
|
+
let index = this.selected.findIndex(value => value === row)
|
|
126
|
+
if (index !== -1) {
|
|
127
|
+
this.selected.splice(index, 1)
|
|
128
|
+
this.dispatchEvent(new CustomEvent("select", { detail: this.selected }))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}}></spectric-input>`
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return html`
|
|
137
|
+
<div role="table">
|
|
138
|
+
<spectric-table-header .columns=${columns}></spectric-table-header>
|
|
139
|
+
<spectric-table-body .columns=${columns} .data=${this.data} .table=${this}></spectric-table-body>
|
|
140
|
+
</div>
|
|
141
|
+
${this.pagination ? html`<spectric-pagination ${spreadProps(this.pagination)} @change=${this._handlePaginationChange}></spectric-pagination>` : null}
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface TableEvents {
|
|
147
|
+
'change': (event: CustomEvent<{ pagination: PaginationChangeProps }>) => void;
|
|
148
|
+
'filter': (event: CustomEvent<FilterEvent<any>>) => void;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
declare global {
|
|
152
|
+
interface HTMLElementTagNameMap {
|
|
153
|
+
[TableElementTag]: HTMLElementTagWithEvents<TableElement<any>, TableEvents>
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
namespace JSX {
|
|
157
|
+
interface IntrinsicElements {
|
|
158
|
+
/**
|
|
159
|
+
* @see {@link DialogElement}
|
|
160
|
+
*/
|
|
161
|
+
[TableElementTag]: ReactElementWithPropsAndEvents<TableElement<any>, TableProps<any>, TableEvents>;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
namespace React {
|
|
165
|
+
namespace JSX {
|
|
166
|
+
interface IntrinsicElements {
|
|
167
|
+
/**
|
|
168
|
+
* @see {@link DialogElement}
|
|
169
|
+
*/
|
|
170
|
+
[TableElementTag]: ReactElementWithPropsAndEvents<TableElement<any>, TableProps<any>, TableEvents>
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./tooltip"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.spectric-tooltip-portal{
|
|
2
|
+
position: fixed;
|
|
3
|
+
z-index: 9999;
|
|
4
|
+
pointer-events: none;
|
|
5
|
+
--spectric-tooltip-background: color-mix(in srgb,var(--spectric-background-inverse,#f4f4f4) 100%,var(--spectric-primary,#1ea7fd) 90%)
|
|
6
|
+
}
|
|
7
|
+
.spectric-tooltip-portal .tooltip-container{
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
align-items: center;
|
|
11
|
+
}
|
|
12
|
+
.spectric-tooltip-portal.top .tooltip-container{
|
|
13
|
+
flex-direction: column-reverse;
|
|
14
|
+
}
|
|
15
|
+
.spectric-tooltip-portal.bottom .tooltip-container{
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
}
|
|
18
|
+
.spectric-tooltip-portal.left .tooltip-container{
|
|
19
|
+
flex-direction: row-reverse;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.spectric-tooltip-portal .tooltip-content{
|
|
23
|
+
background: var(--spectric-tooltip-background);
|
|
24
|
+
border-radius: var(--spectric-border-radius,.4em);
|
|
25
|
+
box-shadow: 0 0 .01em .01em color-mix(in srgb, var(--spectric-background-hover,rgba(141, 141, 141, 0.12)) 90%, var(--spectric-text-on-color,#ffffff) 90%);
|
|
26
|
+
padding: .2em;
|
|
27
|
+
color:var(--spectric-text-on-color,#ffffff);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.spectric-tooltip-portal .tooltip-caret{
|
|
31
|
+
background:var(--spectric-tooltip-background);
|
|
32
|
+
}
|
|
33
|
+
.spectric-tooltip-portal.top .tooltip-caret,.spectric-tooltip-portal.bottom .tooltip-caret{
|
|
34
|
+
inline-size: .75rem;
|
|
35
|
+
block-size: .374rem;
|
|
36
|
+
}
|
|
37
|
+
.spectric-tooltip-portal.left .tooltip-caret,.spectric-tooltip-portal.right .tooltip-caret{
|
|
38
|
+
inline-size: .375rem;
|
|
39
|
+
block-size: .75rem;
|
|
40
|
+
}
|
|
41
|
+
.spectric-tooltip-portal.top .tooltip-caret{
|
|
42
|
+
clip-path: polygon(0 0,50% 100%,100% 0);
|
|
43
|
+
}
|
|
44
|
+
.spectric-tooltip-portal.bottom .tooltip-caret{
|
|
45
|
+
clip-path: polygon(0 100%,50% 0,100% 100%);
|
|
46
|
+
}
|
|
47
|
+
.spectric-tooltip-portal.left .tooltip-caret{
|
|
48
|
+
clip-path: polygon(0 0,100% 50%,0 100%);
|
|
49
|
+
}
|
|
50
|
+
.spectric-tooltip-portal.right .tooltip-caret{
|
|
51
|
+
clip-path: polygon(0 50% ,100% 0, 100% 100%);
|
|
52
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
|
|
4
|
+
import "./tooltip.css"
|
|
5
|
+
export const TooltipElementTag = "spectric-tooltip"
|
|
6
|
+
import { css, CSSResultGroup, html, LitElement, render } from "lit-element";
|
|
7
|
+
import { DomRenderable } from "../table";
|
|
8
|
+
export type { TooltipProps, TooltipEvents }
|
|
9
|
+
export enum TooltipPostions {
|
|
10
|
+
top = "top",
|
|
11
|
+
bottom = "bottom",
|
|
12
|
+
left = "left",
|
|
13
|
+
right = "right",
|
|
14
|
+
mouse = "mouse"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type TooltipPostionsTypes = `${TooltipPostions}`
|
|
18
|
+
interface TooltipProps {
|
|
19
|
+
/**
|
|
20
|
+
* How long you need to hover before the tooltip displays
|
|
21
|
+
*/
|
|
22
|
+
delay: number;
|
|
23
|
+
/**
|
|
24
|
+
* How long the fade in animation should run
|
|
25
|
+
*/
|
|
26
|
+
animationDuration: number;
|
|
27
|
+
/**
|
|
28
|
+
* Tooltip contents
|
|
29
|
+
*/
|
|
30
|
+
text: DomRenderable
|
|
31
|
+
/**
|
|
32
|
+
* Where to anchor the tooltip
|
|
33
|
+
*/
|
|
34
|
+
position: TooltipPostionsTypes
|
|
35
|
+
/**
|
|
36
|
+
* Sets a max width for the contents (default:300) you can disable this by setting to 0 or -1
|
|
37
|
+
*/
|
|
38
|
+
maxWidth?: number
|
|
39
|
+
/**
|
|
40
|
+
* Container the tool tip will be attached to. (default:document.body)
|
|
41
|
+
*/
|
|
42
|
+
portalTarget?: HTMLElement
|
|
43
|
+
|
|
44
|
+
/**
|
|
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
|
+
*/
|
|
47
|
+
triggerTarget?: HTMLElement
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Spectric tooltip will add a tooltip to any container
|
|
52
|
+
*/
|
|
53
|
+
@customElement(TooltipElementTag)
|
|
54
|
+
export class TooltipElement extends LitElement implements TooltipProps {
|
|
55
|
+
@property({ type: Number, reflect: true })
|
|
56
|
+
delay: number = 100;
|
|
57
|
+
@property({ type: Number, reflect: true })
|
|
58
|
+
animationDuration: number = 0;
|
|
59
|
+
@property({ type: String, reflect: false })
|
|
60
|
+
text: DomRenderable = "";
|
|
61
|
+
@property({ type: String, reflect: true })
|
|
62
|
+
position: TooltipPostionsTypes = "right";
|
|
63
|
+
@property({ type: Number, reflect: true })
|
|
64
|
+
maxWidth?: number = 300;
|
|
65
|
+
private portalElement = document.createElement("div")
|
|
66
|
+
private mouseLocation?: { left: number; top: number; };
|
|
67
|
+
static styles?: CSSResultGroup | undefined = css`:host{max-height: 0px;
|
|
68
|
+
max-width: 0px;
|
|
69
|
+
display: none;
|
|
70
|
+
pointer-events:none;}`;
|
|
71
|
+
@property({ attribute: false })
|
|
72
|
+
portalTarget: HTMLElement = document.body
|
|
73
|
+
private timer?: number;
|
|
74
|
+
private open: boolean = false;
|
|
75
|
+
mouseframe?: number;
|
|
76
|
+
@property({ attribute: false })
|
|
77
|
+
triggerTarget?: HTMLElement;
|
|
78
|
+
private get target() {
|
|
79
|
+
return this.triggerTarget || this.parentElement
|
|
80
|
+
}
|
|
81
|
+
connectedCallback(): void {
|
|
82
|
+
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
|
+
}
|
|
89
|
+
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)
|
|
94
|
+
this.portalElement.remove()
|
|
95
|
+
}
|
|
96
|
+
private _getMousePosition = (ev: MouseEvent) => {
|
|
97
|
+
this.mouseLocation = {
|
|
98
|
+
left: ev.clientX,
|
|
99
|
+
top: ev.clientY
|
|
100
|
+
}
|
|
101
|
+
if (this.position == "mouse" && this.open && !this.mouseframe) {
|
|
102
|
+
this.mouseframe = requestAnimationFrame(() => this.positionTooltip())
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
_hideTooltip = () => {
|
|
106
|
+
if (this.timer) {
|
|
107
|
+
clearTimeout(this.timer)
|
|
108
|
+
}
|
|
109
|
+
this.open = false
|
|
110
|
+
this.portalElement.remove()
|
|
111
|
+
}
|
|
112
|
+
private showToolTip = async () => {
|
|
113
|
+
if (this.timer) {
|
|
114
|
+
clearTimeout(this.timer)
|
|
115
|
+
}
|
|
116
|
+
await new Promise(resolve => {
|
|
117
|
+
this.timer = window.setTimeout(resolve, this.delay)
|
|
118
|
+
})
|
|
119
|
+
this.portalElement.style.pointerEvents = "none"
|
|
120
|
+
this.portalElement.className = `spectric-tooltip-portal ${this.position}`
|
|
121
|
+
const tooltip = html`<div class="tooltip-container">
|
|
122
|
+
<span class="tooltip-caret"></span>
|
|
123
|
+
<div class="tooltip-content">${this.text}</div>
|
|
124
|
+
</div>`
|
|
125
|
+
render(tooltip, this.portalElement)
|
|
126
|
+
//with the delay it is possible that the triggering element was removed or hidden lets check that it is visible
|
|
127
|
+
if (!this.target || !this.target.checkVisibility()) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
//We need to append our tooltip and let the css updates apply before we can take measurements
|
|
131
|
+
this.portalTarget.appendChild(this.portalElement)
|
|
132
|
+
this.open = true
|
|
133
|
+
requestAnimationFrame(this.positionTooltip)
|
|
134
|
+
}
|
|
135
|
+
private applyStyle = (style: Record<string, string>) => {
|
|
136
|
+
Object.entries(style).forEach(([prop, value]) => {
|
|
137
|
+
this.portalElement.style.setProperty(prop, value)
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
private positionTooltip = () => {
|
|
141
|
+
if (!this.target) {
|
|
142
|
+
//We got detached before the animation frame completed
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
//Since we are attaching to a body we need to send the correct styles from the target portion of the dom to the portal element
|
|
146
|
+
let computedStyle = getComputedStyle(this)
|
|
147
|
+
let styles = {
|
|
148
|
+
"--spectric-primary": computedStyle.getPropertyValue("--spectric-primary"),
|
|
149
|
+
"--spectric-background-inverse": computedStyle.getPropertyValue("--spectric-background-inverse"),
|
|
150
|
+
"--spectric-background-hover": computedStyle.getPropertyValue("--spectric-background-hover"),
|
|
151
|
+
"--spectric-text-on-color": computedStyle.getPropertyValue("--spectric-text-on-color"),
|
|
152
|
+
"--spectric-border-radius": computedStyle.getPropertyValue("--spectric-border-radius"),
|
|
153
|
+
}
|
|
154
|
+
const bounds = this.target.getBoundingClientRect()
|
|
155
|
+
const portalBounds = this.portalElement.getBoundingClientRect()
|
|
156
|
+
if (this.target !== document.body)
|
|
157
|
+
if (this.maxWidth && this.maxWidth > 0) {
|
|
158
|
+
portalBounds.width = Math.min(portalBounds.width, this.maxWidth)
|
|
159
|
+
}
|
|
160
|
+
if (this.position === "mouse" && this.mouseLocation) {
|
|
161
|
+
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 })
|
|
164
|
+
} else if (this.position === "top") {
|
|
165
|
+
const location = {
|
|
166
|
+
top: bounds.top - portalBounds.height + "px",
|
|
167
|
+
left: (bounds.left + (bounds.width / 2)) - (portalBounds.width / 2) + "px"
|
|
168
|
+
}
|
|
169
|
+
this.applyStyle({ ...styles, ...location })
|
|
170
|
+
} else if (this.position === "bottom") {
|
|
171
|
+
const location = {
|
|
172
|
+
top: bounds.bottom + "px",
|
|
173
|
+
left: (bounds.left + (bounds.width / 2)) - (portalBounds.width / 2) + "px"
|
|
174
|
+
}
|
|
175
|
+
this.applyStyle({ ...styles, ...location })
|
|
176
|
+
} else if (this.position === "left") {
|
|
177
|
+
const location = {
|
|
178
|
+
top: Math.max(0, bounds.top + bounds.height / 2 - portalBounds.height / 2) + "px",
|
|
179
|
+
left: bounds.left - (portalBounds.width) + "px"
|
|
180
|
+
}
|
|
181
|
+
this.applyStyle({ ...styles, ...location })
|
|
182
|
+
} else if (this.position === "right") {
|
|
183
|
+
const location = {
|
|
184
|
+
top: Math.max(0, bounds.top + bounds.height / 2 - portalBounds.height / 2) + "px",
|
|
185
|
+
left: bounds.right + "px"
|
|
186
|
+
}
|
|
187
|
+
this.applyStyle({ ...styles, ...location })
|
|
188
|
+
}
|
|
189
|
+
if (this.position !== "mouse") {
|
|
190
|
+
this.portalElement.animate({ opacity: [0, 1] }, { duration: this.animationDuration })
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
protected render() {
|
|
194
|
+
//We don't need to render anything here this is just a placeholder element the content is displayed in a portal attached to the body.
|
|
195
|
+
// See showTooltip for the real rendering
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
interface TooltipEvents {
|
|
201
|
+
//'open': (event: CustomEvent<{ pagination: PaginationChangeProps }>) => void;
|
|
202
|
+
//'filter': (event: CustomEvent<FilterEvent>) => void;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
declare global {
|
|
206
|
+
interface HTMLElementTagNameMap {
|
|
207
|
+
[TooltipElementTag]: HTMLElementTagWithEvents<TooltipElement, TooltipEvents>
|
|
208
|
+
|
|
209
|
+
}
|
|
210
|
+
namespace JSX {
|
|
211
|
+
interface IntrinsicElements {
|
|
212
|
+
/**
|
|
213
|
+
* @see {@link DialogElement}
|
|
214
|
+
*/
|
|
215
|
+
[TooltipElementTag]: ReactElementWithPropsAndEvents<TooltipElement, TooltipProps, TooltipEvents>;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
namespace React {
|
|
219
|
+
namespace JSX {
|
|
220
|
+
interface IntrinsicElements {
|
|
221
|
+
/**
|
|
222
|
+
* @see {@link DialogElement}
|
|
223
|
+
*/
|
|
224
|
+
[TooltipElementTag]: ReactElementWithPropsAndEvents<TooltipElement, TooltipProps, TooltipEvents>
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# HTML integration
|
|
2
|
+
|
|
3
|
+
Add the dist/custom-element.json to the html.languge settings
|
|
4
|
+
https://code.visualstudio.com/docs/languages/html#_html-custom-data
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
# VUE Integration
|
|
8
|
+
|
|
9
|
+
Complete steps to include custom elements in the HTML language server
|
|
10
|
+
then
|
|
11
|
+
Add the HTML language server in the @ext:Vue.volar extention
|
|
12
|
+

|
|
13
|
+
Once setup hovering over spectric elements will provide documentation
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
# Python Jinja
|
|
17
|
+
|
|
18
|
+
Same as HTML Integration
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# React 19+
|
|
2
|
+
|
|
3
|
+
Has full support for webcomponents and you can use webcomponents exactly the same way you use any react component.
|
|
4
|
+
|
|
5
|
+
There is one small stipulation. Any custom event that isn't in the react.DOMAttributes events (onClick,onBlur,onFocus... ect) will not be auto capitalized and handled as a react synthetic event.
|
|
6
|
+
|
|
7
|
+
What does this mean?
|
|
8
|
+
|
|
9
|
+
This means you will need to write
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
<spectric-page onlogin={()=>}></spectric-page>
|
|
13
|
+
```
|
|
14
|
+
[Example Here](https://stackblitz.com/edit/react-ts-gq8n43qy?file=App.tsx)
|
|
15
|
+
|
|
16
|
+
## React < 19
|
|
17
|
+
|
|
18
|
+
For react < 19 the properties aren't passed down by react so you will need to use the useEffect hook to set the properties after the element has been created.
|
|
19
|
+
|
|
20
|
+
[Example here](https://stackblitz.com/edit/react-ts-oo5hgcp2?file=App.tsx)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Component Examples and playground
|
|
2
|
+
|
|
3
|
+
https://pages.spectric.com/web/spectric-ui/?path=/docs/spectric-ui-components-ui-page--docs
|
|
4
|
+
|
|
5
|
+
# Developing
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
nvm use
|
|
9
|
+
npm start
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
# Publishing
|
|
13
|
+
|
|
14
|
+
To publish a new version run
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npm version patch
|
|
18
|
+
git push --follow-tags
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This will trigger a pipeline to run and build the source and publish to our internal gitlab NPM
|
|
22
|
+
|
|
23
|
+
# Installing In your project
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
npm i @spectric/ui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The types files produced when building seemlessly integrate the custom elements into javascript giving full type hinting
|