@spectric/ui 0.0.4

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 (88) hide show
  1. package/.gitlab-ci.yml +28 -0
  2. package/.nvmrc +1 -0
  3. package/.storybook/analyze.sh +4 -0
  4. package/.storybook/main.ts +55 -0
  5. package/.storybook/preview.ts +42 -0
  6. package/.vscode/extensions.json +5 -0
  7. package/.vscode/settings.json +41 -0
  8. package/README.MD +50 -0
  9. package/html-include.png +0 -0
  10. package/package.json +33 -0
  11. package/src/classes/BitArray.ts +48 -0
  12. package/src/classes/DisposibleElement.ts +108 -0
  13. package/src/components/Banner.ts +102 -0
  14. package/src/components/Bitdisplay.ts +383 -0
  15. package/src/components/Button.ts +121 -0
  16. package/src/components/Header.ts +125 -0
  17. package/src/components/Page.ts +157 -0
  18. package/src/components/Panel.ts +56 -0
  19. package/src/components/ThemeProvider.ts +251 -0
  20. package/src/components/button.css.ts +160 -0
  21. package/src/components/configurations/classifications.ts +194 -0
  22. package/src/components/dialog/dialog.css.ts +50 -0
  23. package/src/components/dialog/dialog.ts +163 -0
  24. package/src/components/dialog/index.ts +1 -0
  25. package/src/components/header.css.ts +38 -0
  26. package/src/components/index.ts +10 -0
  27. package/src/components/input.css +75 -0
  28. package/src/components/input.ts +312 -0
  29. package/src/components/page.css.ts +158 -0
  30. package/src/components/panel.css.ts +44 -0
  31. package/src/components/query_bar/QueryBar.css +48 -0
  32. package/src/components/query_bar/QueryBar.ts +378 -0
  33. package/src/components/query_bar/index.ts +2 -0
  34. package/src/components/query_bar/querylanguage/kuery/ast/_generated_/kuery.js +3186 -0
  35. package/src/components/query_bar/querylanguage/kuery/ast/ast.ts +113 -0
  36. package/src/components/query_bar/querylanguage/kuery/ast/index.ts +31 -0
  37. package/src/components/query_bar/querylanguage/kuery/ast/kuery.peg +417 -0
  38. package/src/components/query_bar/querylanguage/kuery/functions/and.ts +55 -0
  39. package/src/components/query_bar/querylanguage/kuery/functions/exists.ts +62 -0
  40. package/src/components/query_bar/querylanguage/kuery/functions/index.ts +47 -0
  41. package/src/components/query_bar/querylanguage/kuery/functions/is.ts +211 -0
  42. package/src/components/query_bar/querylanguage/kuery/functions/nested.ts +63 -0
  43. package/src/components/query_bar/querylanguage/kuery/functions/not.ts +53 -0
  44. package/src/components/query_bar/querylanguage/kuery/functions/or.ts +56 -0
  45. package/src/components/query_bar/querylanguage/kuery/functions/range.ts +163 -0
  46. package/src/components/query_bar/querylanguage/kuery/functions/utils/get_fields.ts +49 -0
  47. package/src/components/query_bar/querylanguage/kuery/functions/utils/get_full_field_name_node.ts +87 -0
  48. package/src/components/query_bar/querylanguage/kuery/index.ts +38 -0
  49. package/src/components/query_bar/querylanguage/kuery/kuery_syntax_error.ts +76 -0
  50. package/src/components/query_bar/querylanguage/kuery/node_types/function.ts +75 -0
  51. package/src/components/query_bar/querylanguage/kuery/node_types/index.ts +46 -0
  52. package/src/components/query_bar/querylanguage/kuery/node_types/literal.ts +42 -0
  53. package/src/components/query_bar/querylanguage/kuery/node_types/named_arg.ts +47 -0
  54. package/src/components/query_bar/querylanguage/kuery/node_types/types.ts +108 -0
  55. package/src/components/query_bar/querylanguage/kuery/node_types/wildcard.ts +80 -0
  56. package/src/components/query_bar/querylanguage/kuery/types.ts +52 -0
  57. package/src/components/query_bar/querylanguage/outputTypes/toCQL.ts +122 -0
  58. package/src/components/query_bar/querylanguage/outputTypes/toMongo.ts +103 -0
  59. package/src/components/query_bar/querylanguage/utils.ts +35 -0
  60. package/src/components/query_bar/types.ts +59 -0
  61. package/src/components/splitview/index.ts +1 -0
  62. package/src/components/splitview/splitview.css.ts +66 -0
  63. package/src/components/splitview/splitview.ts +183 -0
  64. package/src/components/types.ts +35 -0
  65. package/src/index.ts +1 -0
  66. package/src/stories/Banner.stories.ts +46 -0
  67. package/src/stories/BitDisplay.stories.ts +68 -0
  68. package/src/stories/Button.stories.ts +138 -0
  69. package/src/stories/Header.stories.ts +55 -0
  70. package/src/stories/Page.stories.ts +108 -0
  71. package/src/stories/QueryBar.stories.ts +63 -0
  72. package/src/stories/Splitview.stories.ts +52 -0
  73. package/src/stories/fixtures/Bits.ts +15 -0
  74. package/src/stories/fixtures/ExampleContent.ts +102 -0
  75. package/src/stories/fixtures/data.ts +30 -0
  76. package/src/stories/fixtures/lorumipsum.ts +19 -0
  77. package/src/stories/input.stories.ts +77 -0
  78. package/src/stories/tsconfig.json +35 -0
  79. package/src/utils/debounce.ts +18 -0
  80. package/src/utils/spread.ts +71 -0
  81. package/src/vite-env.d.ts +1 -0
  82. package/test/__init__.py +9 -0
  83. package/test/elastic.py +9 -0
  84. package/test/interface.py +16 -0
  85. package/tsconfig.json +29 -0
  86. package/vite.config.js +34 -0
  87. package/vue-example.png +0 -0
  88. package/vue-include.png +0 -0
