@spectric/ui 0.0.21 → 0.0.23

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 (46) hide show
  1. package/dist/components/dialog/dialog.d.ts +1 -0
  2. package/dist/components/pagination/pagination.d.ts +1 -1
  3. package/dist/components/query_bar/QueryBar.d.ts +30 -10
  4. package/dist/components/query_bar/dateTimePopup.d.ts +2 -0
  5. package/dist/components/query_bar/geojsonPopup.d.ts +2 -0
  6. package/dist/components/query_bar/querylanguage/kuery/functions/geospatial.d.ts +19 -0
  7. package/dist/components/query_bar/querylanguage/outputTypes/toCQL.d.ts +2 -1
  8. package/dist/components/query_bar/querylanguage/outputTypes/toMongo.d.ts +6 -1
  9. package/dist/components/symbols.d.ts +6 -0
  10. package/dist/components/table/cell.d.ts +1 -1
  11. package/dist/components/table/table.d.ts +14 -1
  12. package/dist/custom-elements.json +6 -6
  13. package/dist/index.d.ts +4 -0
  14. package/dist/index.es.js +4405 -2803
  15. package/dist/index.es.js.map +1 -1
  16. package/dist/index.umd.js +361 -252
  17. package/dist/index.umd.js.map +1 -1
  18. package/dist/style.css +1 -1
  19. package/package.json +6 -1
  20. package/src/components/dialog/dialog.css.ts +29 -29
  21. package/src/components/dialog/dialog.ts +3 -1
  22. package/src/components/input.css +5 -0
  23. package/src/components/input.ts +50 -41
  24. package/src/components/pagination/pagination.ts +167 -113
  25. package/src/components/query_bar/QueryBar.ts +438 -187
  26. package/src/components/query_bar/dateTimePopup.ts +54 -0
  27. package/src/components/query_bar/geojsonPopup.ts +44 -0
  28. package/src/components/query_bar/querylanguage/kuery/ast/_generated_/kuery.js +1836 -2745
  29. package/src/components/query_bar/querylanguage/kuery/ast/ast.ts +15 -13
  30. package/src/components/query_bar/querylanguage/kuery/ast/kuery.peg +92 -126
  31. package/src/components/query_bar/querylanguage/kuery/functions/geospatial.ts +25 -0
  32. package/src/components/query_bar/querylanguage/kuery/functions/index.ts +9 -7
  33. package/src/components/query_bar/querylanguage/outputTypes/toCQL.ts +56 -34
  34. package/src/components/query_bar/querylanguage/outputTypes/toMongo.ts +46 -34
  35. package/src/components/symbols.ts +6 -0
  36. package/src/components/table/__tests__/table.spec.ts +2 -2
  37. package/src/components/table/cell.ts +28 -11
  38. package/src/components/table/header.ts +3 -2
  39. package/src/components/table/table.css +11 -4
  40. package/src/components/table/table.ts +75 -5
  41. package/src/components/table/virtualBody.ts +8 -3
  42. package/src/components/tooltip/popover.ts +263 -225
  43. package/src/stories/Dialog.stories.ts +59 -0
  44. package/src/stories/QueryBar.stories.ts +46 -37
  45. package/src/stories/fixtures/data.ts +229 -37
  46. package/src/stories/table.stories.ts +77 -29
@@ -1,13 +1,21 @@
1
1
  /* eslint-disable @kbn/eslint/require-license-header */
2
- import { FieldTypes, KueryNode } from '../..';
3
- import { wildcardSymbol } from '../kuery/node_types/wildcard';
2
+ import { FieldTypes, KueryNode } from "../..";
3
+ import { wildcardSymbol } from "../kuery/node_types/wildcard";
4
+ import { wktToGeoJSON } from "@terraformer/wkt";
4
5
 
5
6
  export const KQL_WILDCARD_SYMBOL = wildcardSymbol;
