@spectric/ui 0.0.10 → 0.0.11
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/components/pagination/pagination.d.ts +5 -5
- package/dist/components/table/cell.d.ts +1 -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 +41 -8
- package/dist/components/tooltip/tooltip.d.ts +2 -2
- package/dist/custom-elements.json +28 -8
- package/dist/index.es.js +1585 -1520
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +86 -84
- 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/components/input.ts +0 -1
- package/src/components/pagination/pagination.ts +7 -7
- package/src/components/query_bar/QueryBar.ts +26 -21
- package/src/components/table/cell.ts +5 -6
- package/src/components/table/header.ts +22 -3
- package/src/components/table/sorting.ts +34 -0
- package/src/components/table/table.css +52 -0
- package/src/components/table/table.ts +109 -22
- package/src/components/tooltip/tooltip.ts +2 -2
- package/src/stories/fixtures/ExampleContent.ts +15 -8
- package/src/stories/fixtures/data.ts +21 -10
- package/src/stories/table.stories.ts +25 -5
- package/src/utils/once.ts +12 -0
- package/src/utils/spread.ts +3 -3
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
spectric-input{--input-color: var(--spectric-input-color, #f4f4f4);--border-radius: var(--spectric-border-radius, .4em);--input-bottom: var(--spectric-input-bottom, var(--spectric-button-primary, #a8a8a8));--input-bottom-focused: var(--primary, #1ea7fd);--text-on-color: var(--spectric-text-on-color, #ffffff);--text-on-color-disabled: var(--spectric-text-on-color-disabled, #8d8d8d);--text-placeholder: rgba(22, 22, 22, .4);--text-primary: var(--spectric-text-primary, #161616);--text-secondary: var(--spectric-text-secondary, #525252)}spectric-input .inputWrapper{color:var(--text-secondary)}spectric-input .inputWrapper input{box-sizing:border-box;margin:0;vertical-align:baseline;font-size:.875rem;font-weight:400;line-height:1.28572;letter-spacing:.16px;outline:transparent solid 2px;outline-offset:-2px;border:none;padding:0 1rem;background-color:var(--input-color);color:var(--text-primary, #161616);font-family:inherit;inline-size:100%;block-size:2.5rem}spectric-input .inputWrapper .inputContainer:active:after,spectric-input .inputContainer:focus-within:after{border-bottom-color:var(--input-bottom-focused);width:calc(100% - 5px);transition:width .4s ease-in-out}spectric-input .inputWrapper input:read-only{background-color:transparent;border-bottom-color:var(--border-disabled)}spectric-input .inputContainer{position:relative;border-radius:var(--border-radius);overflow:hidden}spectric-input .inputContainer:after{content:"";width:0px;transition:background-color .4s cubic-bezier(.2,0,.38,.9),border-bottom-color .4s cubic-bezier(.2,0,.38,.9);border-bottom-color:var(--input-bottom);border-bottom-style:solid;border-bottom-width:1px;position:absolute;left:2.5px;bottom:0}spectric-input #helper-text{height:18px}spectric-input[variant=password] spectric-button{position:absolute;right:4px;bottom:3px}spectric-input .checkbox{display:flex;justify-self:center}spectric-query{font-family:monospace}spectric-query .autocomplete{color:var(--spectric-text-primary, #161616);border-radius:0em 0em var(--spectric-border-radius, .4em) var(--spectric-border-radius, .4em);background-color:var(--spectric-background, #ffffff);border:1px solid var(--spectric-background-hover, rgba(141, 141, 141, .12));max-height:300px;border-top:0px;margin:-18px 0 0;position:fixed;top:anchor(bottom);justify-self:anchor-center;text-align:center}spectric-query .autocomplete .optiontype{float:left;max-width:10px}spectric-query .autocomplete .label{position:absolute;right:0}spectric-query .autocomplete .option.active,spectric-query .autocomplete .option:hover{background-color:var(--spectric-background-hover, rgba(141, 141, 141, .12));border-bottom:1px solid var(--primary, #1ea7fd)}spectric-query .autocomplete .option{border-bottom:1px solid transparent;padding:8px}.query-bar-date-quick-select{display:flex;justify-content:space-evenly}spectric-pagination .spectric-pagination-container{display:flex;justify-content:space-between;align-items:center}spectric-pagination .spectric-pagination-text{flex-grow:1;text-align:center}spectric-table{display:flex;flex-direction:column}spectric-table tr{text-align:center}spectric-table-body tr:hover{background-color:color-mix(in srgb,var(--spectric-primary, #1ea7fd),transparent 70%)}spectric-table-header{display:table-header-group;font-weight:700}spectric-table div[role=table]{display:table}spectric-table-body{display:table-row-group}spectric-table-cell{display:contents;vertical-align:middle}spectric-table-cell td{position:relative}spectric-table td:hover:has(.filterable){border:1px solid var(--spectric-primary, #1ea7fd)}spectric-table td{border:1px solid transparent}spectric-table-cell .table-cell-actions{position:absolute;display:flex;width:100%;flex-direction:row-reverse;visibility:hidden;top:-10px}spectric-table-cell td:hover .table-cell-actions{visibility:unset}.spectric-tooltip-portal{position:fixed;z-index:9999;pointer-events:none;--spectric-tooltip-background: color-mix(in srgb,var(--spectric-background-inverse,#f4f4f4) 100%,var(--spectric-primary,#1ea7fd) 90%) }.spectric-tooltip-portal .tooltip-container{display:flex;justify-content:center;align-items:center}.spectric-tooltip-portal.top .tooltip-container{flex-direction:column-reverse}.spectric-tooltip-portal.bottom .tooltip-container{flex-direction:column}.spectric-tooltip-portal.left .tooltip-container{flex-direction:row-reverse}.spectric-tooltip-portal .tooltip-content{background:var(--spectric-tooltip-background);border-radius:var(--spectric-border-radius,.4em);box-shadow:0 0 .01em .01em color-mix(in srgb,var(--spectric-background-hover,rgba(141, 141, 141, .12)) 90%,var(--spectric-text-on-color,#ffffff) 90%);padding:.2em;color:var(--spectric-text-on-color,#ffffff)}.spectric-tooltip-portal .tooltip-caret{background:var(--spectric-tooltip-background)}.spectric-tooltip-portal.top .tooltip-caret,.spectric-tooltip-portal.bottom .tooltip-caret{inline-size:.75rem;block-size:.374rem}.spectric-tooltip-portal.left .tooltip-caret,.spectric-tooltip-portal.right .tooltip-caret{inline-size:.375rem;block-size:.75rem}.spectric-tooltip-portal.top .tooltip-caret{clip-path:polygon(0 0,50% 100%,100% 0)}.spectric-tooltip-portal.bottom .tooltip-caret{clip-path:polygon(0 100%,50% 0,100% 100%)}.spectric-tooltip-portal.left .tooltip-caret{clip-path:polygon(0 0,100% 50%,0 100%)}.spectric-tooltip-portal.right .tooltip-caret{clip-path:polygon(0 50%,100% 0,100% 100%)}
|
|
1
|
+
spectric-input{--input-color: var(--spectric-input-color, #f4f4f4);--border-radius: var(--spectric-border-radius, .4em);--input-bottom: var(--spectric-input-bottom, var(--spectric-button-primary, #a8a8a8));--input-bottom-focused: var(--primary, #1ea7fd);--text-on-color: var(--spectric-text-on-color, #ffffff);--text-on-color-disabled: var(--spectric-text-on-color-disabled, #8d8d8d);--text-placeholder: rgba(22, 22, 22, .4);--text-primary: var(--spectric-text-primary, #161616);--text-secondary: var(--spectric-text-secondary, #525252)}spectric-input .inputWrapper{color:var(--text-secondary)}spectric-input .inputWrapper input{box-sizing:border-box;margin:0;vertical-align:baseline;font-size:.875rem;font-weight:400;line-height:1.28572;letter-spacing:.16px;outline:transparent solid 2px;outline-offset:-2px;border:none;padding:0 1rem;background-color:var(--input-color);color:var(--text-primary, #161616);font-family:inherit;inline-size:100%;block-size:2.5rem}spectric-input .inputWrapper .inputContainer:active:after,spectric-input .inputContainer:focus-within:after{border-bottom-color:var(--input-bottom-focused);width:calc(100% - 5px);transition:width .4s ease-in-out}spectric-input .inputWrapper input:read-only{background-color:transparent;border-bottom-color:var(--border-disabled)}spectric-input .inputContainer{position:relative;border-radius:var(--border-radius);overflow:hidden}spectric-input .inputContainer:after{content:"";width:0px;transition:background-color .4s cubic-bezier(.2,0,.38,.9),border-bottom-color .4s cubic-bezier(.2,0,.38,.9);border-bottom-color:var(--input-bottom);border-bottom-style:solid;border-bottom-width:1px;position:absolute;left:2.5px;bottom:0}spectric-input #helper-text{height:18px}spectric-input[variant=password] spectric-button{position:absolute;right:4px;bottom:3px}spectric-input .checkbox{display:flex;justify-self:center}spectric-query{font-family:monospace}spectric-query .autocomplete{color:var(--spectric-text-primary, #161616);border-radius:0em 0em var(--spectric-border-radius, .4em) var(--spectric-border-radius, .4em);background-color:var(--spectric-background, #ffffff);border:1px solid var(--spectric-background-hover, rgba(141, 141, 141, .12));max-height:300px;border-top:0px;margin:-18px 0 0;position:fixed;top:anchor(bottom);justify-self:anchor-center;text-align:center}spectric-query .autocomplete .optiontype{float:left;max-width:10px}spectric-query .autocomplete .label{position:absolute;right:0}spectric-query .autocomplete .option.active,spectric-query .autocomplete .option:hover{background-color:var(--spectric-background-hover, rgba(141, 141, 141, .12));border-bottom:1px solid var(--primary, #1ea7fd)}spectric-query .autocomplete .option{border-bottom:1px solid transparent;padding:8px}.query-bar-date-quick-select{display:flex;justify-content:space-evenly}spectric-pagination .spectric-pagination-container{display:flex;justify-content:space-between;align-items:center}spectric-pagination .spectric-pagination-text{flex-grow:1;text-align:center}spectric-table{display:flex;flex-direction:column;overflow:hidden}spectric-table .table-wrapper{overflow:auto;flex-grow:1}spectric-table tr{text-align:center}spectric-table-body tr:nth-child(odd){background-color:color-mix(in srgb,var(--spectric-primary, #1ea7fd),transparent 90%)}spectric-table-body tr:hover{background-color:color-mix(in srgb,var(--spectric-primary, #1ea7fd),transparent 70%)}spectric-table-header{display:table-header-group;font-weight:700;position:sticky;top:0;left:0;z-index:1;background:var(--spectric-background, #ffffff)}spectric-table-header td{vertical-align:middle}spectric-table-header .header-contents{position:relative}spectric-table-header .header-contents .sort-direction{position:absolute;right:0}spectric-table-header .header-contents.sortable{cursor:pointer}spectric-table-header .header-contents.sortable:hover .sort-direction.none:before{content:"⮁"}spectric-table div[role=table]{display:table;min-width:100%}spectric-table-body{display:table-row-group}spectric-table-cell{display:contents;vertical-align:middle}spectric-table-cell td{position:relative}spectric-table td:hover:has(.filterable){border:1px solid var(--spectric-primary, #1ea7fd)}spectric-table td{border:1px solid transparent}spectric-table-cell .table-cell-actions{position:absolute;display:flex;width:100%;flex-direction:row-reverse;visibility:hidden;top:-10px}spectric-table-cell td:hover .table-cell-actions{visibility:unset}spectric-table .table-checkbox-single spectric-button{--button-border-radius: 50%}spectric-input.table-checkbox-single[checked] spectric-button{--text-on-color: transparent;border-radius:50%;position:relative}spectric-input.table-checkbox-single[checked] spectric-button:before{position:absolute;content:" ";height:50%;width:50%;left:25%;top:25%;border-radius:50%;z-index:1;box-shadow:0 0 0 4px var(--input-color)}.spectric-tooltip-portal{position:fixed;z-index:9999;pointer-events:none;--spectric-tooltip-background: color-mix(in srgb,var(--spectric-background-inverse,#f4f4f4) 100%,var(--spectric-primary,#1ea7fd) 90%) }.spectric-tooltip-portal .tooltip-container{display:flex;justify-content:center;align-items:center}.spectric-tooltip-portal.top .tooltip-container{flex-direction:column-reverse}.spectric-tooltip-portal.bottom .tooltip-container{flex-direction:column}.spectric-tooltip-portal.left .tooltip-container{flex-direction:row-reverse}.spectric-tooltip-portal .tooltip-content{background:var(--spectric-tooltip-background);border-radius:var(--spectric-border-radius,.4em);box-shadow:0 0 .01em .01em color-mix(in srgb,var(--spectric-background-hover,rgba(141, 141, 141, .12)) 90%,var(--spectric-text-on-color,#ffffff) 90%);padding:.2em;color:var(--spectric-text-on-color,#ffffff)}.spectric-tooltip-portal .tooltip-caret{background:var(--spectric-tooltip-background)}.spectric-tooltip-portal.top .tooltip-caret,.spectric-tooltip-portal.bottom .tooltip-caret{inline-size:.75rem;block-size:.374rem}.spectric-tooltip-portal.left .tooltip-caret,.spectric-tooltip-portal.right .tooltip-caret{inline-size:.375rem;block-size:.75rem}.spectric-tooltip-portal.top .tooltip-caret{clip-path:polygon(0 0,50% 100%,100% 0)}.spectric-tooltip-portal.bottom .tooltip-caret{clip-path:polygon(0 100%,50% 0,100% 100%)}.spectric-tooltip-portal.left .tooltip-caret{clip-path:polygon(0 0,100% 50%,0 100%)}.spectric-tooltip-portal.right .tooltip-caret{clip-path:polygon(0 50%,100% 0,100% 100%)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function once(func: Function): (...args: any[]) => any;
|
package/dist/utils/spread.d.ts
CHANGED
package/package.json
CHANGED
package/src/components/input.ts
CHANGED
|
@@ -303,7 +303,6 @@ export class SpectricInput extends LitElement implements InputProps {
|
|
|
303
303
|
<spectric-button @click=${() => {
|
|
304
304
|
this.checked = !this.checked;
|
|
305
305
|
this.value = Boolean(this.checked);
|
|
306
|
-
console.log(this.checked, this.value)
|
|
307
306
|
this.dispatchEvent(new Event("change", { bubbles: true }))
|
|
308
307
|
}} icon size=${this.size || "xxsmall"} variant=${this.checked ? "primary" : "secondary"}>${this.checked ? '✓' : "\u00A0"}</spectric-button>
|
|
309
308
|
${this.invalid || this.helperText ? html`<spectric-tooltip text=${this.invalid || this.helperText}></spectric-tooltip>` : null}
|
|
@@ -8,12 +8,12 @@ import { ButtonSizesTypes } from '../Button';
|
|
|
8
8
|
export type { PaginationProps, PaginationChangeProps, PaginationEvents }
|
|
9
9
|
|
|
10
10
|
interface PaginationChangeProps {
|
|
11
|
-
page
|
|
12
|
-
pageSize
|
|
11
|
+
page?: number;
|
|
12
|
+
pageSize?: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
interface PaginationProps extends PaginationChangeProps {
|
|
16
|
-
size
|
|
16
|
+
size?: ButtonSizesTypes
|
|
17
17
|
totalItems?: number
|
|
18
18
|
pageSizeOptions?: number[]
|
|
19
19
|
}
|
|
@@ -93,8 +93,8 @@ export class PaginationElement extends LitElement implements PaginationProps {
|
|
|
93
93
|
${pageText}
|
|
94
94
|
</div>
|
|
95
95
|
<div>
|
|
96
|
-
<spectric-button size=${this.size} ?disabled=${this.page === 1} @click=${this._handlePageDown} icon
|
|
97
|
-
<spectric-button size=${this.size} ?disabled=${nextPageDisabled} @click=${this._handlePageUp} icon
|
|
96
|
+
<spectric-button size=${this.size} ?disabled=${this.page === 1} @click=${this._handlePageDown} icon>🠈</spectric-button>
|
|
97
|
+
<spectric-button size=${this.size} ?disabled=${nextPageDisabled} @click=${this._handlePageUp} icon>🠊</spectric-button>
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
|
100
100
|
`: null}
|
|
@@ -115,7 +115,7 @@ declare global {
|
|
|
115
115
|
namespace JSX {
|
|
116
116
|
interface IntrinsicElements {
|
|
117
117
|
/**
|
|
118
|
-
* @see {@link
|
|
118
|
+
* @see {@link PaginationElement}
|
|
119
119
|
*/
|
|
120
120
|
[PaginationElementTag]: ReactElementWithPropsAndEvents<PaginationElement, PaginationProps, PaginationEvents>;
|
|
121
121
|
}
|
|
@@ -124,7 +124,7 @@ declare global {
|
|
|
124
124
|
namespace JSX {
|
|
125
125
|
interface IntrinsicElements {
|
|
126
126
|
/**
|
|
127
|
-
* @see {@link
|
|
127
|
+
* @see {@link PaginationElement}
|
|
128
128
|
*/
|
|
129
129
|
[PaginationElementTag]: ReactElementWithPropsAndEvents<PaginationElement, PaginationProps, PaginationEvents>
|
|
130
130
|
}
|
|
@@ -133,32 +133,34 @@ export class SpectricQuery extends LitElement implements IQueryProps {
|
|
|
133
133
|
protected _input!: SpectricInput;
|
|
134
134
|
_parseQuery = (e: InputEvent | undefined = undefined) => {
|
|
135
135
|
let ast;
|
|
136
|
-
if (e && e?.currentTarget
|
|
136
|
+
if (e && e?.currentTarget) {
|
|
137
137
|
this.value = (e.currentTarget as HTMLInputElement).value
|
|
138
138
|
}
|
|
139
|
-
if (this.value
|
|
140
|
-
|
|
139
|
+
if (this.value) {
|
|
140
|
+
try {
|
|
141
|
+
if (e && e.data == "(") {
|
|
142
|
+
//Auto close parentheses or parsing and suggestions fail
|
|
143
|
+
this.value = this.value + " )"
|
|
144
|
+
this._input.setSelectionRange(this.value.length - 2, this.value.length - 2)
|
|
145
|
+
}
|
|
146
|
+
let value = this.value;
|
|
147
|
+
if (this._input.selectionStart !== null) {
|
|
148
|
+
value = value.substring(0, this._input.selectionStart) + "@kuery-cursor@" + value.substring(this._input.selectionStart)
|
|
149
|
+
}
|
|
150
|
+
//FIXME: make auto complete work well.
|
|
151
|
+
let suggestions = kuery.parse(value, { parseCursor: true, cursorSymbol: "@kuery-cursor@", allowLeadingWildcards: false }) as unknown as Suggestion;
|
|
152
|
+
this.autoComplete(suggestions)
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
// this.completions = []
|
|
155
|
+
// this._input.invalid = true;
|
|
156
|
+
// let [expect, _, arrow] = e.message.split("\n")
|
|
157
|
+
// this._input.invalidText = html`  ${arrow} ${expect}`;
|
|
158
|
+
return
|
|
159
|
+
}
|
|
141
160
|
}
|
|
142
161
|
try {
|
|
143
|
-
if (e && e.data == "(") {
|
|
144
|
-
//Auto close parentheses or parsing and suggestions fail
|
|
145
|
-
this.value = this.value + " )"
|
|
146
|
-
this._input.setSelectionRange(this.value.length - 2, this.value.length - 2)
|
|
147
|
-
}
|
|
148
|
-
let value = this.value;
|
|
149
|
-
if (this._input.selectionStart !== null) {
|
|
150
|
-
value = value.substring(0, this._input.selectionStart) + "@kuery-cursor@" + value.substring(this._input.selectionStart)
|
|
151
|
-
}
|
|
152
|
-
//FIXME: make auto complete work well.
|
|
153
|
-
let suggestions = kuery.parse(value, { parseCursor: true, cursorSymbol: "@kuery-cursor@", allowLeadingWildcards: false }) as unknown as Suggestion;
|
|
154
|
-
this.autoComplete(suggestions)
|
|
155
|
-
|
|
156
162
|
ast = kuery.parse(this.value, { allowLeadingWildcards: false });
|
|
157
|
-
} catch (
|
|
158
|
-
// this.completions = []
|
|
159
|
-
// this._input.invalid = true;
|
|
160
|
-
// let [expect, _, arrow] = e.message.split("\n")
|
|
161
|
-
// this._input.invalidText = html`  ${arrow} ${expect}`;
|
|
163
|
+
} catch (error: any) {
|
|
162
164
|
return
|
|
163
165
|
}
|
|
164
166
|
let output
|
|
@@ -288,6 +290,8 @@ export class SpectricQuery extends LitElement implements IQueryProps {
|
|
|
288
290
|
} else {
|
|
289
291
|
this.value = prefix + this.value.substring(completion.end)
|
|
290
292
|
}
|
|
293
|
+
//Important if the underlying inputs value hasn't been set the setSelectionRange will fail to set a range greater than the inputs current value
|
|
294
|
+
this._input.value = this.value
|
|
291
295
|
this._input.setSelectionRange(insertIndex, insertIndex)
|
|
292
296
|
if (completion.onSelect) {
|
|
293
297
|
let value = await completion.onSelect()
|
|
@@ -299,6 +303,7 @@ export class SpectricQuery extends LitElement implements IQueryProps {
|
|
|
299
303
|
this.completionIndex = 0;
|
|
300
304
|
this.completions = []
|
|
301
305
|
this._parseQuery()
|
|
306
|
+
this._input.focus()
|
|
302
307
|
}
|
|
303
308
|
_handleArrows = (e: KeyboardEvent) => {
|
|
304
309
|
if (e.key === "Escape") {
|
|
@@ -43,7 +43,7 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
|
|
|
43
43
|
_handleFilterOut = () => {
|
|
44
44
|
let value = undefined;
|
|
45
45
|
if (this.column.key && typeof this.row === "object") {
|
|
46
|
-
value =
|
|
46
|
+
value = rowGetValue(this.row as Record<any, any>, this.column.key)
|
|
47
47
|
}
|
|
48
48
|
this.dispatchEvent(new CustomEvent<FilterEvent<T>>("filter", {
|
|
49
49
|
composed: true,
|
|
@@ -59,7 +59,7 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
|
|
|
59
59
|
_handleFilterFor = () => {
|
|
60
60
|
let value = undefined;
|
|
61
61
|
if (this.column.key && typeof this.row === "object") {
|
|
62
|
-
value =
|
|
62
|
+
value = rowGetValue(this.row as Record<any, any>, this.column.key)
|
|
63
63
|
}
|
|
64
64
|
this.dispatchEvent(new CustomEvent<FilterEvent<T>>("filter", {
|
|
65
65
|
composed: true,
|
|
@@ -77,8 +77,7 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
|
|
|
77
77
|
if (this.column.render) {
|
|
78
78
|
rendered = this.column.render(this.row, this.table)
|
|
79
79
|
} else if (this.column.key && typeof this.row === 'object') {
|
|
80
|
-
|
|
81
|
-
rendered = getValue(this.row as any, this.column.key)
|
|
80
|
+
rendered = rowGetValue(this.row as any, this.column.key)
|
|
82
81
|
} else {
|
|
83
82
|
rendered = html`error`
|
|
84
83
|
}
|
|
@@ -134,11 +133,11 @@ declare global {
|
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
|
|
137
|
-
const
|
|
136
|
+
export const rowGetValue = (context: Record<string, any>, key: string) => {
|
|
138
137
|
let path = key.split(".")
|
|
139
138
|
let value = context[path[0]]
|
|
140
139
|
if (path.length > 1) {
|
|
141
|
-
value =
|
|
140
|
+
value = rowGetValue(value, path.slice(1).join("."))
|
|
142
141
|
}
|
|
143
142
|
return value
|
|
144
143
|
}
|
|
@@ -4,7 +4,7 @@ import { customElement, property, } from 'lit/decorators.js';
|
|
|
4
4
|
import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
|
|
5
5
|
import "./table.css"
|
|
6
6
|
export const TableHeaderElementTag = "spectric-table-header"
|
|
7
|
-
import { ColumnSettings } from './table';
|
|
7
|
+
import { ColumnSettings, TableSortDirection } from './table';
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
interface HeaderProps<T> {
|
|
@@ -22,6 +22,20 @@ export class TableHeaderElement<T> extends LitElement implements HeaderProps<T>
|
|
|
22
22
|
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
23
23
|
return this
|
|
24
24
|
}
|
|
25
|
+
_handleSortChange = (column: ColumnSettings<T>) => {
|
|
26
|
+
column = JSON.parse(JSON.stringify(column))// Clone the column to not mutate the original object
|
|
27
|
+
if (!column.sortable) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
if (column.sortDirection === TableSortDirection.none || column.sortDirection === undefined) {
|
|
31
|
+
column.sortDirection = TableSortDirection.ascending
|
|
32
|
+
} else if (column.sortDirection === TableSortDirection.ascending) {
|
|
33
|
+
column.sortDirection = TableSortDirection.decending
|
|
34
|
+
} else if (column.sortDirection === TableSortDirection.decending) {
|
|
35
|
+
column.sortDirection = TableSortDirection.none
|
|
36
|
+
}
|
|
37
|
+
this.dispatchEvent(new CustomEvent<ColumnSettings<T>>("sortChange", { detail: column }))
|
|
38
|
+
}
|
|
25
39
|
protected render(): unknown {
|
|
26
40
|
|
|
27
41
|
return html`
|
|
@@ -31,7 +45,12 @@ export class TableHeaderElement<T> extends LitElement implements HeaderProps<T>
|
|
|
31
45
|
if (column.filterable) {
|
|
32
46
|
//classes.push("filterable")
|
|
33
47
|
}
|
|
34
|
-
|
|
48
|
+
if (column.sortable) {
|
|
49
|
+
classes.push("sortable")
|
|
50
|
+
}
|
|
51
|
+
let sortDirection = column.sortDirection === TableSortDirection.ascending ? `🠉` : column.sortDirection == TableSortDirection.decending ? `🠋` : ``
|
|
52
|
+
let sortClass = column.sortDirection || TableSortDirection.none
|
|
53
|
+
return html`<td @click=${() => this._handleSortChange(column)}><div class=${classes.join(" ")}>${column.title || column.key} <span class="sort-direction ${sortClass}">${sortDirection}</span></div></td>`
|
|
35
54
|
})}
|
|
36
55
|
</tr>
|
|
37
56
|
`
|
|
@@ -39,7 +58,7 @@ export class TableHeaderElement<T> extends LitElement implements HeaderProps<T>
|
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
interface TableHeaderEvents {
|
|
42
|
-
'
|
|
61
|
+
'sortChange': (event: CustomEvent<ColumnSettings<any>>) => void; //TODO sort events
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
declare global {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { once } from "../../utils/once";
|
|
2
|
+
import { rowGetValue } from "./cell";
|
|
3
|
+
import { ColumnSettings, TableSortDirection } from "./table";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a chain of sort functions so you can perform multi column sorting.
|
|
7
|
+
*/
|
|
8
|
+
export const createSortChain = <T>(sorts: ColumnSettings<T>[]) => {
|
|
9
|
+
return sorts.map(({ key, sortDirection, compareFn }) => {
|
|
10
|
+
return (a: T, b: T) => {
|
|
11
|
+
if (!key) {
|
|
12
|
+
return undefined
|
|
13
|
+
}
|
|
14
|
+
let v1 = rowGetValue(a as Record<string, any>, key)
|
|
15
|
+
let v2 = rowGetValue(b as Record<string, any>, key)
|
|
16
|
+
if (compareFn) {
|
|
17
|
+
return compareFn(a, b)
|
|
18
|
+
}
|
|
19
|
+
if (typeof v1 === "number" || typeof v1 === "bigint" || typeof v1 === "boolean") {
|
|
20
|
+
//@ts-ignore
|
|
21
|
+
let sort: number = v1 - v2 as unknown as number
|
|
22
|
+
if (sortDirection === TableSortDirection.decending) {
|
|
23
|
+
sort = sort * -1
|
|
24
|
+
}
|
|
25
|
+
return sort
|
|
26
|
+
} else if (typeof v1 === "string") {
|
|
27
|
+
return v1.localeCompare(v2) * (sortDirection === TableSortDirection.decending ? -1 : 1)
|
|
28
|
+
}
|
|
29
|
+
once(() => { console.error(`Unable to sort type ${typeof v1}`) })
|
|
30
|
+
return undefined
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -1,20 +1,53 @@
|
|
|
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;
|
|
4
9
|
}
|
|
5
10
|
spectric-table tr{
|
|
6
11
|
text-align: center;
|
|
7
12
|
}
|
|
8
13
|
|
|
14
|
+
spectric-table-body tr:nth-child(odd){
|
|
15
|
+
background-color: color-mix(in srgb, var(--spectric-primary, #1ea7fd), transparent 90%);
|
|
16
|
+
}
|
|
9
17
|
spectric-table-body tr:hover{
|
|
10
18
|
background-color: color-mix(in srgb, var(--spectric-primary, #1ea7fd), transparent 70%)
|
|
11
19
|
}
|
|
12
20
|
spectric-table-header{
|
|
13
21
|
display: table-header-group;
|
|
14
22
|
font-weight: bold;
|
|
23
|
+
position: sticky;
|
|
24
|
+
top: 0px;
|
|
25
|
+
left: 0px;
|
|
26
|
+
z-index: 1;
|
|
27
|
+
background: var(--spectric-background, #ffffff);
|
|
28
|
+
}
|
|
29
|
+
spectric-table-header td {
|
|
30
|
+
vertical-align: middle;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
spectric-table-header .header-contents {
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
spectric-table-header .header-contents .sort-direction {
|
|
38
|
+
position: absolute;
|
|
39
|
+
right: 0;
|
|
40
|
+
}
|
|
41
|
+
spectric-table-header .header-contents.sortable {
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
spectric-table-header .header-contents.sortable:hover .sort-direction.none::before
|
|
45
|
+
{
|
|
46
|
+
content: "\2B81";
|
|
15
47
|
}
|
|
16
48
|
spectric-table div[role="table"]{
|
|
17
49
|
display: table;
|
|
50
|
+
min-width: 100%;
|
|
18
51
|
}
|
|
19
52
|
spectric-table-body {
|
|
20
53
|
display: table-row-group;
|
|
@@ -43,4 +76,23 @@ spectric-table-cell .table-cell-actions{
|
|
|
43
76
|
}
|
|
44
77
|
spectric-table-cell td:hover .table-cell-actions{
|
|
45
78
|
visibility: unset;
|
|
79
|
+
}
|
|
80
|
+
spectric-table .table-checkbox-single spectric-button{
|
|
81
|
+
--button-border-radius: 50%;
|
|
82
|
+
}
|
|
83
|
+
spectric-input.table-checkbox-single[checked] spectric-button{
|
|
84
|
+
--text-on-color: transparent;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
position: relative;
|
|
87
|
+
}
|
|
88
|
+
spectric-input.table-checkbox-single[checked] spectric-button::before {
|
|
89
|
+
position: absolute;
|
|
90
|
+
content: " ";
|
|
91
|
+
height: 50%;
|
|
92
|
+
width: 50%;
|
|
93
|
+
left: 25%;
|
|
94
|
+
top: 25%;
|
|
95
|
+
border-radius: 50%;
|
|
96
|
+
z-index: 1;
|
|
97
|
+
box-shadow: 0px 0px 0px 4px var(--input-color);
|
|
46
98
|
}
|
|
@@ -9,26 +9,57 @@ export const TableElementTag = "spectric-table"
|
|
|
9
9
|
import { spreadProps } from '../../utils/spread';
|
|
10
10
|
import { PaginationChangeProps, PaginationProps } from '../pagination';
|
|
11
11
|
import { FilterEvent } from './cell';
|
|
12
|
+
import { createSortChain } from './sorting';
|
|
12
13
|
export type { TableProps, TableEvents }
|
|
13
14
|
|
|
14
15
|
export type DomRenderable = HTMLElement | TemplateResult | string | number | null
|
|
16
|
+
export enum TableSelectOptions {
|
|
17
|
+
multi = "multi",
|
|
18
|
+
single = "single",
|
|
19
|
+
none = "none"
|
|
20
|
+
}
|
|
15
21
|
|
|
22
|
+
export enum TableSortOption {
|
|
23
|
+
multi = "multi",
|
|
24
|
+
single = "single",
|
|
25
|
+
}
|
|
26
|
+
export type TableSortOptionTypes = `${TableSortOption}`
|
|
27
|
+
export enum TableSortDirection {
|
|
28
|
+
ascending = "ascending",
|
|
29
|
+
decending = "decending",
|
|
30
|
+
none = "none"
|
|
31
|
+
}
|
|
32
|
+
export type TableSortDirectionTypes = `${TableSortDirection}`
|
|
16
33
|
export type ColumnSettings<T> = {
|
|
17
34
|
width?: number
|
|
18
35
|
whiteSpace?: "nowrap";
|
|
19
36
|
hidden?: boolean
|
|
20
37
|
sortable?: boolean
|
|
38
|
+
sortDirection?: TableSortDirectionTypes
|
|
21
39
|
filterable?: boolean
|
|
22
40
|
title?: DomRenderable
|
|
41
|
+
/**
|
|
42
|
+
* Key to used for getting data from an object for a cell
|
|
43
|
+
*/
|
|
23
44
|
key?: string
|
|
45
|
+
/**
|
|
46
|
+
* Render function to render a table cell for displaying custom html
|
|
47
|
+
*/
|
|
24
48
|
render?: (row: T, table: TableElement<T>) => DomRenderable
|
|
49
|
+
/**
|
|
50
|
+
* Custom comparator function for sorting
|
|
51
|
+
*/
|
|
52
|
+
compareFn?: ((a: T, b: T) => number) | undefined
|
|
25
53
|
}
|
|
26
|
-
type
|
|
27
|
-
interface
|
|
54
|
+
export type TableSelectOptionsTypes = `${TableSelectOptions}`
|
|
55
|
+
export interface TableDataOptions<T> {
|
|
28
56
|
pagination?: PaginationProps
|
|
29
57
|
columns: ColumnSettings<T>[]
|
|
58
|
+
}
|
|
59
|
+
interface TableProps<T> extends TableDataOptions<T> {
|
|
30
60
|
data: T[]
|
|
31
|
-
select
|
|
61
|
+
select: TableSelectOptionsTypes
|
|
62
|
+
sort?: TableSortOptionTypes
|
|
32
63
|
}
|
|
33
64
|
|
|
34
65
|
type DomEvent<T> = Event & {
|
|
@@ -37,30 +68,84 @@ type DomEvent<T> = Event & {
|
|
|
37
68
|
/**
|
|
38
69
|
* React example
|
|
39
70
|
* <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
|
-
*
|
|
71
|
+
*
|
|
41
72
|
*/
|
|
42
73
|
@customElement(TableElementTag)
|
|
43
74
|
export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
44
75
|
@property({ type: Array, attribute: false })
|
|
45
76
|
data: T[] = [];
|
|
46
77
|
@property({ type: Object, attribute: false })
|
|
47
|
-
pagination?: PaginationProps
|
|
78
|
+
pagination?: PaginationProps;
|
|
48
79
|
@property({ attribute: false })
|
|
49
80
|
columns: ColumnSettings<T>[] = [];
|
|
50
|
-
@property({ type: String, reflect:
|
|
51
|
-
select
|
|
81
|
+
@property({ type: String, reflect: true })
|
|
82
|
+
select: TableSelectOptionsTypes = TableSelectOptions.none;
|
|
83
|
+
@property({ type: String, reflect: true })
|
|
84
|
+
sort: TableSortOptionTypes = TableSortOption.single;
|
|
85
|
+
|
|
86
|
+
static getDefaultDataSorterAndPaginatior<T>(data: T[]) {
|
|
87
|
+
return (props: TableDataOptions<T>) => {
|
|
88
|
+
let sorts = props.columns.filter(column => column.sortable && column.sortDirection && column.sortDirection !== TableSortDirection.none)
|
|
89
|
+
let rows = [...data] // Need to copy the array to prevent mutating the ordering of the original data
|
|
90
|
+
if (sorts.length) {
|
|
91
|
+
let sortChain = createSortChain(sorts)
|
|
92
|
+
rows.sort((a, b) => {
|
|
93
|
+
for (let sort of sortChain) {
|
|
94
|
+
let result = sort(a, b)
|
|
95
|
+
if (result) {
|
|
96
|
+
return result
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return 0
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!props.pagination) {
|
|
104
|
+
return rows
|
|
105
|
+
}
|
|
106
|
+
let { page, pageSize } = props.pagination
|
|
107
|
+
if (!page || !pageSize) {
|
|
108
|
+
return rows
|
|
109
|
+
}
|
|
110
|
+
return !props.pagination ? rows : rows.slice((page - 1) * pageSize, (page) * pageSize)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
52
114
|
private _handlePaginationChange = (e: CustomEvent<PaginationChangeProps>) => {
|
|
53
115
|
e.preventDefault()
|
|
54
116
|
e.stopPropagation()
|
|
55
117
|
if (this.pagination) {
|
|
56
|
-
this.pagination.
|
|
57
|
-
|
|
118
|
+
let pagination = { ...this.pagination, ...e.detail }
|
|
119
|
+
|
|
120
|
+
let { totalItems, page, pageSize } = pagination
|
|
121
|
+
if (totalItems && page && pageSize && ((page - 1) * pageSize) > totalItems) {
|
|
122
|
+
pagination.page = 1
|
|
123
|
+
}
|
|
124
|
+
this.pagination = pagination
|
|
58
125
|
}
|
|
59
126
|
this._emitChange()
|
|
60
127
|
};
|
|
128
|
+
private _handleSortChange = (e: CustomEvent<ColumnSettings<T>>) => {
|
|
129
|
+
let columnSetting = e.detail
|
|
130
|
+
let column = this.columns.find(col => col.key == columnSetting.key)
|
|
131
|
+
if (!column) {
|
|
132
|
+
console.warn("Unable to find sort column")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
if (this.sort == TableSortOption.single) {
|
|
136
|
+
//Single column sort so we reset the sort direction for all columns
|
|
137
|
+
this.columns.forEach(col => {
|
|
138
|
+
col.sortDirection = TableSortDirection.none
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
column.sortDirection = columnSetting.sortDirection;
|
|
142
|
+
this.columns = [...this.columns]
|
|
143
|
+
this._emitChange()
|
|
144
|
+
}
|
|
145
|
+
|
|
61
146
|
private _emitChange = () => {
|
|
62
|
-
let { pagination } = this
|
|
63
|
-
this.dispatchEvent(new CustomEvent<
|
|
147
|
+
let { pagination, columns } = this
|
|
148
|
+
this.dispatchEvent(new CustomEvent<TableDataOptions<T>>("change", { detail: { pagination, columns } }))
|
|
64
149
|
}
|
|
65
150
|
//@ts-ignore
|
|
66
151
|
private __DO_NOT_USE_filter = () => {
|
|
@@ -68,7 +153,7 @@ export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
|
68
153
|
this.dispatchEvent(new CustomEvent<FilterEvent<T>>("filter"))
|
|
69
154
|
}
|
|
70
155
|
@state()
|
|
71
|
-
selected: T[] = [];
|
|
156
|
+
private selected: T[] = [];
|
|
72
157
|
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
73
158
|
return this
|
|
74
159
|
}
|
|
@@ -79,27 +164,27 @@ export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
|
79
164
|
} else {
|
|
80
165
|
this.selected = []
|
|
81
166
|
}
|
|
82
|
-
this.dispatchEvent(new CustomEvent("
|
|
167
|
+
this.dispatchEvent(new CustomEvent("selected", { detail: this.selected }))
|
|
83
168
|
}
|
|
84
169
|
protected render(): unknown {
|
|
85
170
|
let columns = this.columns.filter(column => !column.hidden)
|
|
86
|
-
if (this.select) {
|
|
171
|
+
if (this.select !== TableSelectOptions.none) {
|
|
87
172
|
columns.unshift({
|
|
88
173
|
title: this.select === "multi" ? html`<spectric-input variant="checkbox" @change=${this._handleSelectAllChange} .helperText=${"Select All"}></spectric-input>` : null,
|
|
89
174
|
render: (row) => {
|
|
90
|
-
return html`<spectric-input variant="checkbox" .checked=${this.selected.includes(row)} @change=${(e: DomEvent<HTMLInputElement>) => {
|
|
175
|
+
return html`<spectric-input variant="checkbox" class="table-checkbox-${this.select}" .checked=${this.selected.includes(row)} @change=${(e: DomEvent<HTMLInputElement>) => {
|
|
91
176
|
e.stopPropagation()
|
|
92
177
|
if (this.select === "single") {
|
|
93
178
|
this.selected = []
|
|
94
179
|
}
|
|
95
180
|
if (e.target.checked) {
|
|
96
181
|
this.selected.push(row)
|
|
97
|
-
this.dispatchEvent(new CustomEvent("
|
|
182
|
+
this.dispatchEvent(new CustomEvent("selected", { detail: this.selected }))
|
|
98
183
|
} else {
|
|
99
184
|
let index = this.selected.findIndex(value => value === row)
|
|
100
185
|
if (index !== -1) {
|
|
101
186
|
this.selected.splice(index, 1)
|
|
102
|
-
this.dispatchEvent(new CustomEvent("
|
|
187
|
+
this.dispatchEvent(new CustomEvent("selected", { detail: this.selected }))
|
|
103
188
|
}
|
|
104
189
|
}
|
|
105
190
|
}}></spectric-input>`
|
|
@@ -108,9 +193,11 @@ export class TableElement<T> extends LitElement implements TableProps<T> {
|
|
|
108
193
|
}
|
|
109
194
|
|
|
110
195
|
return html`
|
|
111
|
-
<div
|
|
112
|
-
<
|
|
113
|
-
|
|
196
|
+
<div class="table-wrapper">
|
|
197
|
+
<div role="table">
|
|
198
|
+
<spectric-table-header .columns=${columns} @sortChange=${this._handleSortChange}></spectric-table-header>
|
|
199
|
+
<spectric-table-body .columns=${columns} .data=${this.data} .table=${this}></spectric-table-body>
|
|
200
|
+
</div>
|
|
114
201
|
</div>
|
|
115
202
|
${this.pagination ? html`<spectric-pagination ${spreadProps(this.pagination)} @change=${this._handlePaginationChange}></spectric-pagination>` : null}
|
|
116
203
|
`;
|
|
@@ -130,7 +217,7 @@ declare global {
|
|
|
130
217
|
namespace JSX {
|
|
131
218
|
interface IntrinsicElements {
|
|
132
219
|
/**
|
|
133
|
-
* @see {@link
|
|
220
|
+
* @see {@link TableElement}
|
|
134
221
|
*/
|
|
135
222
|
[TableElementTag]: ReactElementWithPropsAndEvents<TableElement<any>, TableProps<any>, TableEvents>;
|
|
136
223
|
}
|
|
@@ -139,7 +226,7 @@ declare global {
|
|
|
139
226
|
namespace JSX {
|
|
140
227
|
interface IntrinsicElements {
|
|
141
228
|
/**
|
|
142
|
-
* @see {@link
|
|
229
|
+
* @see {@link TableElement}
|
|
143
230
|
*/
|
|
144
231
|
[TableElementTag]: ReactElementWithPropsAndEvents<TableElement<any>, TableProps<any>, TableEvents>
|
|
145
232
|
}
|
|
@@ -210,7 +210,7 @@ declare global {
|
|
|
210
210
|
namespace JSX {
|
|
211
211
|
interface IntrinsicElements {
|
|
212
212
|
/**
|
|
213
|
-
* @see {@link
|
|
213
|
+
* @see {@link TooltipElement}
|
|
214
214
|
*/
|
|
215
215
|
[TooltipElementTag]: ReactElementWithPropsAndEvents<TooltipElement, TooltipProps, TooltipEvents>;
|
|
216
216
|
}
|
|
@@ -219,7 +219,7 @@ declare global {
|
|
|
219
219
|
namespace JSX {
|
|
220
220
|
interface IntrinsicElements {
|
|
221
221
|
/**
|
|
222
|
-
* @see {@link
|
|
222
|
+
* @see {@link TooltipElement}
|
|
223
223
|
*/
|
|
224
224
|
[TooltipElementTag]: ReactElementWithPropsAndEvents<TooltipElement, TooltipProps, TooltipEvents>
|
|
225
225
|
}
|