@redvars/peacock 3.3.0 → 3.3.1
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/assets/components.css +1 -1
- package/dist/assets/components.css.map +1 -1
- package/dist/assets/styles.css +1 -1
- package/dist/assets/styles.css.map +1 -1
- package/dist/button-group-DA7xoziD.js +292 -0
- package/dist/button-group-DA7xoziD.js.map +1 -0
- package/dist/button-group.js +6 -107
- package/dist/button-group.js.map +1 -1
- package/dist/{button-BGFJfbT2.js → button-trIfcqC7.js} +2 -3
- package/dist/{button-BGFJfbT2.js.map → button-trIfcqC7.js.map} +1 -1
- package/dist/button.js +2 -3
- package/dist/button.js.map +1 -1
- package/dist/{class-map-DpeNtqCn.js → class-map-hJdvjl-W.js} +9 -3
- package/dist/class-map-hJdvjl-W.js.map +1 -0
- package/dist/code-editor.js +5 -5
- package/dist/code-editor.js.map +1 -1
- package/dist/code-highlighter.js +5 -23
- package/dist/code-highlighter.js.map +1 -1
- package/dist/custom-elements-jsdocs.json +4706 -2471
- package/dist/custom-elements.json +3444 -1007
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/peacock-loader.js +26 -496
- package/dist/peacock-loader.js.map +1 -1
- package/dist/src/accordion/accordion-item.d.ts +1 -0
- package/dist/src/breadcrumb/breadcrumb/breadcrumb.d.ts +2 -0
- package/dist/src/breadcrumb/breadcrumb-item/breadcrumb-item.d.ts +1 -0
- package/dist/src/button/button-group/button-group.d.ts +4 -0
- package/dist/src/code-editor/code-editor.d.ts +4 -3
- package/dist/src/code-highlighter/code-highlighter.d.ts +4 -7
- package/dist/src/index.d.ts +4 -0
- package/dist/src/menu/index.d.ts +3 -0
- package/dist/src/menu/menu/MenuSurfaceController.d.ts +18 -0
- package/dist/src/menu/menu/menu.d.ts +54 -12
- package/dist/src/menu/menu-item/menu-item.d.ts +12 -5
- package/dist/src/menu/sub-menu/sub-menu.d.ts +36 -0
- package/dist/src/pagination/index.d.ts +1 -0
- package/dist/src/pagination/pagination.d.ts +38 -0
- package/dist/src/popover/PopoverController.d.ts +4 -1
- package/dist/src/table/index.d.ts +1 -0
- package/dist/src/table/table.d.ts +110 -0
- package/dist/src/tabs/tab-group.d.ts +4 -0
- package/dist/src/tabs/tab-panel.d.ts +1 -0
- package/dist/src/tabs/tab.d.ts +1 -0
- package/dist/src/tabs/tabs.d.ts +2 -0
- package/dist/src/tooltip/tooltip.d.ts +1 -3
- package/dist/src/tree-view/index.d.ts +2 -0
- package/dist/src/tree-view/tree-node.d.ts +69 -0
- package/dist/src/tree-view/tree-view.d.ts +40 -0
- package/dist/src/tree-view/wc-tree-view.d.ts +6 -0
- package/dist/test/icon.test.d.ts +1 -1
- package/dist/test/menu.test.d.ts +1 -0
- package/dist/test/sub-menu.test.d.ts +1 -0
- package/dist/test/tree-view.test.d.ts +1 -0
- package/dist/{slider-Dk9CFWTG.js → tree-view-CLolVlU0.js} +3317 -1180
- package/dist/tree-view-CLolVlU0.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/readme.md +40 -40
- package/src/accordion/accordion-item.ts +2 -1
- package/src/breadcrumb/breadcrumb/breadcrumb.ts +3 -0
- package/src/breadcrumb/breadcrumb-item/breadcrumb-item.ts +1 -0
- package/src/button/button-group/button-group.ts +6 -0
- package/src/code-editor/code-editor.ts +4 -3
- package/src/code-highlighter/code-highlighter.ts +4 -22
- package/src/divider/divider.scss +2 -2
- package/src/empty-state/empty-state.scss +1 -1
- package/src/empty-state/empty-state.ts +1 -1
- package/src/index.ts +6 -2
- package/src/menu/index.ts +3 -0
- package/src/menu/menu/MenuSurfaceController.ts +61 -0
- package/src/menu/{menu-list/menu-list.scss → menu/menu.scss} +19 -4
- package/src/menu/menu/menu.ts +389 -81
- package/src/menu/menu-item/menu-item.ts +115 -36
- package/src/menu/sub-menu/sub-menu.scss +7 -0
- package/src/menu/sub-menu/sub-menu.ts +243 -0
- package/src/pagination/index.ts +1 -0
- package/src/pagination/pagination.scss +59 -0
- package/src/pagination/pagination.ts +135 -0
- package/src/peacock-loader.ts +25 -11
- package/src/popover/PopoverController.ts +13 -7
- package/src/table/index.ts +1 -0
- package/src/table/table.scss +174 -0
- package/src/table/table.ts +475 -0
- package/src/tabs/tab-group.ts +12 -6
- package/src/tabs/tab-panel.ts +1 -0
- package/src/tabs/tab.ts +1 -0
- package/src/tabs/tabs.scss +6 -5
- package/src/tabs/tabs.ts +5 -3
- package/src/text/text.css-component.scss +6 -3
- package/src/tooltip/tooltip.scss +16 -13
- package/src/tooltip/tooltip.ts +7 -9
- package/src/tree-view/demo/index.html +57 -0
- package/src/tree-view/index.ts +2 -0
- package/src/tree-view/tree-node.scss +101 -0
- package/src/tree-view/tree-node.ts +268 -0
- package/src/tree-view/tree-view.scss +12 -0
- package/src/tree-view/tree-view.ts +182 -0
- package/src/tree-view/wc-tree-view.ts +9 -0
- package/dist/class-map-DpeNtqCn.js.map +0 -1
- package/dist/slider-Dk9CFWTG.js.map +0 -1
- package/dist/src/menu/menu-list/menu-list.d.ts +0 -22
- package/dist/state-8v48Exzh.js +0 -10
- package/dist/state-8v48Exzh.js.map +0 -1
- package/src/menu/menu-list/menu-list.ts +0 -48
package/src/tooltip/tooltip.ts
CHANGED
|
@@ -16,9 +16,7 @@ export type TooltipTrigger = 'hover' | 'focus' | 'click';
|
|
|
16
16
|
*
|
|
17
17
|
* @example
|
|
18
18
|
* ```html
|
|
19
|
-
* <wc-tooltip
|
|
20
|
-
* <button>Hover me</button>
|
|
21
|
-
* </wc-tooltip>
|
|
19
|
+
* <wc-tooltip preview>Tooltip</wc-tooltip>
|
|
22
20
|
* ```
|
|
23
21
|
*/
|
|
24
22
|
export class Tooltip extends LitElement {
|
|
@@ -58,13 +56,13 @@ export class Tooltip extends LitElement {
|
|
|
58
56
|
|
|
59
57
|
// Define listeners as arrow functions to maintain 'this' context
|
|
60
58
|
private _onMouseEnter = () => {
|
|
61
|
-
if (!this.hasTrigger('hover')) return;
|
|
59
|
+
if (this.preview || !this.hasTrigger('hover')) return;
|
|
62
60
|
window.clearTimeout(this._hideTimeout); // Cancel any pending close
|
|
63
61
|
this.show();
|
|
64
62
|
};
|
|
65
63
|
|
|
66
64
|
private _onMouseLeave = () => {
|
|
67
|
-
if (!this.hasTrigger('hover')) return;
|
|
65
|
+
if (this.preview || !this.hasTrigger('hover')) return;
|
|
68
66
|
|
|
69
67
|
// Small delay allows the mouse to move from target -> tooltip
|
|
70
68
|
// without the tooltip vanishing instantly.
|
|
@@ -79,17 +77,17 @@ export class Tooltip extends LitElement {
|
|
|
79
77
|
}, 100); // 100ms is usually enough for a smooth transition
|
|
80
78
|
};
|
|
81
79
|
|
|
82
|
-
private _onFocusIn = () => this.hasTrigger('focus') && this.show();
|
|
80
|
+
private _onFocusIn = () => this.preview && this.hasTrigger('focus') && this.show();
|
|
83
81
|
|
|
84
82
|
private _onFocusOut = (e: FocusEvent) => {
|
|
85
|
-
if (!this.hasTrigger('focus')) return;
|
|
83
|
+
if (this.preview || !this.hasTrigger('focus')) return;
|
|
86
84
|
if (this._target && !this._target.contains(e.relatedTarget as Node)) {
|
|
87
85
|
this.hide();
|
|
88
86
|
}
|
|
89
87
|
};
|
|
90
88
|
|
|
91
89
|
private _onClick = (e: MouseEvent) => {
|
|
92
|
-
if (!this.hasTrigger('click')) return;
|
|
90
|
+
if (this.preview || !this.hasTrigger('click')) return;
|
|
93
91
|
e.stopPropagation();
|
|
94
92
|
this.toggle();
|
|
95
93
|
};
|
|
@@ -176,7 +174,7 @@ export class Tooltip extends LitElement {
|
|
|
176
174
|
this.attachListeners();
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
if (changedProps.has('open') && this.open && this._target
|
|
177
|
+
if (changedProps.has('open') && this.open && this._target) {
|
|
180
178
|
this._popover.updatePosition(this._target, this.floatingEl);
|
|
181
179
|
}
|
|
182
180
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang='en-GB'>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset='utf-8'>
|
|
5
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0, viewport-fit=cover' />
|
|
6
|
+
<link rel='stylesheet' href='/dist/assets/styles/tokens.css' />
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
|
8
|
+
|
|
9
|
+
<style>
|
|
10
|
+
body {
|
|
11
|
+
background: #fafafa;
|
|
12
|
+
padding: 2rem;
|
|
13
|
+
font-family: 'Noto Sans', sans-serif;
|
|
14
|
+
}
|
|
15
|
+
h2 {
|
|
16
|
+
margin-top: 2rem;
|
|
17
|
+
margin-bottom: 0.5rem;
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
|
|
23
|
+
<h2>Basic Tree View</h2>
|
|
24
|
+
<wc-tree-view>
|
|
25
|
+
<wc-tree-node label="Documents" icon="folder" expanded>
|
|
26
|
+
<wc-tree-node label="Work" icon="folder" expanded>
|
|
27
|
+
<wc-tree-node label="Project A" icon="description"></wc-tree-node>
|
|
28
|
+
<wc-tree-node label="Project B" icon="description"></wc-tree-node>
|
|
29
|
+
</wc-tree-node>
|
|
30
|
+
<wc-tree-node label="Personal" icon="folder">
|
|
31
|
+
<wc-tree-node label="Resume.pdf" icon="picture_as_pdf"></wc-tree-node>
|
|
32
|
+
</wc-tree-node>
|
|
33
|
+
</wc-tree-node>
|
|
34
|
+
<wc-tree-node label="Pictures" icon="photo_library">
|
|
35
|
+
<wc-tree-node label="Vacation.jpg" icon="image"></wc-tree-node>
|
|
36
|
+
<wc-tree-node label="Family.jpg" icon="image"></wc-tree-node>
|
|
37
|
+
</wc-tree-node>
|
|
38
|
+
<wc-tree-node label="Disabled Node" icon="block" disabled></wc-tree-node>
|
|
39
|
+
</wc-tree-view>
|
|
40
|
+
|
|
41
|
+
<h2>Tree View without Icons</h2>
|
|
42
|
+
<wc-tree-view>
|
|
43
|
+
<wc-tree-node label="Category 1" expanded>
|
|
44
|
+
<wc-tree-node label="Item 1.1"></wc-tree-node>
|
|
45
|
+
<wc-tree-node label="Item 1.2">
|
|
46
|
+
<wc-tree-node label="Item 1.2.1"></wc-tree-node>
|
|
47
|
+
<wc-tree-node label="Item 1.2.2"></wc-tree-node>
|
|
48
|
+
</wc-tree-node>
|
|
49
|
+
</wc-tree-node>
|
|
50
|
+
<wc-tree-node label="Category 2">
|
|
51
|
+
<wc-tree-node label="Item 2.1"></wc-tree-node>
|
|
52
|
+
</wc-tree-node>
|
|
53
|
+
</wc-tree-view>
|
|
54
|
+
|
|
55
|
+
<script type='module' src='/dist/peacock-loader.js'></script>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
@use '../../scss/mixin';
|
|
2
|
+
|
|
3
|
+
@include mixin.base-styles;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
display: block;
|
|
7
|
+
|
|
8
|
+
// M3 tree node sizing
|
|
9
|
+
--tree-node-height: 2.5rem;
|
|
10
|
+
--tree-node-icon-size: 1.25rem;
|
|
11
|
+
--tree-node-border-radius: var(--global-shape-corner-full, 9999px);
|
|
12
|
+
|
|
13
|
+
// M3 color tokens
|
|
14
|
+
--tree-node-label-color: var(--color-on-surface);
|
|
15
|
+
--tree-node-icon-color: var(--color-on-surface-variant);
|
|
16
|
+
--tree-node-selected-background: var(--color-secondary-container);
|
|
17
|
+
--tree-node-selected-color: var(--color-on-secondary-container);
|
|
18
|
+
--tree-node-hover-background: var(--color-inverse-primary);
|
|
19
|
+
--tree-node-focus-ring-color: var(--color-primary);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.tree-node {
|
|
23
|
+
display: block;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.tree-node-content {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 0.25rem;
|
|
30
|
+
height: var(--tree-node-height);
|
|
31
|
+
border-radius: var(--tree-node-border-radius);
|
|
32
|
+
color: var(--tree-node-label-color);
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
user-select: none;
|
|
35
|
+
text-decoration: none;
|
|
36
|
+
outline: none;
|
|
37
|
+
|
|
38
|
+
@include mixin.get-typography(body-medium);
|
|
39
|
+
|
|
40
|
+
&:not(.disabled):hover {
|
|
41
|
+
background-color: var(--tree-node-hover-background);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:focus-visible {
|
|
45
|
+
@include mixin.focus-ring(var(--tree-node-focus-ring-color));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.selected {
|
|
49
|
+
background-color: var(--tree-node-selected-background);
|
|
50
|
+
color: var(--tree-node-selected-color);
|
|
51
|
+
|
|
52
|
+
.node-icon {
|
|
53
|
+
--icon-color: var(--tree-node-selected-color);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&.disabled {
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
opacity: 0.38;
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.expand-icon {
|
|
65
|
+
--icon-size: var(--tree-node-icon-size);
|
|
66
|
+
--icon-color: var(--tree-node-icon-color);
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
transition: transform var(--duration-short2, 200ms) var(--easing-standard, ease);
|
|
69
|
+
|
|
70
|
+
&.expanded {
|
|
71
|
+
transform: rotate(90deg);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.icon-space {
|
|
76
|
+
display: inline-block;
|
|
77
|
+
width: var(--tree-node-icon-size);
|
|
78
|
+
height: var(--tree-node-icon-size);
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.node-icon {
|
|
83
|
+
--icon-size: var(--tree-node-icon-size);
|
|
84
|
+
--icon-color: var(--tree-node-icon-color);
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.tree-node-label {
|
|
89
|
+
flex: 1;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
text-overflow: ellipsis;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.node-children {
|
|
96
|
+
display: none;
|
|
97
|
+
|
|
98
|
+
&.expanded {
|
|
99
|
+
display: block;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit';
|
|
2
|
+
import { property, query } from 'lit/decorators.js';
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
4
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
5
|
+
import styles from './tree-node.scss';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @label Tree Node
|
|
9
|
+
* @tag wc-tree-node
|
|
10
|
+
* @rawTag tree-node
|
|
11
|
+
* @parentRawTag tree-view
|
|
12
|
+
* @summary A tree node represents a single item in a hierarchical tree structure. It supports nesting, icons, links, and keyboard navigation.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```html
|
|
16
|
+
* <wc-tree-view>
|
|
17
|
+
* <wc-tree-node label="Parent">
|
|
18
|
+
* <wc-tree-node label="Child"></wc-tree-node>
|
|
19
|
+
* </wc-tree-node>
|
|
20
|
+
* </wc-tree-view>
|
|
21
|
+
* ```
|
|
22
|
+
* @tags navigation
|
|
23
|
+
*/
|
|
24
|
+
export class TreeNode extends LitElement {
|
|
25
|
+
static styles = [styles];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The value used to identify this node when selected.
|
|
29
|
+
*/
|
|
30
|
+
@property({ type: String, reflect: true })
|
|
31
|
+
value: string = '';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The display label for this node.
|
|
35
|
+
*/
|
|
36
|
+
@property({ type: String, reflect: true })
|
|
37
|
+
label: string = '';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Optional icon name to display before the label.
|
|
41
|
+
*/
|
|
42
|
+
@property({ type: String, reflect: true })
|
|
43
|
+
icon: string = '';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Optional hyperlink to navigate to on click.
|
|
47
|
+
*/
|
|
48
|
+
@property({ type: String, reflect: true })
|
|
49
|
+
href: string = '';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sets or retrieves the window or frame at which to target content.
|
|
53
|
+
*/
|
|
54
|
+
@property({ type: String, reflect: true })
|
|
55
|
+
target: string = '_self';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* If true, the user cannot interact with the node.
|
|
59
|
+
*/
|
|
60
|
+
@property({ type: Boolean, reflect: true })
|
|
61
|
+
disabled: boolean = false;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether the node is currently selected.
|
|
65
|
+
*/
|
|
66
|
+
@property({ type: Boolean, reflect: true })
|
|
67
|
+
selected: boolean = false;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether child nodes are visible.
|
|
71
|
+
*/
|
|
72
|
+
@property({ type: Boolean, reflect: true })
|
|
73
|
+
expanded: boolean = false;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The nesting depth level (set automatically by the parent tree-view).
|
|
77
|
+
*/
|
|
78
|
+
@property({ type: Number, reflect: true })
|
|
79
|
+
level: number = 0;
|
|
80
|
+
|
|
81
|
+
@query('.tree-node-content')
|
|
82
|
+
private readonly _nativeElement!: HTMLElement | null;
|
|
83
|
+
|
|
84
|
+
override focus() {
|
|
85
|
+
this._nativeElement?.focus();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
override blur() {
|
|
89
|
+
this._nativeElement?.blur();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private _getChildNodes(): TreeNode[] {
|
|
93
|
+
return Array.from(this.children).filter(
|
|
94
|
+
el => el.tagName.toLowerCase() === 'wc-tree-node',
|
|
95
|
+
) as TreeNode[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
connectedCallback() {
|
|
99
|
+
super.connectedCallback();
|
|
100
|
+
this._updateChildLevels();
|
|
101
|
+
this.setAttribute('role', 'treeitem');
|
|
102
|
+
this._syncHostAria();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private _syncHostAria() {
|
|
106
|
+
const hasChildren = this._getChildNodes().length > 0;
|
|
107
|
+
if (hasChildren) {
|
|
108
|
+
this.setAttribute('aria-expanded', String(this.expanded));
|
|
109
|
+
} else {
|
|
110
|
+
this.removeAttribute('aria-expanded');
|
|
111
|
+
}
|
|
112
|
+
this.setAttribute('aria-selected', String(this.selected));
|
|
113
|
+
this.setAttribute('aria-disabled', String(this.disabled));
|
|
114
|
+
this.setAttribute('aria-level', String(this.level + 1));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private _updateChildLevels() {
|
|
118
|
+
const children = this._getChildNodes();
|
|
119
|
+
children.forEach(child => {
|
|
120
|
+
// eslint-disable-next-line no-param-reassign
|
|
121
|
+
child.level = this.level + 1;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private _handleClick(event: Event) {
|
|
126
|
+
if (this.disabled) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
event.stopPropagation();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const hasChildren = this._getChildNodes().length > 0;
|
|
133
|
+
if (hasChildren) {
|
|
134
|
+
this.expanded = !this.expanded;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.dispatchEvent(
|
|
138
|
+
new CustomEvent('tree-node:click', {
|
|
139
|
+
bubbles: true,
|
|
140
|
+
composed: true,
|
|
141
|
+
detail: {
|
|
142
|
+
value: this.value || this.label,
|
|
143
|
+
label: this.label,
|
|
144
|
+
expanded: this.expanded,
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _handleKeyDown(event: KeyboardEvent) {
|
|
151
|
+
if (this.disabled) return;
|
|
152
|
+
|
|
153
|
+
const hasChildren = this._getChildNodes().length > 0;
|
|
154
|
+
|
|
155
|
+
switch (event.key) {
|
|
156
|
+
case ' ':
|
|
157
|
+
case 'Enter':
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
if (hasChildren) {
|
|
160
|
+
this.expanded = !this.expanded;
|
|
161
|
+
}
|
|
162
|
+
this.dispatchEvent(
|
|
163
|
+
new CustomEvent('tree-node:click', {
|
|
164
|
+
bubbles: true,
|
|
165
|
+
composed: true,
|
|
166
|
+
detail: {
|
|
167
|
+
value: this.value || this.label,
|
|
168
|
+
label: this.label,
|
|
169
|
+
expanded: this.expanded,
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
if (this.href) {
|
|
174
|
+
window.open(this.href, this.target);
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'ArrowLeft':
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
if (this.expanded && hasChildren) {
|
|
180
|
+
this.expanded = false;
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case 'ArrowRight':
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
if (hasChildren) {
|
|
186
|
+
if (!this.expanded) {
|
|
187
|
+
this.expanded = true;
|
|
188
|
+
} else {
|
|
189
|
+
const firstChild = this._getChildNodes()[0];
|
|
190
|
+
firstChild?.focus();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
default:
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
updated(changedProps: Map<string, unknown>) {
|
|
200
|
+
super.updated(changedProps);
|
|
201
|
+
if (changedProps.has('level')) {
|
|
202
|
+
this._updateChildLevels();
|
|
203
|
+
}
|
|
204
|
+
this._syncHostAria();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private _renderContent(hasChildren: boolean) {
|
|
208
|
+
// 0.125rem offset aligns text visually with the expand/icon space
|
|
209
|
+
const indentStyle = styleMap({
|
|
210
|
+
paddingInlineStart: `calc(${this.level + 1}rem - 0.125rem)`,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const contentClasses = classMap({
|
|
214
|
+
'tree-node-content': true,
|
|
215
|
+
selected: this.selected,
|
|
216
|
+
disabled: this.disabled,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const innerContent = html`
|
|
220
|
+
${hasChildren
|
|
221
|
+
? html`<wc-icon
|
|
222
|
+
class=${classMap({ 'expand-icon': true, expanded: this.expanded })}
|
|
223
|
+
name="arrow_right"
|
|
224
|
+
></wc-icon>`
|
|
225
|
+
: html`<span class="icon-space"></span>`}
|
|
226
|
+
${this.icon
|
|
227
|
+
? html`<wc-icon class="node-icon" name=${this.icon}></wc-icon>`
|
|
228
|
+
: ''}
|
|
229
|
+
<span class="tree-node-label">${this.label}<slot name="label"></slot></span>
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
if (this.href) {
|
|
233
|
+
return html`<a
|
|
234
|
+
class=${contentClasses}
|
|
235
|
+
style=${indentStyle}
|
|
236
|
+
href=${this.href}
|
|
237
|
+
target=${this.target}
|
|
238
|
+
tabindex=${this.disabled ? '-1' : '0'}
|
|
239
|
+
@click=${this._handleClick}
|
|
240
|
+
@keydown=${this._handleKeyDown}
|
|
241
|
+
>${innerContent}</a>`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return html`<div
|
|
245
|
+
class=${contentClasses}
|
|
246
|
+
style=${indentStyle}
|
|
247
|
+
tabindex=${this.disabled ? '-1' : '0'}
|
|
248
|
+
@click=${this._handleClick}
|
|
249
|
+
@keydown=${this._handleKeyDown}
|
|
250
|
+
>${innerContent}</div>`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
render() {
|
|
254
|
+
const hasChildren = this._getChildNodes().length > 0;
|
|
255
|
+
|
|
256
|
+
const nodeSlotClasses = classMap({
|
|
257
|
+
'node-children': true,
|
|
258
|
+
expanded: this.expanded,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return html`<div class="tree-node">
|
|
262
|
+
${this._renderContent(hasChildren)}
|
|
263
|
+
<div class=${nodeSlotClasses}>
|
|
264
|
+
<slot></slot>
|
|
265
|
+
</div>
|
|
266
|
+
</div>`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit';
|
|
2
|
+
import { property } from 'lit/decorators.js';
|
|
3
|
+
import styles from './tree-view.scss';
|
|
4
|
+
import { TreeNode } from './tree-node.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @label Tree View
|
|
8
|
+
* @tag wc-tree-view
|
|
9
|
+
* @rawTag tree-view
|
|
10
|
+
* @summary A tree view is a hierarchical structure that provides nested levels of navigation. It supports keyboard navigation, single/multi select, and expandable nodes.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```html
|
|
14
|
+
* <wc-tree-view>
|
|
15
|
+
* <wc-tree-node label="Parent" expanded>
|
|
16
|
+
* <wc-tree-node label="Child 1"></wc-tree-node>
|
|
17
|
+
* <wc-tree-node label="Child 2"></wc-tree-node>
|
|
18
|
+
* </wc-tree-node>
|
|
19
|
+
* </wc-tree-view>
|
|
20
|
+
* ```
|
|
21
|
+
* @tags navigation
|
|
22
|
+
*/
|
|
23
|
+
export class TreeView extends LitElement {
|
|
24
|
+
static styles = [styles];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The value of the currently selected node.
|
|
28
|
+
*/
|
|
29
|
+
@property({ type: String, attribute: 'selected-node', reflect: true })
|
|
30
|
+
selectedNode: string = '';
|
|
31
|
+
|
|
32
|
+
connectedCallback() {
|
|
33
|
+
super.connectedCallback();
|
|
34
|
+
this.addEventListener('tree-node:click', this._onNodeClick as EventListener);
|
|
35
|
+
this.addEventListener('keydown', this._onKeyDown);
|
|
36
|
+
this.setAttribute('role', 'tree');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
updated(changedProps: Map<string, unknown>) {
|
|
40
|
+
super.updated(changedProps);
|
|
41
|
+
|
|
42
|
+
if (changedProps.has('selectedNode')) {
|
|
43
|
+
this._syncSelectedStateFromProperty();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
disconnectedCallback() {
|
|
48
|
+
super.disconnectedCallback();
|
|
49
|
+
this.removeEventListener('tree-node:click', this._onNodeClick as EventListener);
|
|
50
|
+
this.removeEventListener('keydown', this._onKeyDown);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private _getTopLevelNodes(): TreeNode[] {
|
|
54
|
+
return Array.from(this.children).filter(
|
|
55
|
+
el => el.tagName.toLowerCase() === 'wc-tree-node',
|
|
56
|
+
) as TreeNode[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private _getAllVisibleNodes(): TreeNode[] {
|
|
60
|
+
const result: TreeNode[] = [];
|
|
61
|
+
const collect = (nodes: TreeNode[]) => {
|
|
62
|
+
nodes.forEach(node => {
|
|
63
|
+
result.push(node);
|
|
64
|
+
if (node.expanded) {
|
|
65
|
+
const children = Array.from(node.children).filter(
|
|
66
|
+
el => el.tagName.toLowerCase() === 'wc-tree-node',
|
|
67
|
+
) as TreeNode[];
|
|
68
|
+
collect(children);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
collect(this._getTopLevelNodes());
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private _onNodeClick = (event: CustomEvent) => {
|
|
77
|
+
const target = event.target as TreeNode;
|
|
78
|
+
if (target.disabled) return;
|
|
79
|
+
|
|
80
|
+
const value = event.detail?.value ?? target.value ?? target.label;
|
|
81
|
+
this.selectedNode = value;
|
|
82
|
+
|
|
83
|
+
// Update selected state on all nodes
|
|
84
|
+
this._updateSelectedState(value);
|
|
85
|
+
|
|
86
|
+
this.dispatchEvent(
|
|
87
|
+
new CustomEvent('tree-view:change', {
|
|
88
|
+
bubbles: true,
|
|
89
|
+
composed: true,
|
|
90
|
+
detail: { value, node: target },
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
private _updateSelectedState(selectedValue: string) {
|
|
96
|
+
const allNodes = this._collectAllNodes(this._getTopLevelNodes());
|
|
97
|
+
allNodes.forEach(node => {
|
|
98
|
+
const nodeValue = node.value || node.label;
|
|
99
|
+
// eslint-disable-next-line no-param-reassign
|
|
100
|
+
node.selected = nodeValue === selectedValue;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private _clearSelectedState() {
|
|
105
|
+
const allNodes = this._collectAllNodes(this._getTopLevelNodes());
|
|
106
|
+
allNodes.forEach(node => {
|
|
107
|
+
// eslint-disable-next-line no-param-reassign
|
|
108
|
+
node.selected = false;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private _syncSelectedStateFromProperty() {
|
|
113
|
+
if (this.selectedNode) {
|
|
114
|
+
this._updateSelectedState(this.selectedNode);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this._clearSelectedState();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private _onSlotChange = () => {
|
|
122
|
+
this._syncSelectedStateFromProperty();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
private _collectAllNodes(nodes: TreeNode[]): TreeNode[] {
|
|
126
|
+
const result: TreeNode[] = [];
|
|
127
|
+
nodes.forEach(node => {
|
|
128
|
+
result.push(node);
|
|
129
|
+
const children = Array.from(node.children).filter(
|
|
130
|
+
el => el.tagName.toLowerCase() === 'wc-tree-node',
|
|
131
|
+
) as TreeNode[];
|
|
132
|
+
result.push(...this._collectAllNodes(children));
|
|
133
|
+
});
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private _onKeyDown = (event: KeyboardEvent) => {
|
|
138
|
+
const visibleNodes = this._getAllVisibleNodes().filter(n => !n.disabled);
|
|
139
|
+
if (visibleNodes.length === 0) return;
|
|
140
|
+
|
|
141
|
+
const focusedIndex = visibleNodes.findIndex(node => {
|
|
142
|
+
const root = node.shadowRoot;
|
|
143
|
+
return root?.activeElement !== null && root?.activeElement !== undefined;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (focusedIndex === -1 && event.key !== 'Tab') return;
|
|
147
|
+
|
|
148
|
+
switch (event.key) {
|
|
149
|
+
case 'ArrowDown': {
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
const nextIndex = (focusedIndex + 1) % visibleNodes.length;
|
|
152
|
+
visibleNodes[nextIndex]?.focus();
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case 'ArrowUp': {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
const prevIndex =
|
|
158
|
+
(focusedIndex - 1 + visibleNodes.length) % visibleNodes.length;
|
|
159
|
+
visibleNodes[prevIndex]?.focus();
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'Home':
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
visibleNodes[0]?.focus();
|
|
165
|
+
break;
|
|
166
|
+
case 'End':
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
visibleNodes[visibleNodes.length - 1]?.focus();
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
render() {
|
|
176
|
+
return html`<div class="tree-view">
|
|
177
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
178
|
+
</div>`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static Node = TreeNode;
|
|
182
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { customElement } from 'lit/decorators.js';
|
|
2
|
+
import { TreeView } from './tree-view.js';
|
|
3
|
+
import { TreeNode } from './tree-node.js';
|
|
4
|
+
|
|
5
|
+
@customElement('wc-tree-node')
|
|
6
|
+
export class WcTreeNode extends TreeNode {}
|
|
7
|
+
|
|
8
|
+
@customElement('wc-tree-view')
|
|
9
|
+
export class WcTreeView extends TreeView {}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"class-map-DpeNtqCn.js","sources":["../node_modules/lit-html/directives/class-map.js"],"sourcesContent":["import{noChange as t}from\"../lit-html.js\";import{directive as s,Directive as i,PartType as r}from\"../directive.js\";\n/**\n * @license\n * Copyright 2018 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */const e=s(class extends i{constructor(t){if(super(t),t.type!==r.ATTRIBUTE||\"class\"!==t.name||t.strings?.length>2)throw Error(\"`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.\")}render(t){return\" \"+Object.keys(t).filter(s=>t[s]).join(\" \")+\" \"}update(s,[i]){if(void 0===this.st){this.st=new Set,void 0!==s.strings&&(this.nt=new Set(s.strings.join(\" \").split(/\\s/).filter(t=>\"\"!==t)));for(const t in i)i[t]&&!this.nt?.has(t)&&this.st.add(t);return this.render(i)}const r=s.element.classList;for(const t of this.st)t in i||(r.remove(t),this.st.delete(t));for(const t in i){const s=!!i[t];s===this.st.has(t)||this.nt?.has(t)||(s?(r.add(t),this.st.add(t)):(r.remove(t),this.st.delete(t)))}return t}});export{e as classMap};\n//# sourceMappingURL=class-map.js.map\n"],"names":["s","t","r"],"mappings":";;;AACA;AACA;AACA;AACA;AACA,GAAQ,MAAC,CAAC,CAACA,GAAC,CAAC,cAAc,CAAC,CAAC,WAAW,CAACC,GAAC,CAAC,CAAC,GAAG,KAAK,CAACA,GAAC,CAAC,CAACA,GAAC,CAAC,IAAI,GAAGC,CAAC,CAAC,SAAS,EAAE,OAAO,GAAGD,GAAC,CAAC,IAAI,EAAEA,GAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,oGAAoG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAC,CAAC,OAAOA,CAAC,CAAC,CAAC;;;;","x_google_ignoreList":[0]}
|