6
- export const KQL_NODE_TYPE_WILDCARD = 'wildcard';
7
- export type FunctionName = 'is' | 'and' | 'or' | 'not' | 'range' | 'exists' | 'nested';
7
+ export const KQL_NODE_TYPE_WILDCARD = "wildcard";
8
+ export type FunctionName =
9
+ | "is"
10
+ | "and"
11
+ | "or"
12
+ | "not"
13
+ | "range"
14
+ | "exists"
15
+ | "nested";
8
16
  const and = (node: KueryNode) => {
9
17
  const children = node.arguments || [];
10
- let query: any = { "$and": children.map((c: KueryNode) => toMongo(c)) }
18
+ let query: any = { $and: children.map((c: KueryNode) => toMongo(c)) };
11
19
 
12
20
  return query;
13
21
  };
@@ -17,62 +25,65 @@ const is = (node: KueryNode) => {
17
25
  } = node;
18
26
 
19
27
  let value = toMongo(valueArg);
20
- const isExistsQuery = valueArg.type === 'wildcard' && (valueArg.value as any) === '@kuery-wildcard@';
28
+ const isExistsQuery =
29
+ valueArg.type === "wildcard" &&
30
+ (valueArg.value as any) === "@kuery-wildcard@";
21
31
  if (isExistsQuery) {
22
- return exists(node)
32
+ return exists(node);
23
33
  }
24
- let query: any = {}
25
- query[toMongo(fieldNameArg)] = { "$eq": value }
34
+ let query: any = {};
35
+ query[toMongo(fieldNameArg)] = { $eq: value };
26
36
  return query;
27
37
  };
28
38
 
29
39
  const or = (node: KueryNode) => {
30
40
  const children = node.arguments || [];
31
41
  return {
32
- "$or": children
33
- .map((child: KueryNode) => {
34
- return toMongo(child);
35
- })
36
- }
37
-
42
+ $or: children.map((child: KueryNode) => {
43
+ return toMongo(child);
44
+ }),
45
+ };
38
46
  };
39
47
  const not = (node: KueryNode) => {
40
48
  const [fieldNameArg] = node.arguments;
41
49
  let query: any = {};
42
- query = { "$ne": toMongo(fieldNameArg) }
50
+ query = { $ne: toMongo(fieldNameArg) };
43
51
  return query;
44
52
  };
45
53
 
46
54
  const AST_TO_CQL = {
47
- gt: '$gt',
48
- lt: '$lt',
49
- gte: '$gte',
50
- lte: '$lte',
55
+ gt: "$gt",
56
+ lt: "$lt",
57
+ gte: "$gte",
58
+ lte: "$lte",
51
59
  };
52
60
  const range = (node: KueryNode) => {
53
61
  const [fieldNameArg, operator] = node.arguments;
54
- let valueArg = operator.value
62
+ let valueArg = operator.value;
55
63
  // @ts-ignore
56
64
  const opsign = AST_TO_CQL[operator.name];
57
65
  let value = toMongo(valueArg);
58
66
 
59
- let query: any = {}
60
- query[fieldNameArg.value] = {}
61
- query[fieldNameArg.value][opsign] = value
62
- return query
63
-
64
-
67
+ let query: any = {};
68
+ query[fieldNameArg.value] = {};
69
+ query[fieldNameArg.value][opsign] = value;
70
+ return query;
65
71
  };
66
72
  const exists = (node: KueryNode) => {
67
73
  const [fieldNameArg] = node.arguments;
68
- return { [toMongo(fieldNameArg)]: { $ne: null } }
74
+ return { [toMongo(fieldNameArg)]: { $ne: null } };
69
75
  };
70
76
 
