fluentui-webcomponents 0.0.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/AGENTS.md +212 -0
- package/README.md +99 -0
- package/components/avatar/fluent-avatar.css +481 -0
- package/components/avatar/fluent-avatar.js +80 -0
- package/components/badge/fluent-badge.css +289 -0
- package/components/badge/fluent-badge.js +20 -0
- package/components/breadcrumb/fluent-breadcrumb.css +29 -0
- package/components/breadcrumb/fluent-breadcrumb.js +33 -0
- package/components/breadcrumb-item/fluent-breadcrumb-item.css +70 -0
- package/components/breadcrumb-item/fluent-breadcrumb-item.js +77 -0
- package/components/button/fluent-button.css +265 -0
- package/components/button/fluent-button.js +326 -0
- package/components/card/fluent-card.css +85 -0
- package/components/card/fluent-card.js +21 -0
- package/components/checkbox/fluent-checkbox.css +171 -0
- package/components/checkbox/fluent-checkbox.js +294 -0
- package/components/dialog/fluent-dialog.css +82 -0
- package/components/dialog/fluent-dialog.js +137 -0
- package/components/divider/fluent-divider.css +124 -0
- package/components/divider/fluent-divider.js +14 -0
- package/components/image/fluent-image.css +73 -0
- package/components/image/fluent-image.js +36 -0
- package/components/label/fluent-label.css +49 -0
- package/components/label/fluent-label.js +61 -0
- package/components/link/fluent-link.css +72 -0
- package/components/link/fluent-link.js +109 -0
- package/components/menu/fluent-menu.css +57 -0
- package/components/menu/fluent-menu.js +202 -0
- package/components/menu-item/fluent-menu-item.css +152 -0
- package/components/menu-item/fluent-menu-item.js +177 -0
- package/components/popover/fluent-popover.css +95 -0
- package/components/popover/fluent-popover.js +93 -0
- package/components/radio/fluent-radio.css +123 -0
- package/components/radio/fluent-radio.js +257 -0
- package/components/select/fluent-select.css +194 -0
- package/components/select/fluent-select.js +245 -0
- package/components/slider/fluent-slider.css +199 -0
- package/components/slider/fluent-slider.js +438 -0
- package/components/spinner/fluent-spinner.css +160 -0
- package/components/spinner/fluent-spinner.js +30 -0
- package/components/switch/fluent-switch.css +154 -0
- package/components/switch/fluent-switch.js +260 -0
- package/components/text/fluent-text.css +128 -0
- package/components/text/fluent-text.js +21 -0
- package/components/text-input/fluent-text-input.css +227 -0
- package/components/text-input/fluent-text-input.js +298 -0
- package/components/textarea/fluent-textarea.css +227 -0
- package/components/textarea/fluent-textarea.js +400 -0
- package/components/tooltip/fluent-tooltip.css +65 -0
- package/components/tooltip/fluent-tooltip.js +102 -0
- package/components/tree/fluent-tree.css +16 -0
- package/components/tree/fluent-tree.js +167 -0
- package/components/tree-item/fluent-tree-item.css +147 -0
- package/components/tree-item/fluent-tree-item.js +163 -0
- package/core/fluent-element.js +34 -0
- package/gallery.html +492 -0
- package/package.json +19 -0
- package/theme/theme-picker.js +38 -0
- package/tokens.css +724 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { FluentElement } from '../../core/fluent-element.js';
|
|
2
|
+
|
|
3
|
+
const stylesUrl = new URL('./fluent-tooltip.css', import.meta.url).href;
|
|
4
|
+
|
|
5
|
+
let _tooltipIdCounter = 0;
|
|
6
|
+
|
|
7
|
+
class FluentTooltip extends FluentElement {
|
|
8
|
+
static stylesUrl = stylesUrl;
|
|
9
|
+
static template = `<div class="root"><slot></slot></div>`;
|
|
10
|
+
|
|
11
|
+
static formAssociated = true;
|
|
12
|
+
|
|
13
|
+
static get observedAttributes() {
|
|
14
|
+
return ['visible', 'anchor', 'positioning', 'delay'];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this._internals = this.attachInternals();
|
|
20
|
+
this._internals.role = 'tooltip';
|
|
21
|
+
this._id = `fluent-tooltip-${++_tooltipIdCounter}`;
|
|
22
|
+
this._defaultDelay = 250;
|
|
23
|
+
this._anchorElement = null;
|
|
24
|
+
this._timer = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
connectedCallback() {
|
|
28
|
+
super.connectedCallback();
|
|
29
|
+
this.setAttribute('id', this._id);
|
|
30
|
+
this.setAttribute('aria-hidden', 'true');
|
|
31
|
+
this.setAttribute('popover', 'manual');
|
|
32
|
+
this._resolveAnchor();
|
|
33
|
+
this._addAnchorListeners();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
disconnectedCallback() {
|
|
37
|
+
super.disconnectedCallback();
|
|
38
|
+
this._removeAnchorListeners();
|
|
39
|
+
if (this._anchorElement) {
|
|
40
|
+
this._anchorElement.style.removeProperty('anchor-name');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_resolveAnchor() {
|
|
45
|
+
const anchorId = this.getAttribute('anchor');
|
|
46
|
+
if (!anchorId) return;
|
|
47
|
+
const rootNode = this.getRootNode();
|
|
48
|
+
this._anchorElement = (rootNode instanceof ShadowRoot ? rootNode : document).getElementById(anchorId);
|
|
49
|
+
if (this._anchorElement) {
|
|
50
|
+
this._anchorElement.style.setProperty('anchor-name', `--${this._id}`);
|
|
51
|
+
this.style.setProperty('position-anchor', `--${this._id}`);
|
|
52
|
+
const describedBy = this._anchorElement.getAttribute('aria-describedby');
|
|
53
|
+
this._anchorElement.setAttribute('aria-describedby',
|
|
54
|
+
describedBy ? `${describedBy} ${this._id}` : this._id);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_addAnchorListeners() {
|
|
59
|
+
if (!this._anchorElement) return;
|
|
60
|
+
this._anchorElement.addEventListener('focus', this._focusHandler);
|
|
61
|
+
this._anchorElement.addEventListener('blur', this._blurHandler);
|
|
62
|
+
this._anchorElement.addEventListener('mouseenter', this._mouseenterHandler);
|
|
63
|
+
this._anchorElement.addEventListener('mouseleave', this._mouseleaveHandler);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_removeAnchorListeners() {
|
|
67
|
+
if (!this._anchorElement) return;
|
|
68
|
+
this._anchorElement.removeEventListener('focus', this._focusHandler);
|
|
69
|
+
this._anchorElement.removeEventListener('blur', this._blurHandler);
|
|
70
|
+
this._anchorElement.removeEventListener('mouseenter', this._mouseenterHandler);
|
|
71
|
+
this._anchorElement.removeEventListener('mouseleave', this._mouseleaveHandler);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
show(delay) {
|
|
75
|
+
clearTimeout(this._timer);
|
|
76
|
+
const d = delay != null ? Number(delay) : this._defaultDelay;
|
|
77
|
+
this._timer = setTimeout(() => {
|
|
78
|
+
this.setAttribute('aria-hidden', 'false');
|
|
79
|
+
this.showPopover();
|
|
80
|
+
}, d);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
hide(delay) {
|
|
84
|
+
clearTimeout(this._timer);
|
|
85
|
+
const d = delay != null ? Number(delay) : this._defaultDelay;
|
|
86
|
+
this._timer = setTimeout(() => {
|
|
87
|
+
if (this.matches(':hover') || (this._anchorElement && this._anchorElement.matches(':hover'))) {
|
|
88
|
+
this.hide(d);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.setAttribute('aria-hidden', 'true');
|
|
92
|
+
this.hidePopover();
|
|
93
|
+
}, d);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_focusHandler = () => this.show(0);
|
|
97
|
+
_blurHandler = () => this.hide(0);
|
|
98
|
+
_mouseenterHandler = () => this.show(this.getAttribute('delay'));
|
|
99
|
+
_mouseleaveHandler = () => this.hide(this.getAttribute('delay'));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
customElements.define('fluent-tooltip', FluentTooltip);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@import url('../../tokens.css');
|
|
2
|
+
|
|
3
|
+
:host { display: flex; }
|
|
4
|
+
|
|
5
|
+
.root {
|
|
6
|
+
display: block;
|
|
7
|
+
outline: none;
|
|
8
|
+
font-size: var(--fontSizeBase300);
|
|
9
|
+
line-height: var(--lineHeightBase300);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.root:focus-visible {
|
|
13
|
+
outline: var(--strokeWidthThick) solid var(--colorStrokeFocus2);
|
|
14
|
+
outline-offset: 2px;
|
|
15
|
+
border-radius: var(--borderRadiusMedium);
|
|
16
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { FluentElement } from '../../core/fluent-element.js';
|
|
2
|
+
|
|
3
|
+
const stylesUrl = new URL('./fluent-tree.css', import.meta.url).href;
|
|
4
|
+
|
|
5
|
+
class FluentTree extends FluentElement {
|
|
6
|
+
static stylesUrl = stylesUrl;
|
|
7
|
+
static template = `<div class="root"><slot></slot></div>`;
|
|
8
|
+
|
|
9
|
+
static get observedAttributes() {
|
|
10
|
+
return ['appearance', 'size'];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this._currentSelected = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
connectedCallback() {
|
|
19
|
+
super.connectedCallback();
|
|
20
|
+
this.setAttribute('role', 'tree');
|
|
21
|
+
this.setAttribute('tabindex', '0');
|
|
22
|
+
this.addEventListener('click', this._clickHandler);
|
|
23
|
+
this.addEventListener('keydown', this._keydownHandler);
|
|
24
|
+
this.addEventListener('change', this._changeHandler);
|
|
25
|
+
this._propagateAppearanceAndSize();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
disconnectedCallback() {
|
|
29
|
+
super.disconnectedCallback();
|
|
30
|
+
this.removeEventListener('click', this._clickHandler);
|
|
31
|
+
this.removeEventListener('keydown', this._keydownHandler);
|
|
32
|
+
this.removeEventListener('change', this._changeHandler);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
36
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
37
|
+
if (name === 'appearance' || name === 'size') {
|
|
38
|
+
this._propagateAppearanceAndSize();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_propagateAppearanceAndSize() {
|
|
43
|
+
const appearance = this.getAttribute('appearance');
|
|
44
|
+
const size = this.getAttribute('size');
|
|
45
|
+
const items = this.querySelectorAll('fluent-tree-item');
|
|
46
|
+
items.forEach(item => {
|
|
47
|
+
if (appearance && !item.hasAttribute('appearance')) {
|
|
48
|
+
item.setAttribute('appearance', appearance);
|
|
49
|
+
}
|
|
50
|
+
if (size && !item.hasAttribute('size')) {
|
|
51
|
+
item.setAttribute('size', size);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_getVisibleItems() {
|
|
57
|
+
const items = [];
|
|
58
|
+
const walk = el => {
|
|
59
|
+
for (const child of el.children) {
|
|
60
|
+
if (child.tagName.toLowerCase() === 'fluent-tree-item') {
|
|
61
|
+
if (!child.isHidden) {
|
|
62
|
+
items.push(child);
|
|
63
|
+
if (child.hasAttribute('expanded')) {
|
|
64
|
+
walk(child);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
walk(this);
|
|
71
|
+
return items;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_clickHandler = e => {
|
|
75
|
+
const item = e.target.closest('fluent-tree-item');
|
|
76
|
+
if (!item || item.disabled) return;
|
|
77
|
+
item.toggleExpansion();
|
|
78
|
+
this._selectItem(item);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
_changeHandler = e => {
|
|
82
|
+
const item = e.target;
|
|
83
|
+
if (!item || item.tagName.toLowerCase() !== 'fluent-tree-item') return;
|
|
84
|
+
if (item.selected) {
|
|
85
|
+
if (this._currentSelected && this._currentSelected !== item) {
|
|
86
|
+
this._currentSelected.selected = false;
|
|
87
|
+
}
|
|
88
|
+
this._currentSelected = item;
|
|
89
|
+
} else if (!item.selected && this._currentSelected === item) {
|
|
90
|
+
this._currentSelected = null;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
_selectItem(item) {
|
|
95
|
+
if (item.disabled) return;
|
|
96
|
+
if (this._currentSelected && this._currentSelected !== item) {
|
|
97
|
+
this._currentSelected.selected = false;
|
|
98
|
+
}
|
|
99
|
+
item.selected = true;
|
|
100
|
+
this._currentSelected = item;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_keydownHandler = e => {
|
|
104
|
+
if (e.defaultPrevented) return;
|
|
105
|
+
|
|
106
|
+
const visibleItems = this._getVisibleItems();
|
|
107
|
+
const focused = this._root.activeElement || document.activeElement;
|
|
108
|
+
let idx = visibleItems.indexOf(focused);
|
|
109
|
+
if (idx === -1) idx = visibleItems.indexOf(this._currentSelected);
|
|
110
|
+
|
|
111
|
+
switch (e.key) {
|
|
112
|
+
case 'ArrowUp':
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
if (idx > 0) visibleItems[idx - 1].focus();
|
|
115
|
+
break;
|
|
116
|
+
case 'ArrowDown':
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
if (idx >= 0 && idx < visibleItems.length - 1) visibleItems[idx + 1].focus();
|
|
119
|
+
break;
|
|
120
|
+
case 'ArrowLeft': {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
const item = visibleItems[idx] || this._currentSelected;
|
|
123
|
+
if (item) {
|
|
124
|
+
if (item.childTreeItems && item.childTreeItems.length && item.hasAttribute('expanded')) {
|
|
125
|
+
item.expanded = false;
|
|
126
|
+
} else {
|
|
127
|
+
const parent = item.parentElement && item.parentElement.closest('fluent-tree-item');
|
|
128
|
+
if (parent) parent.focus();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'ArrowRight': {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
const item = visibleItems[idx] || this._currentSelected;
|
|
136
|
+
if (item) {
|
|
137
|
+
if (item.childTreeItems && item.childTreeItems.length) {
|
|
138
|
+
if (!item.hasAttribute('expanded')) {
|
|
139
|
+
item.expanded = true;
|
|
140
|
+
} else if (item.childTreeItems.length > 0) {
|
|
141
|
+
item.childTreeItems[0].focus();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case 'Enter':
|
|
148
|
+
case ' ':
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
if (idx >= 0 && idx < visibleItems.length) {
|
|
151
|
+
visibleItems[idx].toggleExpansion();
|
|
152
|
+
this._selectItem(visibleItems[idx]);
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
case 'Home':
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
if (visibleItems.length > 0) visibleItems[0].focus();
|
|
158
|
+
break;
|
|
159
|
+
case 'End':
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
if (visibleItems.length > 0) visibleItems[visibleItems.length - 1].focus();
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
customElements.define('fluent-tree', FluentTree);
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
@import url('../../tokens.css');
|
|
2
|
+
|
|
3
|
+
:host { display: block; }
|
|
4
|
+
|
|
5
|
+
.root {
|
|
6
|
+
outline: none;
|
|
7
|
+
font-size: var(--fontSizeBase300);
|
|
8
|
+
line-height: var(--lineHeightBase300);
|
|
9
|
+
display: block;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.root:focus-visible .positioning-region {
|
|
13
|
+
box-shadow: inset 0 0 0 var(--spacingVerticalXXS) var(--colorStrokeFocus2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.positioning-region {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: space-between;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
height: var(--spacingVerticalXXXL);
|
|
22
|
+
padding-inline-start: calc(var(--indent, 0) * var(--spacingHorizontalXXL));
|
|
23
|
+
padding-inline-end: var(--spacingVerticalS);
|
|
24
|
+
border-radius: var(--borderRadiusMedium);
|
|
25
|
+
background-color: var(--colorSubtleBackground);
|
|
26
|
+
color: var(--colorNeutralForeground2);
|
|
27
|
+
gap: var(--spacingHorizontalXS);
|
|
28
|
+
transition: background var(--durationFaster) var(--curveEasyEaseMax),
|
|
29
|
+
color var(--durationFaster) var(--curveEasyEaseMax);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.positioning-region:hover {
|
|
33
|
+
background-color: var(--colorSubtleBackgroundHover);
|
|
34
|
+
color: var(--colorNeutralForeground2Hover);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.positioning-region:active {
|
|
38
|
+
background-color: var(--colorSubtleBackgroundPressed);
|
|
39
|
+
color: var(--colorNeutralForeground2Pressed);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.content {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: var(--spacingHorizontalXS);
|
|
46
|
+
min-width: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.chevron {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
width: var(--spacingHorizontalXXL);
|
|
55
|
+
height: var(--spacingVerticalXXL);
|
|
56
|
+
transition: transform var(--durationFaster) var(--curveEasyEaseMax);
|
|
57
|
+
transform: rotate(0deg);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.chevron svg {
|
|
61
|
+
inline-size: 12px;
|
|
62
|
+
block-size: 12px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.aside {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.items {
|
|
71
|
+
display: none;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:host([expanded]) .chevron {
|
|
75
|
+
transform: rotate(90deg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:host([expanded]) .items {
|
|
79
|
+
display: block;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:host([empty]) .chevron,
|
|
83
|
+
:host([empty]) .items {
|
|
84
|
+
visibility: hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:host([selected]) .positioning-region {
|
|
88
|
+
background-color: var(--colorSubtleBackgroundSelected);
|
|
89
|
+
color: var(--colorNeutralForeground2Selected);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
:host([disabled]) .root {
|
|
93
|
+
pointer-events: none;
|
|
94
|
+
opacity: 0.5;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:host([size='small']) .positioning-region {
|
|
98
|
+
height: var(--spacingVerticalXXL);
|
|
99
|
+
padding-inline-start: calc(var(--indent, 0) * var(--spacingHorizontalM));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
:host([appearance='transparent']) .positioning-region {
|
|
103
|
+
background-color: var(--colorTransparentBackground);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
:host([appearance='transparent']) .positioning-region:hover {
|
|
107
|
+
background-color: var(--colorTransparentBackgroundHover);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:host([appearance='transparent']) .positioning-region:active {
|
|
111
|
+
background-color: var(--colorTransparentBackgroundPressed);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
:host([appearance='transparent'][selected]) .positioning-region {
|
|
115
|
+
background-color: var(--colorTransparentBackgroundSelected);
|
|
116
|
+
color: var(--colorNeutralForeground2Selected);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
:host([appearance='subtle-alpha']) .positioning-region {
|
|
120
|
+
background-color: var(--colorSubtleBackground);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:host([appearance='subtle-alpha']) .positioning-region:hover {
|
|
124
|
+
background-color: var(--colorSubtleBackgroundHover);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
:host([appearance='subtle-alpha']) .positioning-region:active {
|
|
128
|
+
background-color: var(--colorSubtleBackgroundPressed);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
:host([appearance='subtle-alpha'][selected]) .positioning-region {
|
|
132
|
+
background-color: var(--colorSubtleBackgroundSelected);
|
|
133
|
+
color: var(--colorNeutralForeground2Selected);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@media (prefers-contrast: more) {
|
|
137
|
+
.root:focus-visible .positioning-region {
|
|
138
|
+
outline: 1px solid var(--colorStrokeFocus2);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@media (forced-colors: active) {
|
|
143
|
+
:host([disabled]) .root {
|
|
144
|
+
color: GrayText;
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { FluentElement } from '../../core/fluent-element.js';
|
|
2
|
+
|
|
3
|
+
const stylesUrl = new URL('./fluent-tree-item.css', import.meta.url).href;
|
|
4
|
+
|
|
5
|
+
class FluentTreeItem extends FluentElement {
|
|
6
|
+
static stylesUrl = stylesUrl;
|
|
7
|
+
static template = `
|
|
8
|
+
<div class="root">
|
|
9
|
+
<div class="positioning-region" part="positioning-region">
|
|
10
|
+
<div class="content" part="content">
|
|
11
|
+
<span class="chevron" part="chevron" aria-hidden="true">
|
|
12
|
+
<slot name="chevron">
|
|
13
|
+
<svg viewBox="0 0 12 12" fill="currentColor" width="12" height="12">
|
|
14
|
+
<path d="M4.65 2.15a.5.5 0 000 .7L7.79 6 4.65 9.15a.5.5 0 10.7.7l3.5-3.5a.5.5 0 000-.7l-3.5-3.5a.5.5 0 00-.7 0z"/>
|
|
15
|
+
</svg>
|
|
16
|
+
</slot>
|
|
17
|
+
</span>
|
|
18
|
+
<slot name="start"></slot>
|
|
19
|
+
<slot></slot>
|
|
20
|
+
<slot name="end"></slot>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="aside" part="aside">
|
|
23
|
+
<slot name="aside"></slot>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div role="group" class="items" part="items">
|
|
27
|
+
<slot name="item"></slot>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
static get observedAttributes() {
|
|
33
|
+
return ['expanded', 'selected', 'disabled', 'data-indent', 'appearance', 'size', 'empty'];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
super();
|
|
38
|
+
this._internals = this.attachInternals();
|
|
39
|
+
this._internals.role = 'treeitem';
|
|
40
|
+
this.childTreeItems = [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
connectedCallback() {
|
|
44
|
+
super.connectedCallback();
|
|
45
|
+
|
|
46
|
+
if (this.parentElement && this.parentElement.tagName.toLowerCase() === 'fluent-tree-item') {
|
|
47
|
+
this.setAttribute('slot', 'item');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.setAttribute('tabindex', '-1');
|
|
51
|
+
|
|
52
|
+
const itemSlot = this._root.querySelector('slot[name="item"]');
|
|
53
|
+
if (itemSlot) {
|
|
54
|
+
itemSlot.addEventListener('slotchange', () => this._handleItemSlotChange());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this._handleItemSlotChange();
|
|
58
|
+
this._updateEmpty();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
62
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
63
|
+
|
|
64
|
+
switch (name) {
|
|
65
|
+
case 'expanded':
|
|
66
|
+
this._internals.ariaExpanded = this.childTreeItems.length > 0
|
|
67
|
+
? String(newVal !== null)
|
|
68
|
+
: null;
|
|
69
|
+
this._toggleState('expanded', newVal !== null);
|
|
70
|
+
break;
|
|
71
|
+
case 'selected':
|
|
72
|
+
this._internals.ariaSelected = String(newVal !== null);
|
|
73
|
+
this._toggleState('selected', newVal !== null);
|
|
74
|
+
if (oldVal !== newVal) {
|
|
75
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case 'disabled':
|
|
79
|
+
this._internals.ariaDisabled = newVal !== null ? 'true' : null;
|
|
80
|
+
this._toggleState('disabled', newVal !== null);
|
|
81
|
+
break;
|
|
82
|
+
case 'data-indent':
|
|
83
|
+
this.style.setProperty('--indent', newVal || '0');
|
|
84
|
+
break;
|
|
85
|
+
case 'empty':
|
|
86
|
+
this._toggleState('empty', newVal !== null);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_toggleState(name, value) {
|
|
92
|
+
try {
|
|
93
|
+
if (value) {
|
|
94
|
+
this._internals.states.add(name);
|
|
95
|
+
} else {
|
|
96
|
+
this._internals.states.delete(name);
|
|
97
|
+
}
|
|
98
|
+
} catch (e) { /* states API may not be supported */ }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get selected() { return this.hasAttribute('selected'); }
|
|
102
|
+
set selected(val) {
|
|
103
|
+
if (val) { this.setAttribute('selected', ''); }
|
|
104
|
+
else { this.removeAttribute('selected'); }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get expanded() { return this.hasAttribute('expanded'); }
|
|
108
|
+
set expanded(val) {
|
|
109
|
+
if (val) { this.setAttribute('expanded', ''); }
|
|
110
|
+
else { this.removeAttribute('expanded'); }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get disabled() { return this.hasAttribute('disabled'); }
|
|
114
|
+
|
|
115
|
+
toggleExpansion() {
|
|
116
|
+
if (this.childTreeItems.length > 0) {
|
|
117
|
+
this.expanded = !this.expanded;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get isHidden() {
|
|
122
|
+
let parent = this.parentElement;
|
|
123
|
+
while (parent && parent.tagName.toLowerCase() === 'fluent-tree-item') {
|
|
124
|
+
if (!parent.hasAttribute('expanded')) return true;
|
|
125
|
+
parent = parent.parentElement && parent.parentElement.closest
|
|
126
|
+
? parent.parentElement.closest('fluent-tree-item')
|
|
127
|
+
: null;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get isNestedItem() {
|
|
133
|
+
const parent = this.parentElement;
|
|
134
|
+
return parent && parent.tagName.toLowerCase() === 'fluent-tree-item';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_handleItemSlotChange() {
|
|
138
|
+
const itemSlot = this._root.querySelector('slot[name="item"]');
|
|
139
|
+
if (!itemSlot) return;
|
|
140
|
+
this.childTreeItems = itemSlot.assignedElements().filter(
|
|
141
|
+
el => el.tagName.toLowerCase() === 'fluent-tree-item',
|
|
142
|
+
);
|
|
143
|
+
this._updateEmpty();
|
|
144
|
+
this._updateChildIndent();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_updateEmpty() {
|
|
148
|
+
if (this.childTreeItems.length === 0) {
|
|
149
|
+
this.setAttribute('empty', '');
|
|
150
|
+
} else {
|
|
151
|
+
this.removeAttribute('empty');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_updateChildIndent() {
|
|
156
|
+
const indent = parseInt(this.getAttribute('data-indent') || '0', 10);
|
|
157
|
+
this.childTreeItems.forEach(item => {
|
|
158
|
+
item.setAttribute('data-indent', String(indent + 1));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
customElements.define('fluent-tree-item', FluentTreeItem);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class FluentElement extends HTMLElement {
|
|
2
|
+
static stylesUrl = '';
|
|
3
|
+
static template = '';
|
|
4
|
+
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
this._root = this.attachShadow({ mode: 'open' });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
if (this.constructor.stylesUrl) {
|
|
12
|
+
const link = document.createElement('link');
|
|
13
|
+
link.rel = 'stylesheet';
|
|
14
|
+
link.href = this.constructor.stylesUrl;
|
|
15
|
+
this._root.appendChild(link);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const tmpl = document.createElement('template');
|
|
19
|
+
tmpl.innerHTML = this.constructor.template;
|
|
20
|
+
this._root.appendChild(tmpl.content.cloneNode(true));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static get observedAttributes() {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
28
|
+
if (oldVal !== newVal) this.changed(name, oldVal, newVal);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
changed() {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { FluentElement };
|