@@ -0,0 +1,122 @@
1
+
2
+ import { FieldTypes, KueryNode } from '../..';
3
+ import { wildcardSymbol } from '../kuery/node_types/wildcard';
4
+
5
+ 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';
8
+ const and = (node: KueryNode, fields?: FieldTypes[]) => {
9
+ const children = node.arguments || [];
10
+ return (
11
+ '(' +
12
+ children
13
+ .map((child: KueryNode) => {
14
+ return toCql(child, fields);
15
+ })
16
+ .join(' AND ') +
17
+ ')'
18
+ );
19
+ };
20
+ const is = (node: KueryNode, fields?: FieldTypes[]) => {
21
+ var {
22
+ arguments: [fieldNameArg, valueArg, isValue],
23
+ } = node;
24
+ let operator = '=';
25
+ if (valueArg.type === 'wildcard') {
26
+ operator = '';
27
+ }
28
+ let value = toCql(valueArg);
29
+ isValue = isValue.value || typeof value === "string";
30
+ if (valueArg.type === 'literal' && isValue) {
31
+ value = `'${value}'`; //should be quoted
32
+ }
33
+ if (valueArg.type === 'literal' && !isValue) {
34
+ value = `${value}`; //shouldn't be quoted
35
+ }
36
+ let fieldName = toCql(fieldNameArg);
37
+ if (fieldName == null) {
38
+ //this isn't possible if we don't have the names of all the fields.
39
+ //if we have all the fields we can do a list of or statements (field ILIKE "%value%")
40
+ if (fields && isValue) {
41
+ return `(${fields.filter(f => f.type === "string").map(field => `${field.name} ILIKE '%${valueArg.value}%'`).join(" OR ")})`
42
+ }
43
+ if (fields && !isValue && (valueArg.value === true || valueArg.value === false)) {
44
+ return `(${fields.filter(f => f.type === "boolean").map(field => `${field.name}=${valueArg.value}`).join(" OR ")})`
45
+ }
46
+ return ""
47
+ }
48
+ return fieldName + operator + value;
49
+ };
50
+
51
+ const or = (node: KueryNode, fields?: FieldTypes[]) => {
52
+ const children = node.arguments || [];
53
+ return (
54
+ '(' +
55
+ children
56
+ .map((child: KueryNode) => {
57
+ return toCql(child, fields);
58
+ })
59
+ .join(' OR ') +
60
+ ')'
61
+ );
62
+ };
63
+ const not = (node: KueryNode, fields?: FieldTypes[]) => {
64
+ const [argument] = node.arguments;
65
+ return 'NOT (' + toCql(argument, fields) + ')';
66
+ };
67
+
68
+ const AST_TO_CQL = {
69
+ gt: '>',
70
+ lt: '<',
71
+ gte: '>=',
72
+ lte: '<=',
73
+ };
74
+ const range = (node: KueryNode,) => {
75
+
76
+ const [fieldNameArg, operator] = node.arguments;
77
+ let valueArg = operator.value
78
+ // @ts-ignore
79
+ const opsign = AST_TO_CQL[operator.name];
80
+ let value = toCql(valueArg);
81
+ if (valueArg.type === 'literal') {
82
+ value = `${value}`;
83
+ }
84
+ return `${fieldNameArg.value} ${opsign} ${value}`;
85
+ };
86
+ const exists = (node: KueryNode) => {
87
+ const [fieldNameArg] = node.arguments;
88
+ return `${fieldNameArg.value} IS NOT NULL`;
89
+ };
90
+ const nested = (node: KueryNode) => {
91
+ //nested types don't exist in CQL
92
+ console.warn("Nested types dont exist in CQL", node)
93
+ return ""
94
+ }
95
+ export const functions = {
96
+ is,
97
+ and,
98
+ or,
99
+ not,
100
+ range,
101
+ exists,
102
+ nested
103
+ };
104
+ const nodeTypes = {
105
+ function: (node: KueryNode, fields?: FieldTypes[]) => {
106
+ // @ts-ignore
107
+ return functions[node.function as FunctionName](node, fields);
108
+ },
109
+ literal: (node: KueryNode) => {
110
+ return node.value;
111
+ },
112
+ wildcard: (node: KueryNode) => {
113
+ const { value } = node;
114
+ return ` LIKE '${value.split(KQL_WILDCARD_SYMBOL).join('%')}'`;
115
+ },
116
+ };
117
+
118
+ export const toCql = (node: KueryNode, fields?: FieldTypes[]): string => {
119
+ //@ts-ignore
120
+ const nodeType = nodeTypes[node.type] as unknown as any;
121
+ return nodeType(node, fields);
122
+ };
@@ -0,0 +1,103 @@
1
+ /* eslint-disable @kbn/eslint/require-license-header */
2
+ import { FieldTypes, KueryNode } from '../..';
3
+ import { wildcardSymbol } from '../kuery/node_types/wildcard';
4
+
5
+ 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';
8
+ const and = (node: KueryNode) => {
9
+ const children = node.arguments || [];
10
+ let query: any = { "$and": children.map((c: KueryNode) => toMongo(c)) }
11
+
12
+ return query;
13
+ };
14
+ const is = (node: KueryNode) => {
15
+ const {
16
+ arguments: [fieldNameArg, valueArg],
17
+ } = node;
18
+
19
+ let value = toMongo(valueArg);
20
+ const isExistsQuery = valueArg.type === 'wildcard' && (valueArg.value as any) === '@kuery-wildcard@';
21
+ if (isExistsQuery) {
22
+ return exists(node)
23
+ }
24
+ let query: any = {}
25
+ query[toMongo(fieldNameArg)] = { "$eq": value }
26
+ return query;
27
+ };
28
+
29
+ const or = (node: KueryNode) => {
30
+ const children = node.arguments || [];
31
+ return {
32
+ "$or": children
33
+ .map((child: KueryNode) => {
34
+ return toMongo(child);
35
+ })
36
+ }
37
+
38
+ };
39
+ const not = (node: KueryNode) => {
40
+ const [fieldNameArg] = node.arguments;
41
+ let query: any = {};
42
+ query = { "$ne": toMongo(fieldNameArg) }
43
+ return query;
44
+ };
45
+
46
+ const AST_TO_CQL = {
47
+ gt: '$gt',
48
+ lt: '$lt',
49
+ gte: '$gte',
50
+ lte: '$lte',
51
+ };
52
+ const range = (node: KueryNode) => {
53
+ const [fieldNameArg, operator] = node.arguments;
54
+ let valueArg = operator.value
55
+ // @ts-ignore
56
+ const opsign = AST_TO_CQL[operator.name];
57
+ let value = toMongo(valueArg);
58
+
59
+ let query: any = {}
60
+ query[fieldNameArg.value] = {}
61
+ query[fieldNameArg.value][opsign] = value
62
+ return query
63
+
64
+
65
+ };
66
+ const exists = (node: KueryNode) => {
67
+ const [fieldNameArg] = node.arguments;
68
+ return { [toMongo(fieldNameArg)]: { $ne: null } }
69
+ };
70
+
71
+ const nested = (node: KueryNode) => {
72
+
73
+ console.warn("TODO Implement nested search", node)
74
+ return ""
75
+ }
76
+ export const functions = {
77
+ is,
78
+ and,
79
+ or,
80
+ not,
81
+ range,
82
+ exists,
83
+ nested
84
+ };
85
+ const nodeTypes = {
86
+ function: (node: KueryNode) => {
87
+ // @ts-ignore
88
+ return functions[node.function as FunctionName](node);
89
+ },
90
+ literal: (node: KueryNode) => {
91
+ return node.value;
92
+ },
93
+ wildcard: (node: KueryNode) => {
94
+ const { value } = node;
95
+ return `/${value.split(KQL_WILDCARD_SYMBOL).join('.*')}/`;
96
+ },
97
+ };
98
+
99
+ export const toMongo = (node: KueryNode, fields?: FieldTypes[]): string => {
100
+ //@ts-ignore
101
+ const nodeType = nodeTypes[node.type] as unknown as any;
102
+ return nodeType(node, fields);
103
+ };
@@ -0,0 +1,35 @@
1
+ /*
2
+ * SPDX-License-Identifier: Apache-2.0
3
+ *
4
+ * The OpenSearch Contributors require contributions made to
5
+ * this file be licensed under the Apache-2.0 license or a
6
+ * compatible open source license.
7
+ *
8
+ * Any modifications Copyright OpenSearch Contributors. See
9
+ * GitHub history for details.
10
+ */
11
+
12
+ /*
13
+ * Licensed to Elasticsearch B.V. under one or more contributor
14
+ * license agreements. See the NOTICE file distributed with
15
+ * this work for additional information regarding copyright
16
+ * ownership. Elasticsearch B.V. licenses this file to you under
17
+ * the Apache License, Version 2.0 (the "License"); you may
18
+ * not use this file except in compliance with the License.
19
+ * You may obtain a copy of the License at
20
+ *
21
+ * http://www.apache.org/licenses/LICENSE-2.0
22
+ *
23
+ * Unless required by applicable law or agreed to in writing,
24
+ * software distributed under the License is distributed on an
25
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
26
+ * KIND, either express or implied. See the License for the
27
+ * specific language governing permissions and limitations
28
+ * under the License.
29
+ */
30
+
31
+
32
+ export function getTimeZoneFromSettings(dateFormatTZ: string) {
33
+
34
+ return dateFormatTZ;//always use zulu
35
+ }
@@ -0,0 +1,59 @@
1
+ import { NodeTypes } from './querylanguage/kuery/node_types';
2
+
3
+ export interface KueryNode {
4
+ type: keyof NodeTypes;
5
+ [key: string]: any;
6
+ }
7
+
8
+ export type DslQuery = any;
9
+
10
+ export interface KueryParseOptions {
11
+ helpers: {
12
+ [key: string]: any;
13
+ };
14
+ startRule: string;
15
+ allowLeadingWildcards: boolean;
16
+ errorOnLuceneSyntax: boolean;
17
+ cursorSymbol?: string;
18
+ parseCursor?: boolean;
19
+ }
20
+
21
+ export type IIndexPattern = { fields: IFieldType[] }
22
+
23
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
24
+ export interface JsonArray extends Array<JsonValue> { }
25
+ export interface JsonObject {
26
+ [key: string]: JsonValue;
27
+ }
28
+ export type JsonValue = null | boolean | number | string | JsonObject | JsonArray;
29
+ export { nodeTypes } from './querylanguage/kuery/node_types';
30
+
31
+ export interface IFieldSubType {
32
+ multi?: { parent: string };
33
+ nested?: { path: string };
34
+ }
35
+ export interface IFieldType {
36
+ name: string;
37
+ type: string;
38
+ script?: string;
39
+ lang?: string;
40
+ count?: number;
41
+ // esTypes might be undefined on old index patterns that have not been refreshed since we added
42
+ // this prop. It is also undefined on scripted fields.
43
+ esTypes?: string[];
44
+ aggregatable?: boolean;
45
+ filterable?: boolean;
46
+ searchable?: boolean;
47
+ sortable?: boolean;
48
+ visualizable?: boolean;
49
+ readFromDocValues?: boolean;
50
+ scripted?: boolean;
51
+ subType?: IFieldSubType;
52
+ displayName?: string;
53
+ format?: any;
54
+
55
+ }
56
+ export interface LatLon {
57
+ lat: number;
58
+ lon: number;
59
+ }
@@ -0,0 +1 @@
1
+ export * from "./splitview"
@@ -0,0 +1,66 @@
1
+ import { css } from "lit";
2
+
3
+ export const style = css`
4
+ :host {
5
+ display: block;
6
+ height: 100%;
7
+ width: 100%;
8
+
9
+ }
10
+ .split-view {
11
+ display: flex;
12
+ height: 100%;
13
+ width: 100%;
14
+ position:relative;
15
+ }
16
+ .split-view.active{
17
+ cursor:ew-resize;
18
+ }
19
+ .split-view.active.vertical{
20
+ cursor:ns-resize;
21
+ }
22
+ .split-view.vertical {
23
+ flex-direction: column;
24
+ }
25
+ .panel {
26
+ flex: 1;
27
+ overflow: auto;
28
+ }
29
+ .splitter {
30
+ border-radius: var(--spectric-border-radius, .4em);
31
+ background-color: var(--spectric-input-color,#ccc);
32
+ user-select: none;
33
+ transition: background-color 100ms linear;
34
+ }
35
+
36
+ .splitter.invisible {
37
+ background-color: #00000000;
38
+ }
39
+ .splitter:hover {
40
+ background-color: var(--spectric-button-primary, #1ea7fd);
41
+ transition: background-color 500ms linear;
42
+ }
43
+ .splitter.active {
44
+ background-color: var(--spectric-button-primary, #1ea7fd);
45
+ transition: background-color 500ms linear;
46
+ }
47
+ .split-view > .error-display{
48
+ display:none;
49
+ position:absolute;
50
+ top:0;
51
+ right:0
52
+ }
53
+ .split-view.error > .error-display{
54
+ display:inline-block;
55
+
56
+ }
57
+ .split-view.vertical .splitter {
58
+ cursor: ns-resize;
59
+ height: 5px;
60
+ width: 100%;
61
+ }
62
+ .split-view:not(.vertical) .splitter {
63
+ width: 5px;
64
+ cursor: ew-resize;
65
+ }
66
+ `;
@@ -0,0 +1,183 @@
1
+ import { html, PropertyValues, } from 'lit-element';
2
+ import { DisposableElement } from '../../classes/DisposibleElement';
3
+ import { customElement, property, query, queryAsync, state } from 'lit/decorators.js';
4
+
5
+ import { style } from './splitview.css';
6
+ import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
7
+ import { debounceAnimation } from '../../utils/debounce';
8
+
9
+ export enum Orientations {
10
+ horizontal = "horizontal",
11
+ vertical = "vertical"
12
+ }
13
+ export interface SplitViewProps {
14
+ /** Controls the orientation of the splitter handle */
15
+ orientation: `${Orientations}`;
16
+ /** the percentage to split the view default: 50*/
17
+ percentage?: number
18
+ /** Should the splitter handle be invisible? */
19
+ invisible?: boolean
20
+ /** Clamps the minimum split percentage default: 10 */
21
+ min: number
22
+ /** Clamps the maximum split percentage default: 90 */
23
+ max: number
24
+ /** save and load split state to localstorage splitter must have an id attribute default: true */
25
+ useSavedState?: boolean
26
+ }
27
+ export const ElementTag = 'spectric-splitview'
28
+ /**
29
+ * Split view will take a container and split it horizontally or vertically. This element can only have two children.
30
+ * If you supply the **id** attribute on the split view element it will save and load the state keeping the user defined position
31
+ * @slot - Element can only take 2 HTMLElements.
32
+ */
33
+ @customElement(ElementTag)
34
+ export class SplitView extends DisposableElement implements SplitViewProps {
35
+
36
+ @property({ type: String }) orientation = 'horizontal' as Orientations;
37
+
38
+ @property({ type: Number, reflect: true }) percentage: SplitViewProps['percentage'] = 50;
39
+
40
+ @property({ type: Boolean }) invisible: SplitViewProps['invisible'] = false;
41
+ static styles = style
42
+
43
+ @property({ type: Number, reflect: true }) min: SplitViewProps['min'] = 10;
44
+ @property({ type: Number, reflect: true }) max: SplitViewProps['max'] = 90;
45
+ @property({ type: Boolean, reflect: true }) useSavedState: SplitViewProps['useSavedState'] = true;
46
+
47
+ @state()
48
+ private isDragging: boolean = false;
49
+ @state()
50
+ private _error: string | false = false;
51
+ @queryAsync(".splitter")
52
+ private _splitter!: Promise<HTMLElement>
53
+
54
+ @query("slot[name='panel1']")
55
+ private _panel1!: HTMLSlotElement
56
+ @query("slot[name='panel2']")
57
+ private _panel2!: HTMLSlotElement
58
+ constructor() {
59
+ super()
60
+ let id = this.getAttribute("id");
61
+ if (id && this.useSavedState) {
62
+ let savedValue = localStorage.getItem(`splitview-${id}`)
63
+ if (savedValue) {
64
+ this.percentage = parseFloat(savedValue)
65
+ }
66
+ }
67
+ this.addDisposableListener(this._splitter, "mousedown", () => {
68
+ this.isDragging = true;
69
+ })
70
+ /**
71
+ * On double click set percentage to 50%
72
+ */
73
+ this.addDisposableListener(this._splitter, "dblclick", () => {
74
+ this.percentage = 50
75
+ this._emitChange()
76
+ })
77
+ this.addDisposableListener(document.body, "mousemove", this._onMouseMove)
78
+ this.addDisposableListener(document.body, 'mouseup', () => {
79
+ this.isDragging = false;
80
+ });
81
+ }
82
+
83
+ protected updated(changedProperties: PropertyValues): void {
84
+ if (changedProperties.has("percentage")) {
85
+ this.percentage = Math.min(Math.max(Number(this.percentage), this.min), this.max)
86
+ }
87
+ }
88
+ _emitChange = () => {
89
+ let { percentage, orientation, invisible, min, max, useSavedState } = this
90
+ let id = this.getAttribute("id");
91
+ if (id && this.useSavedState) {
92
+ localStorage.setItem(`splitview-${id}`, String(percentage))
93
+ }
94
+ /** CustomEvent\<SplitViewProps\> Fired every time there is a change to the split percentage */
95
+ this.dispatchEvent(new CustomEvent<SplitViewProps>("change", { detail: { orientation, percentage, invisible, min, max, useSavedState } }))
96
+ }
97
+ _onMouseMove = debounceAnimation((e: MouseEvent) => {
98
+ if (this.isDragging) {
99
+ const rect = this.getBoundingClientRect();
100
+ let offset: number;
101
+ let percentage: number;
102
+ if (this.orientation === 'horizontal') {
103
+ offset = e.clientX - rect.left;
104
+ percentage = (offset / rect.width) * 100;
105
+ } else {
106
+ offset = e.clientY - rect.top;
107
+ percentage = (offset / rect.height) * 100;
108
+ }
109
+ //clamp to min/mix of the split
110
+ percentage = Math.min(Math.max(percentage, this.min), this.max)
111
+ this.percentage = percentage
112
+ this._emitChange()
113
+ }
114
+ });
115
+ private _assignSlot = (e: Event) => {
116
+ if (!e.target) {
117
+ return
118
+ }
119
+ let slot = e.target as HTMLSlotElement;
120
+ let nodes = slot.assignedNodes().filter(n => n instanceof HTMLElement);
121
+ if (nodes.length > 2) {
122
+ console.log("cannot assign more than 2 elements to a split view")
123
+ }
124
+ nodes.forEach(element => {
125
+ if (this._panel1.assignedNodes().length === 0) {
126
+ element.slot = "panel1"
127
+ return
128
+ }
129
+ if (this._panel2.assignedNodes().length === 0) {
130
+ element.slot = "panel2"
131
+ return
132
+ }
133
+ this._error = "Too many element assigned to split view splitter can only have a maximum of 2 children"
134
+ console.warn("Too many element assigned to split view splitter can only have a maximum of 2 children")
135
+ }
136
+ )
137
+ }
138
+ render() {
139
+ return html`
140
+ <div class="split-view ${this.orientation} ${this._error ? "error" : ""} ${this.isDragging ? "active" : ""}" style="--split-percentage: ${this.percentage}%;">
141
+ <span class="error-display">${this._error}</span>
142
+ <div class="panel" style="${this.orientation === 'horizontal' ? 'flex: var(--split-percentage, 50%)' : 'max-height: var(--split-percentage, 50%)'}">
143
+ <slot name="panel1"></slot>
144
+ </div>
145
+ <div class="splitter ${this.invisible ? "invisible" : ""} ${this.isDragging ? "active" : ""}"></div>
146
+ <div class="panel" style="${this.orientation === 'horizontal' ? 'flex: calc(100% - var(--split-percentage, 50%))' : 'max-height: calc(100% - var(--split-percentage, 50%))'}">
147
+ <slot name="panel2"></slot>
148
+ </div>
149
+ </div>
150
+ <slot @slotchange=${this._assignSlot} style="display:none"></slot>
151
+ `;
152
+ }
153
+ }
154
+
155
+ export interface SplitViewEvents {
156
+ 'change': (event: CustomEvent<SplitViewProps>) => void;
157
+ }
158
+
159
+
160
+ declare global {
161
+ interface HTMLElementTagNameMap {
162
+ [ElementTag]: HTMLElementTagWithEvents<SplitView, SplitViewEvents>
163
+
164
+ }
165
+ namespace JSX {
166
+ interface IntrinsicElements {
167
+ /**
168
+ * @see {@link SplitView}
169
+ */
170
+ [ElementTag]: ReactElementWithPropsAndEvents<SplitView, SplitViewProps, SplitViewEvents>;
171
+ }
172
+ }
173
+ namespace React {
174
+ namespace JSX {
175
+ interface IntrinsicElements {
176
+ /**
177
+ * @see {@link SplitView}
178
+ */
179
+ [ElementTag]: ReactElementWithPropsAndEvents<SplitView, SplitViewProps, SplitViewEvents>
180
+ }
181
+ }
182
+ }
183
+ }
@@ -0,0 +1,35 @@
1
+ export type HTMLElementTagWithEvents<ElementClass, EventsMap> = Omit<{
2
+ [P in keyof ElementClass]: ElementClass[P];
3
+ }, "addEventListener" | "removeEventListener"> & WithEvents<EventsMap>;
4
+
5
+ type WithEvents<EventsMap> = {
6
+ addEventListener<K extends keyof Omit<HTMLElementEventMap, keyof EventsMap>>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
7
+ addEventListener<K extends keyof EventsMap>(type: K, listener: EventsMap[K], options?: boolean | AddEventListenerOptions): void;
8
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
9
+ removeEventListener<K extends keyof Omit<HTMLElementEventMap, keyof EventsMap>>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
10
+ removeEventListener<K extends keyof EventsMap>(type: K, listener: EventsMap[K], options?: boolean | AddEventListenerOptions): void;
11
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
12
+ }
13
+
14
+ //export type ReactElementWithPropsAndEvents<Class,Props,Events> = React.DetailedHTMLProps<Props & React.DOMAttributes<Class>&ReactEventMap<Events>, Class>
15
+ export type ReactElementWithPropsAndEvents<Class, Props, Events = {}> = React.DetailedHTMLProps<Props & React.DOMAttributes<Class> & OverloadReactSyntheticEvents<Class, Events> & ReactEventMap<Events>, Class>
16
+
17
+ // If our Event name happens to be a built in dom event like click
18
+ // the element.addEventListener("click") will not get the
19
+ // specified event in the event map because
20
+ // react internally creates a synthetic event for particular types
21
+ // so we need to extract the those events and return a react synthetic event for those specific types
22
+ type OverloadReactSyntheticEvents<Class, Events> = Pick<ReactSyntheticEvents<Class, Events>, ExtractOverlappingEvents<Class, Events>>
23
+ type ExtractOverlappingEvents<Class, Events> = Extract<React.DOMAttributes<Class>, keyof ReactSyntheticEvents<Class, Events>>
24
+
25
+ //Map the event names that are handled by react to syntheticEvents
26
+ export type ReactSyntheticEvents<Class, Events> = {
27
+ [Key in keyof Events as `on${Capitalize<string & Key>}`]: (e: React.SyntheticEvent<Class, Events[Key]>) => void;
28
+ }
29
+
30
+
31
+ //Map native event to the React on<eventtype> property F
32
+ // FIXME we need to Omit the keys that where mapped to synthetic events (the inverse of the OverloadReactSyntheticEvents)
33
+ export type ReactEventMap<Events> = Partial<{
34
+ [Key in keyof Events as `on${string & Key}`]: Events[Key];
35
+ }>
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./components"
@@ -0,0 +1,46 @@
1
+
2
+ import type { Meta, StoryObj } from '@storybook/web-components';
3
+
4
+ import type { BannerProps, BannerSlots } from '../components/Banner';
5
+ import '../components/Banner';
6
+ import { html } from 'lit';
7
+
8
+ const meta = {
9
+ title: 'UI/Banner',
10
+ // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
11
+ tags: ['autodocs'],
12
+ component: "spectric-banner",
13
+ render: (args) => {
14
+ console.log(args)
15
+ return html`<spectric-banner ?dismissable=${args.dismissable} .headerStyle=${args.headerStyle}>${args.text}</spectric-banner>
16
+ `
17
+ },
18
+ args: {
19
+ text: "Banner with default style"
20
+ }
21
+ } satisfies Meta<BannerProps & BannerSlots>;
22
+
23
+ export default meta;
24
+ type Story = StoryObj<BannerProps & BannerSlots>;
25
+
26
+ export const ClassificationBanner: Story = {
27
+ args: {
28
+ text: "Unclassified",
29
+ headerStyle: {
30
+ backgroundColor: "green",
31
+ color: "white"
32
+ },
33
+ },
34
+ };
35
+
36
+ export const DismissableBanner: Story = {
37
+ args: {
38
+ text: "Can be dismissed",
39
+ dismissable: true,
40
+ headerStyle: {
41
+ backgroundColor: "cornflowerblue",
42
+ color: "white"
43
+ },
44
+ }
45
+ };
46
+