71
77
  const nested = (node: KueryNode) => {
72
-
73
- console.warn("TODO Implement nested search", node)
74
- return ""
75
- }
78
+ console.warn("TODO Implement nested search", node);
79
+ return "";
80
+ };
81
+ const geospatial = (node: KueryNode) => {
82
+ const [fieldName, operator, value] = node.arguments;
83
+ if (operator == "within") {
84
+ return { [fieldName.value]: { $geoWithin: wktToGeoJSON(value.value) } };
85
+ }
86
+ };
76
87
  export const functions = {
77
88
  is,
78
89
  and,
@@ -80,7 +91,8 @@ export const functions = {
80
91
  not,
81
92
  range,
82
93
  exists,
83
- nested
94
+ nested,
95
+ geospatial,
84
96
  };
85
97
  const nodeTypes = {
86
98
  function: (node: KueryNode) => {
@@ -92,7 +104,7 @@ const nodeTypes = {
92
104
  },
93
105
  wildcard: (node: KueryNode) => {
94
106
  const { value } = node;
95
- return `/${value.split(KQL_WILDCARD_SYMBOL).join('.*')}/`;
107
+ return `/${value.split(KQL_WILDCARD_SYMBOL).join(".*")}/`;
96
108
  },
97
109
  };
98
110
 
@@ -0,0 +1,6 @@
1
+ export const EDIT = "🖉";
2
+ export const DELETE = "🗑";
3
+ export const ARROW_LEFT = "🠈";
4
+ export const ARROW_RIGHT = "🠊";
5
+ export const ARROW_UP = "🠉";
6
+ export const ARROW_DOWN = "🠋";
@@ -28,7 +28,7 @@ test.beforeEach(async ({ page }) => {
28
28
  test.describe("Spectric Table Tests", () => {
29
29
  test("MultiSelect Table Select All/Deselect All ", async ({ page }) => {
30
30
  await page.goto(
31
- "http://localhost:6006/iframe.html?globals=&args=&id=spectric-ui-components-ui-table--milti-select&viewMode=story"
31
+ "http://localhost:6006/iframe.html?globals=&args=&id=spectric-ui-components-ui-table--multi-select&viewMode=story"
32
32
  );
33
33
  let tableLocator = page.locator("spectric-table");
34
34
  tableLocator.waitFor();
@@ -149,7 +149,7 @@ test.describe("Spectric Table Tests", () => {
149
149
  page,
150
150
  }) => {
151
151
  await page.goto(
152
- "http://localhost:6006/iframe.html?globals=&args=&id=spectric-ui-components-ui-table--milti-select&viewMode=story"
152
+ "http://localhost:6006/iframe.html?globals=&args=&id=spectric-ui-components-ui-table--multi-select&viewMode=story"
153
153
  );
154
154
  let tableLocator = page.locator("spectric-table");
155
155
  await tableLocator.waitFor();
@@ -79,7 +79,11 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
79
79
  column: this.column,
80
80
  });
81
81
  };
82
- _displayTooltip = () => {
82
+ _displayOverflowTooltip = () => {
83
+ if (this.column.disableCellOverflowTooltip) {
84
+ this.overflow = null;
85
+ return;
86
+ }
83
87
  let div = this.querySelector("div.cell-contents");
84
88
  let span = this.querySelector("span");
85
89
  if (div && span) {
@@ -90,7 +94,7 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
90
94
  divBounds.width * divBounds.height
91
95
  ) {
92
96
  console.log(
93
- "We need to show a tooltip witht he content because we are overflowing"
97
+ "We need to show a tooltip with the content because we are overflowing"
94
98
  );
95
99
  this.overflow = this.getRenderedValue();
96
100
  this.updateComplete.then(() => {
@@ -118,16 +122,16 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
118
122
  protected render(): unknown {
119
123
  let rendered = this.getRenderedValue();
120
124
  let classes = ["cell-contents"];
121
- if (this.column.filterable) {
122
- classes.push("filterable");
125
+ if (this.column.filterable || this.column.cellActions?.length) {
126
+ classes.push("hasActions");
123
127
  }
124
-
128
+ const buttonSize = this.table.getCellActionButtonSize();
125
129
  let filterButtons = cache(
126
130
  this.column.filterable
127
- ? html`<div class="table-cell-actions">
131
+ ? html`
128
132
  <spectric-button
129
133
  @click=${this._handleFilterOut}
130
- size="tiny"
134
+ .size=${buttonSize}
131
135
  variant="text"
132
136
  icon
133
137
  tooltip="Filter Out Value"
@@ -135,15 +139,26 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
135
139
  >
136
140
  <spectric-button
137
141
  @click=${this._handleFilterFor}
138
- size="tiny"
142
+ .size=${buttonSize}
139
143
  variant="text"
140
144
  icon
141
145
  tooltip="Filter For Value"
142
146
  >+</spectric-button
143
147
  >
144
- </div>`
148
+ `
145
149
  : null
146
150
  );
151
+
152
+ let actions = (this.column.cellActions || []).map((action) => {
153
+ return html`<spectric-button
154
+ @click=${() => action.onClick(this.row, this.column)}
155
+ .size=${buttonSize}
156
+ variant="text"
157
+ icon
158
+ .tooltip=${action.tooltip}
159
+ >${action.icon}</spectric-button
160
+ >`;
161
+ });
147
162
  this.styleRules = {
148
163
  width: this.column.width ? this.column.width + "px" : null,
149
164
  whiteSpace: this.column.whiteSpace || null,
@@ -151,9 +166,11 @@ export class TableCellElement<T> extends LitElement implements CellProps<T> {
151
166
  return html`
152
167
  <td
153
168
  style=${styleMap(this.styleRules)}
154
- @mouseenter=${this._displayTooltip}
169
+ @mouseenter=${this._displayOverflowTooltip}
155
170
  >
156
- ${filterButtons}
171
+ <div class="table-cell-actions ${buttonSize}">
172
+ ${actions} ${filterButtons}
173
+ </div>
157
174
  <div class=${classes.join(" ")}>
158
175
  ${this.overflow
159
176
  ? html`<spectric-tooltip .text=${this.overflow}></spectric-tooltip>`
@@ -14,6 +14,7 @@ import {
14
14
  TableSortDirection,
15
15
  } from "./table";
16
16
  import { DisposableElement } from "../../classes";
17
+ import { ARROW_DOWN, ARROW_UP } from "../symbols";
17
18
 
18
19
  interface HeaderProps<T> {
19
20
  columns: ColumnSettings<T>[];
@@ -125,9 +126,9 @@ export class TableHeaderElement<T>
125
126
  : "";
126
127
  let sortDirection =
127
128
  column.sortDirection === TableSortDirection.ascending
128
- ? `🠉`
129
+ ? ARROW_UP
129
130
  : column.sortDirection == TableSortDirection.descending
130
- ? `🠋`
131
+ ? ARROW_DOWN
131
132
  : ``;
132
133
  let sortClass = column.sortDirection || TableSortDirection.none;
133
134
  let resizable = column.allowResize || column.allowResize === undefined;
@@ -20,14 +20,16 @@ spectric-table spectric-table-virtual-body tr:hover{
20
20
  background-color: color-mix(in srgb, var(--spectric-primary, #1ea7fd), transparent 70%)
21
21
  }
22
22
 
23
- spectric-table tr {
23
+ spectric-table spectric-table-virtual-body tr {
24
24
  height: var(--rowHeight);
25
25
  }
26
26
  spectric-table td{
27
- height: var(--rowHeight);
28
27
  padding:1px;
29
28
  border: 1px solid transparent;
30
29
  }
30
+ spectric-table spectric-table-virtual-body td{
31
+ height: var(--rowHeight);
32
+ }
31
33
 
32
34
  spectric-table div[role="table"]{
33
35
  display: table;
@@ -42,7 +44,7 @@ spectric-table-cell td{
42
44
  position: relative;
43
45
  }
44
46
 
45
- spectric-table td:hover:has(.filterable) {
47
+ spectric-table td:hover:has(.hasActions) {
46
48
  border: 1px solid var(--spectric-primary, #1ea7fd);
47
49
  }
48
50
 
@@ -52,9 +54,14 @@ spectric-table-cell .table-cell-actions{
52
54
  width: 100%;
53
55
  flex-direction: row-reverse;
54
56
  visibility: hidden;
55
- top: -10px;
56
57
  z-index: 1;
57
58
  }
59
+ spectric-table-cell .table-cell-actions.tiny{
60
+ top: -10px;
61
+ }
62
+ spectric-table-cell .table-cell-actions.xxsmall{
63
+ top: -16px;
64
+ }
58
65
  spectric-table-cell td:hover .table-cell-actions{
59
66
  visibility: unset;
60
67
  }
@@ -14,6 +14,7 @@ import { spreadProps } from "../../utils/spread";
14
14
  import { PaginationChangeProps, PaginationProps } from "../pagination";
15
15
  import { FilterEvent } from "./cell";
16
16
  import { createSortChain } from "./sorting";
17
+ import { ButtonSizesTypes } from "../Button";
17
18
 
18
19
  export type { TableProps, TableEvents };
19
20
 
@@ -41,6 +42,11 @@ export enum TableSortDirection {
41
42
  none = "none",
42
43
  }
43
44
  export type TableSortDirectionTypes = `${TableSortDirection}`;
45
+ export type CellAction<T> = {
46
+ tooltip: DomRenderable;
47
+ icon: DomRenderable;
48
+ onClick: (row: T, column: ColumnSettings<T>) => void;
49
+ };
44
50
  export type ColumnSettings<T> = {
45
51
  [TABLE_CREATED_SELECTION_COLUMN]?: boolean;
46
52
  width?: number;
@@ -53,6 +59,8 @@ export type ColumnSettings<T> = {
53
59
  sortable?: boolean;
54
60
  sortDirection?: TableSortDirectionTypes;
55
61
  filterable?: boolean;
62
+ disableCellOverflowTooltip?: boolean;
63
+ cellActions?: CellAction<T>[];
56
64
  title?: DomRenderable | ((table: SpectricTableElement<T>) => DomRenderable);
57
65
  /**
58
66
  * Key to used for getting data from an object for a cell
@@ -125,6 +133,11 @@ export class SpectricTableElement<T = any>
125
133
  */
126
134
  @property({ type: Number, reflect: true })
127
135
  fontSize: number = 16;
136
+
137
+ protected cellActionButtonSize: ButtonSizesTypes = "xxsmall";
138
+ getCellActionButtonSize() {
139
+ return this.cellActionButtonSize;
140
+ }
128
141
  static getDefaultDataSorterAndPaginatior<T>(data: T[]) {
129
142
  return (props: TableDataOptions<T>) => {
130
143
  //let sorts = props.columns.filter(column => column.sortable && column.sortDirection && column.sortDirection !== TableSortDirection.none)
@@ -237,7 +250,7 @@ export class SpectricTableElement<T = any>
237
250
  protected createRenderRoot(): HTMLElement | DocumentFragment {
238
251
  return this;
239
252
  }
240
- forceRefreshofSelectionColumn() {
253
+ private forceRefreshofSelectionColumn() {
241
254
  // Because lit reuses dom elements for speed/effeciency it wont update unless the props are a different object.
242
255
  // So we set the selection colum to a "new object" to force the rerender
243
256
  let index = this.columns.findIndex(
@@ -251,6 +264,9 @@ export class SpectricTableElement<T = any>
251
264
  this.selected = selected;
252
265
  this.forceRefreshofSelectionColumn();
253
266
  }
267
+ getSelected() {
268
+ return this.selected;
269
+ }
254
270
  deselectAll() {
255
271
  this.selected = [];
256
272
  this.forceRefreshofSelectionColumn();
@@ -261,6 +277,53 @@ export class SpectricTableElement<T = any>
261
277
  this.forceRefreshofSelectionColumn();
262
278
  this.dispatchEvent(new CustomEvent("selected", { detail: this.selected }));
263
279
  }
280
+ async scrollToRow(row: T | number) {
281
+ let index: number = -1;
282
+ if (Number.isInteger(row)) {
283
+ index = row as number;
284
+ } else {
285
+ index = this.data.findIndex((value) => value === row);
286
+ }
287
+ if (index !== -1) {
288
+ let body = this.querySelector("spectric-table-virtual-body");
289
+ let wrapper = this.querySelector(".table-wrapper")!;
290
+ let scrollPosition = index * this._getRowHeight();
291
+ wrapper.scrollTo({
292
+ top: scrollPosition,
293
+ behavior: "smooth",
294
+ });
295
+ //Wait for the smooth scroll to complete. Scroll to doesn't return a promise so we must manually check periodically
296
+ for (let wait = 0; wait < 100; wait++) {
297
+ await new Promise((resolve) => setTimeout(resolve, 10));
298
+ if (wrapper.scrollTop == scrollPosition) {
299
+ console.log("Scroll complete");
300
+ break;
301
+ }
302
+ }
303
+ if (body) {
304
+ //Highlight the row we scrolled to
305
+ requestAnimationFrame(() => {
306
+ let rows = [...body.querySelectorAll("tr")];
307
+ rows = rows.filter(
308
+ (row) => row.querySelector("spectric-table-cell")?.index === index
309
+ );
310
+ if (rows.length) {
311
+ rows[0].animate(
312
+ [{ backgroundColor: "red" }, { backgroundColor: "unset" }],
313
+ { duration: 200, iterations: 5 }
314
+ );
315
+ }
316
+ });
317
+ }
318
+ }
319
+ }
320
+ _getRowHeight = () => {
321
+ let rowHeight = this.rowHeight - TD_BorderAndPadding;
322
+ if (rowHeight < this.fontSize + TD_BorderAndPadding) {
323
+ rowHeight = this.fontSize + TD_BorderAndPadding;
324
+ }
325
+ return rowHeight;
326
+ };
264
327
  _handleSelectAllChange = (e: DomEvent<HTMLInputElement>) => {
265
328
  e.stopPropagation();
266
329
  if (e.target.checked) {
@@ -271,6 +334,8 @@ export class SpectricTableElement<T = any>
271
334
  };
272
335
  private selectColumnConfig: ColumnSettings<T> = {
273
336
  [TABLE_CREATED_SELECTION_COLUMN]: true,
337
+ allowResize: false,
338
+ filterable: false,
274
339
  width: 39,
275
340
  title: (table) => {
276
341
  return table.select === "multi"
@@ -314,6 +379,14 @@ export class SpectricTableElement<T = any>
314
379
  },
315
380
  };
316
381
  protected update(changedProperties: PropertyValues): void {
382
+ if (changedProperties.has("data")) {
383
+ this.selected = this.data.reduce((a, row) => {
384
+ if (this.selected.includes(row)) {
385
+ a.push(row);
386
+ }
387
+ return a;
388
+ }, [] as T[]);
389
+ }
317
390
  if (changedProperties.has("columns")) {
318
391
  if (this.select !== TableSelectOptions.none) {
319
392
  if (!this.columns.find((col) => col[TABLE_CREATED_SELECTION_COLUMN])) {
@@ -333,10 +406,7 @@ export class SpectricTableElement<T = any>
333
406
  }
334
407
  protected render(): unknown {
335
408
  let columns = this.columns.filter((column) => !column.hidden);
336
- let rowHeight = this.rowHeight - TD_BorderAndPadding;
337
- if (rowHeight < this.fontSize + TD_BorderAndPadding) {
338
- rowHeight = this.fontSize + TD_BorderAndPadding;
339
- }
409
+ let rowHeight = this._getRowHeight();
340
410
  return html`
341
411
  <div
342
412
  class="table-wrapper"
@@ -43,14 +43,19 @@ export class TableVirtualBodyElement<T>
43
43
  columnsMeasured: boolean = false;
44
44
  constructor() {
45
45
  super();
46
+ let lastScroll = 0;
46
47
  this.addDisposableListener(
47
48
  () => this.table.querySelector(".table-wrapper")!,
48
49
  "scroll",
49
50
  () => {
50
51
  const scrollTop = this.table.querySelector(".table-wrapper")!.scrollTop;
51
- requestAnimationFrame(() => {
52
- this.startIndex = Math.floor(scrollTop / this.rowHeight);
53
- });
52
+ // Prevent changing the start index if the scroll hasn't changed more than at at least 1/4 a row
53
+ // I think I have bad math that is causing a new scroll event due to a couple pixel height difference after I create the spacers
54
+ if (Math.abs(lastScroll - scrollTop) <= this.rowHeight / 4) {
55
+ return;
56
+ }
57
+ lastScroll = scrollTop;
58
+ this.startIndex = Math.floor(scrollTop / this.rowHeight);
54
59
  }
55
60
  );
56
61
  }