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.
Files changed (59) hide show
  1. package/AGENTS.md +212 -0
  2. package/README.md +99 -0
  3. package/components/avatar/fluent-avatar.css +481 -0
  4. package/components/avatar/fluent-avatar.js +80 -0
  5. package/components/badge/fluent-badge.css +289 -0
  6. package/components/badge/fluent-badge.js +20 -0
  7. package/components/breadcrumb/fluent-breadcrumb.css +29 -0
  8. package/components/breadcrumb/fluent-breadcrumb.js +33 -0
  9. package/components/breadcrumb-item/fluent-breadcrumb-item.css +70 -0
  10. package/components/breadcrumb-item/fluent-breadcrumb-item.js +77 -0
  11. package/components/button/fluent-button.css +265 -0
  12. package/components/button/fluent-button.js +326 -0
  13. package/components/card/fluent-card.css +85 -0
  14. package/components/card/fluent-card.js +21 -0
  15. package/components/checkbox/fluent-checkbox.css +171 -0
  16. package/components/checkbox/fluent-checkbox.js +294 -0
  17. package/components/dialog/fluent-dialog.css +82 -0
  18. package/components/dialog/fluent-dialog.js +137 -0
  19. package/components/divider/fluent-divider.css +124 -0
  20. package/components/divider/fluent-divider.js +14 -0
  21. package/components/image/fluent-image.css +73 -0
  22. package/components/image/fluent-image.js +36 -0
  23. package/components/label/fluent-label.css +49 -0
  24. package/components/label/fluent-label.js +61 -0
  25. package/components/link/fluent-link.css +72 -0
  26. package/components/link/fluent-link.js +109 -0
  27. package/components/menu/fluent-menu.css +57 -0
  28. package/components/menu/fluent-menu.js +202 -0
  29. package/components/menu-item/fluent-menu-item.css +152 -0
  30. package/components/menu-item/fluent-menu-item.js +177 -0
  31. package/components/popover/fluent-popover.css +95 -0
  32. package/components/popover/fluent-popover.js +93 -0
  33. package/components/radio/fluent-radio.css +123 -0
  34. package/components/radio/fluent-radio.js +257 -0
  35. package/components/select/fluent-select.css +194 -0
  36. package/components/select/fluent-select.js +245 -0
  37. package/components/slider/fluent-slider.css +199 -0
  38. package/components/slider/fluent-slider.js +438 -0
  39. package/components/spinner/fluent-spinner.css +160 -0
  40. package/components/spinner/fluent-spinner.js +30 -0
  41. package/components/switch/fluent-switch.css +154 -0
  42. package/components/switch/fluent-switch.js +260 -0
  43. package/components/text/fluent-text.css +128 -0
  44. package/components/text/fluent-text.js +21 -0
  45. package/components/text-input/fluent-text-input.css +227 -0
  46. package/components/text-input/fluent-text-input.js +298 -0
  47. package/components/textarea/fluent-textarea.css +227 -0
  48. package/components/textarea/fluent-textarea.js +400 -0
  49. package/components/tooltip/fluent-tooltip.css +65 -0
  50. package/components/tooltip/fluent-tooltip.js +102 -0
  51. package/components/tree/fluent-tree.css +16 -0
  52. package/components/tree/fluent-tree.js +167 -0
  53. package/components/tree-item/fluent-tree-item.css +147 -0
  54. package/components/tree-item/fluent-tree-item.js +163 -0
  55. package/core/fluent-element.js +34 -0
  56. package/gallery.html +492 -0
  57. package/package.json +19 -0
  58. package/theme/theme-picker.js +38 -0
  59. 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 };