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,202 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-menu.css', import.meta.url).href;
4
+
5
+ class FluentMenu extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static template = `
8
+ <div class="root">
9
+ <slot name="primary-action"></slot>
10
+ <slot name="trigger"></slot>
11
+ <slot></slot>
12
+ </div>
13
+ `;
14
+
15
+ static get observedAttributes() {
16
+ return ['open-on-hover', 'open-on-context', 'close-on-scroll', 'persist-on-item-click', 'split'];
17
+ }
18
+
19
+ constructor() {
20
+ super();
21
+ this._open = false;
22
+ this._trigger = null;
23
+ this._menuList = null;
24
+ this._triggerAbortController = null;
25
+ this._menuListAbortController = null;
26
+ }
27
+
28
+ connectedCallback() {
29
+ super.connectedCallback();
30
+ this._setupSlots();
31
+ this.setComponent();
32
+ }
33
+
34
+ disconnectedCallback() {
35
+ super.disconnectedCallback();
36
+ if (this._triggerAbortController) {
37
+ this._triggerAbortController.abort();
38
+ this._triggerAbortController = null;
39
+ }
40
+ if (this._menuListAbortController) {
41
+ this._menuListAbortController.abort();
42
+ this._menuListAbortController = null;
43
+ }
44
+ }
45
+
46
+ setComponent() {
47
+ this._setupTriggerSlot();
48
+ this._setupMenuListSlot();
49
+ }
50
+
51
+ _setupSlots() {
52
+ const triggerSlot = this._root.querySelector('slot[name="trigger"]');
53
+ const defaultSlot = this._root.querySelector('slot:not([name])');
54
+
55
+ if (triggerSlot) {
56
+ triggerSlot.addEventListener('slotchange', () => this._setupTriggerSlot());
57
+ }
58
+ if (defaultSlot) {
59
+ defaultSlot.addEventListener('slotchange', () => this._setupMenuListSlot());
60
+ }
61
+
62
+ this._setupTriggerSlot();
63
+ this._setupMenuListSlot();
64
+ }
65
+
66
+ _setupTriggerSlot() {
67
+ if (this._triggerAbortController) {
68
+ this._triggerAbortController.abort();
69
+ this._triggerAbortController = null;
70
+ }
71
+
72
+ const triggerSlot = this._root.querySelector('slot[name="trigger"]');
73
+ if (!triggerSlot) return;
74
+ const assigned = triggerSlot.assignedElements();
75
+ if (assigned.length === 0) return;
76
+ this._trigger = assigned[0];
77
+ this._trigger.setAttribute('aria-haspopup', 'true');
78
+ this._trigger.setAttribute('aria-expanded', String(this._open));
79
+
80
+ this._triggerAbortController = new AbortController();
81
+ const { signal } = this._triggerAbortController;
82
+
83
+ this._trigger.addEventListener('keydown', this._triggerKeydownHandler, { signal });
84
+
85
+ if (this.hasAttribute('open-on-hover')) {
86
+ this._trigger.addEventListener('mouseover', this.openMenu, { signal });
87
+ } else if (this.hasAttribute('open-on-context')) {
88
+ this._trigger.addEventListener('contextmenu', this.openMenu, { signal });
89
+ document.addEventListener('click', this._documentClickHandler, { signal });
90
+ } else {
91
+ this._trigger.addEventListener('click', this.toggleMenu, { signal });
92
+ }
93
+ }
94
+
95
+ _setupMenuListSlot() {
96
+ if (this._menuListAbortController) {
97
+ this._menuListAbortController.abort();
98
+ this._menuListAbortController = null;
99
+ }
100
+
101
+ const defaultSlot = this._root.querySelector('slot:not([name])');
102
+ if (!defaultSlot) return;
103
+ const assigned = defaultSlot.assignedElements();
104
+ if (assigned.length === 0) return;
105
+
106
+ // Use a wrapper div as menu list if first child is a menu-item
107
+ if (assigned[0].tagName.toLowerCase() === 'fluent-menu-item') {
108
+ // All children are menu items, render them directly
109
+ this._menuList = this;
110
+ } else {
111
+ this._menuList = assigned[0];
112
+ }
113
+
114
+ if (this._menuList && this._menuList !== this) {
115
+ this._menuList.setAttribute('popover', this.hasAttribute('open-on-context') ? 'manual' : '');
116
+ this._menuListAbortController = new AbortController();
117
+ const { signal } = this._menuListAbortController;
118
+
119
+ this._menuList.addEventListener('toggle', this._toggleHandler, { signal });
120
+
121
+ if (!this.hasAttribute('persist-on-item-click')) {
122
+ this._menuList.addEventListener('change', this.closeMenu, { signal });
123
+ }
124
+ }
125
+ }
126
+
127
+ toggleMenu = () => {
128
+ if (this._menuList && this._menuList !== this) {
129
+ this._menuList.togglePopover(!this._open);
130
+ }
131
+ };
132
+
133
+ closeMenu = (event) => {
134
+ if (this._menuList && this._menuList !== this) {
135
+ this._menuList.togglePopover(false);
136
+ }
137
+ if (this.hasAttribute('close-on-scroll')) {
138
+ document.removeEventListener('scroll', this.closeMenu);
139
+ }
140
+ };
141
+
142
+ openMenu = (e) => {
143
+ if (this._menuList && this._menuList !== this) {
144
+ this._menuList.togglePopover(true);
145
+ }
146
+ if (e && this.hasAttribute('open-on-context')) {
147
+ e.preventDefault();
148
+ }
149
+ if (this.hasAttribute('close-on-scroll')) {
150
+ document.addEventListener('scroll', this.closeMenu);
151
+ }
152
+ };
153
+
154
+ focusMenuList() {
155
+ if (this._menuList) {
156
+ this._menuList.focus();
157
+ }
158
+ }
159
+
160
+ focusTrigger() {
161
+ if (this._trigger) {
162
+ this._trigger.focus();
163
+ }
164
+ }
165
+
166
+ _toggleHandler = (e) => {
167
+ if (e.type === 'toggle' && e.newState) {
168
+ const open = e.newState === 'open';
169
+ if (this._trigger) {
170
+ this._trigger.setAttribute('aria-expanded', String(open));
171
+ }
172
+ if (this._menuList) {
173
+ this._menuList.setAttribute('focusgroup', open ? 'menu' : 'none');
174
+ }
175
+ this._open = open;
176
+ if (this._open) {
177
+ this.focusMenuList();
178
+ }
179
+ }
180
+ };
181
+
182
+ _triggerKeydownHandler = (e) => {
183
+ if (e.defaultPrevented) return;
184
+ switch (e.key) {
185
+ case ' ':
186
+ case 'Enter':
187
+ e.preventDefault();
188
+ this.toggleMenu();
189
+ break;
190
+ default:
191
+ return true;
192
+ }
193
+ };
194
+
195
+ _documentClickHandler = (e) => {
196
+ if (!e.composedPath().some(el => el === this._trigger || el === this._menuList)) {
197
+ this.closeMenu();
198
+ }
199
+ };
200
+ }
201
+
202
+ customElements.define('fluent-menu', FluentMenu);
@@ -0,0 +1,152 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: block; }
4
+
5
+ .root {
6
+ --indent: 0;
7
+ display: grid;
8
+ align-items: center;
9
+ background: var(--colorNeutralBackground1);
10
+ border-radius: var(--borderRadiusMedium);
11
+ color: var(--colorNeutralForeground2);
12
+ cursor: pointer;
13
+ flex-shrink: 0;
14
+ font: var(--fontWeightRegular) var(--fontSizeBase300) / var(--lineHeightBase300) var(--fontFamilyBase);
15
+ grid-gap: 4px;
16
+ grid-template-columns: 20px 20px auto 20px;
17
+ height: 32px;
18
+ overflow: visible;
19
+ padding: 0 10px;
20
+ }
21
+
22
+ .root:hover {
23
+ background: var(--colorNeutralBackground1Hover);
24
+ color: var(--colorNeutralForeground2Hover);
25
+ }
26
+
27
+ .root:active {
28
+ background-color: var(--colorNeutralBackground1Selected);
29
+ color: var(--colorNeutralForeground2Pressed);
30
+ }
31
+
32
+ :host(:active) ::slotted([slot='start']) {
33
+ color: var(--colorCompoundBrandForeground1Pressed);
34
+ }
35
+
36
+ :host(:state(disabled)) .root {
37
+ background-color: var(--colorNeutralBackgroundDisabled);
38
+ color: var(--colorNeutralForegroundDisabled);
39
+ }
40
+
41
+ :host(:state(disabled)) ::slotted([slot='start']),
42
+ :host(:state(disabled)) ::slotted([slot='end']) {
43
+ color: var(--colorNeutralForegroundDisabled);
44
+ }
45
+
46
+ .root:focus-visible {
47
+ border-radius: var(--borderRadiusMedium);
48
+ outline: var(--strokeWidthThick) solid var(--colorStrokeFocus2);
49
+ }
50
+
51
+ .content {
52
+ white-space: nowrap;
53
+ flex-grow: 1;
54
+ grid-column: auto / span 2;
55
+ padding: 0 2px;
56
+ }
57
+
58
+ :host(:not(:state(checked))) .indicator,
59
+ :host(:not(:state(checked))) ::slotted([slot='indicator']),
60
+ :host(:not(:state(submenu))) .submenu-glyph,
61
+ :host(:not(:state(submenu))) ::slotted([slot='submenu-glyph']) {
62
+ display: none;
63
+ }
64
+
65
+ ::slotted([slot='end']) {
66
+ color: var(--colorNeutralForeground3);
67
+ font: var(--fontWeightRegular) var(--fontSizeBase200) / var(--lineHeightBase200) var(--fontFamilyBase);
68
+ white-space: nowrap;
69
+ }
70
+
71
+ :host([data-indent='1']) {
72
+ --indent: 1;
73
+ }
74
+
75
+ :host([data-indent='2']) .root {
76
+ --indent: 2;
77
+ grid-template-columns: 20px 20px auto auto;
78
+ }
79
+
80
+ :host(:state(submenu)) .root {
81
+ grid-template-columns: 20px auto auto 20px;
82
+ }
83
+
84
+ :host([data-indent='2']:state(submenu)) .root {
85
+ grid-template-columns: 20px 20px auto auto 20px;
86
+ }
87
+
88
+ .indicator,
89
+ ::slotted([slot='indicator']) {
90
+ grid-column: 1 / span 1;
91
+ width: 20px;
92
+ }
93
+
94
+ ::slotted([slot='start']) {
95
+ display: inline-flex;
96
+ grid-column: calc(var(--indent)) / span 1;
97
+ }
98
+
99
+ .content {
100
+ grid-column: calc(var(--indent) + 1) / span 1;
101
+ }
102
+
103
+ ::slotted([slot='end']) {
104
+ grid-column: calc(var(--indent) + 2) / span 1;
105
+ justify-self: end;
106
+ }
107
+
108
+ .submenu-glyph,
109
+ ::slotted([slot='submenu-glyph']) {
110
+ grid-column: -2 / span 1;
111
+ justify-self: end;
112
+ }
113
+
114
+ /* Submenu popover positioning */
115
+ @layer popover {
116
+ :host {
117
+ anchor-name: --menu-trigger;
118
+ position: relative;
119
+ }
120
+
121
+ ::slotted([popover]) {
122
+ margin: 0;
123
+ max-height: var(--menu-max-height, auto);
124
+ position: absolute;
125
+ position-anchor: --menu-trigger;
126
+ position-area: inline-end span-block-end;
127
+ position-try-fallbacks: flip-inline, block-start, block-end;
128
+ z-index: 1;
129
+ }
130
+
131
+ ::slotted([popover]:not(:popover-open)) {
132
+ display: none;
133
+ }
134
+
135
+ ::slotted([popover]:popover-open) {
136
+ inset: unset;
137
+ }
138
+
139
+ @supports not (anchor-name: --menu-trigger) {
140
+ ::slotted([popover]) {
141
+ align-self: start;
142
+ }
143
+ }
144
+ }
145
+
146
+ @media (forced-colors: active) {
147
+ :host(:state(disabled)),
148
+ :host(:state(disabled)) ::slotted([slot='start']),
149
+ :host(:state(disabled)) ::slotted([slot='end']) {
150
+ color: GrayText;
151
+ }
152
+ }
@@ -0,0 +1,177 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-menu-item.css', import.meta.url).href;
4
+
5
+ class FluentMenuItem extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static template = `
8
+ <div class="root">
9
+ <slot name="indicator">
10
+ <svg class="indicator" fill="currentColor" aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
11
+ <path d="M14.05 3.49c.28.3.27.77-.04 1.06l-7.93 7.47A.85.85 0 014.9 12L2.22 9.28a.75.75 0 111.06-1.06l2.24 2.27 7.47-7.04a.75.75 0 011.06.04z" fill="currentColor"/>
12
+ </svg>
13
+ </slot>
14
+ <slot name="start"></slot>
15
+ <div class="content" part="content">
16
+ <slot></slot>
17
+ </div>
18
+ <slot name="end"></slot>
19
+ <slot name="submenu-glyph">
20
+ <svg class="submenu-glyph" fill="currentColor" aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
21
+ <path d="M5.74 3.2a.75.75 0 00-.04 1.06L9.23 8 5.7 11.74a.75.75 0 101.1 1.02l4-4.25a.75.75 0 000-1.02l-4-4.25a.75.75 0 00-1.06-.04z" fill="currentColor"/>
22
+ </svg>
23
+ </slot>
24
+ <slot name="submenu"></slot>
25
+ </div>
26
+ `;
27
+
28
+ static formAssociated = true;
29
+
30
+ static get observedAttributes() {
31
+ return ['checked', 'disabled', 'role'];
32
+ }
33
+
34
+ constructor() {
35
+ super();
36
+ this._internals = this.attachInternals();
37
+ this._internals.role = 'menuitem';
38
+ }
39
+
40
+ connectedCallback() {
41
+ super.connectedCallback();
42
+ this.setAttribute('tabindex', '0');
43
+ this.addEventListener('keydown', this._handleKeyDown);
44
+ this.addEventListener('click', this._handleClick);
45
+ this.addEventListener('mouseover', this._handleMouseOver);
46
+ this.addEventListener('mouseout', this._handleMouseOut);
47
+ this.addEventListener('toggle', this._handleToggle);
48
+
49
+ this._internals.role = this.getAttribute('role') || 'menuitem';
50
+
51
+ const role = this._internals.role;
52
+ if (role === 'menuitemcheckbox' || role === 'menuitemradio') {
53
+ this._internals.ariaChecked = String(!!this.hasAttribute('checked'));
54
+ }
55
+ }
56
+
57
+ attributeChangedCallback(name, oldVal, newVal) {
58
+ super.attributeChangedCallback(name, oldVal, newVal);
59
+ if (name === 'disabled') {
60
+ const isDisabled = newVal !== null;
61
+ this._internals.ariaDisabled = isDisabled ? 'true' : null;
62
+ this._toggleState('disabled', isDisabled);
63
+ }
64
+ if (name === 'checked') {
65
+ const isChecked = newVal !== null;
66
+ const checkable = this._internals.role === 'menuitemcheckbox' || this._internals.role === 'menuitemradio';
67
+ this._internals.ariaChecked = checkable ? String(isChecked) : null;
68
+ this._toggleState('checked', checkable ? isChecked : false);
69
+ }
70
+ if (name === 'role' && newVal) {
71
+ this._internals.role = newVal;
72
+ }
73
+ }
74
+
75
+ _toggleState(name, value) {
76
+ try {
77
+ if (value) {
78
+ this._internals.states.add(name);
79
+ } else {
80
+ this._internals.states.delete(name);
81
+ }
82
+ } catch (e) { /* states API may not be supported */ }
83
+ }
84
+
85
+ _handleKeyDown = (e) => {
86
+ if (e.defaultPrevented) return;
87
+ switch (e.key) {
88
+ case 'Enter':
89
+ case ' ':
90
+ this._invoke();
91
+ break;
92
+ case 'ArrowRight':
93
+ if (!this.hasAttribute('disabled')) {
94
+ const submenu = this.querySelector('[slot="submenu"]');
95
+ if (submenu) {
96
+ try { submenu.togglePopover(true); } catch (ex) { }
97
+ submenu.focus();
98
+ }
99
+ }
100
+ break;
101
+ case 'ArrowLeft':
102
+ if (this.parentElement && this.parentElement.hasAttribute('popover')) {
103
+ try { this.parentElement.togglePopover(false); } catch (ex) { }
104
+ if (this.parentElement.parentElement) {
105
+ this.parentElement.parentElement.focus();
106
+ }
107
+ }
108
+ break;
109
+ default:
110
+ return;
111
+ }
112
+ e.preventDefault();
113
+ };
114
+
115
+ _handleClick = (e) => {
116
+ if (e.defaultPrevented || this.hasAttribute('disabled')) return;
117
+ this._invoke();
118
+ };
119
+
120
+ _handleMouseOver = (e) => {
121
+ if (this.hasAttribute('disabled')) return;
122
+ const submenu = this.querySelector('[slot="submenu"]');
123
+ if (submenu) {
124
+ try { submenu.togglePopover(true); } catch (ex) { }
125
+ }
126
+ };
127
+
128
+ _handleMouseOut = (e) => {
129
+ const submenu = this.querySelector('[slot="submenu"]');
130
+ if (submenu && !this.contains(document.activeElement)) {
131
+ try { submenu.togglePopover(false); } catch (ex) { }
132
+ }
133
+ };
134
+
135
+ _handleToggle = (e) => {
136
+ if (!(e instanceof ToggleEvent)) return;
137
+ const submenu = this.querySelector('[slot="submenu"]');
138
+ if (!submenu) return;
139
+
140
+ if (e.newState === 'open') {
141
+ this._internals.ariaExpanded = 'true';
142
+ } else if (e.newState === 'closed') {
143
+ this._internals.ariaExpanded = 'false';
144
+ }
145
+ submenu.setAttribute('focusgroup', e.newState === 'open' ? 'menu' : 'none');
146
+ };
147
+
148
+ _invoke() {
149
+ if (this.hasAttribute('disabled')) return;
150
+ const role = this._internals.role;
151
+
152
+ if (role === 'menuitemcheckbox') {
153
+ const checked = this.hasAttribute('checked');
154
+ if (checked) {
155
+ this.removeAttribute('checked');
156
+ } else {
157
+ this.setAttribute('checked', '');
158
+ }
159
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: !checked }));
160
+ } else if (role === 'menuitemradio') {
161
+ if (!this.hasAttribute('checked')) {
162
+ this.setAttribute('checked', '');
163
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: true }));
164
+ }
165
+ } else if (role === 'menuitem') {
166
+ const submenu = this.querySelector('[slot="submenu"]');
167
+ if (submenu) {
168
+ try { submenu.togglePopover(true); } catch (ex) { }
169
+ submenu.focus();
170
+ } else {
171
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ customElements.define('fluent-menu-item', FluentMenuItem);
@@ -0,0 +1,95 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: block; }
4
+
5
+ .root {
6
+ display: inline-block;
7
+ position: relative;
8
+ }
9
+
10
+ ::slotted([slot='trigger']) {
11
+ cursor: pointer;
12
+ }
13
+
14
+ .popover {
15
+ display: none;
16
+ position: absolute;
17
+ inset: unset;
18
+ top: calc(100% + var(--spacingVerticalS));
19
+ left: 50%;
20
+ translate: -50% 0;
21
+ background: var(--colorNeutralBackground1);
22
+ border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
23
+ border-radius: var(--borderRadiusMedium);
24
+ box-shadow: var(--shadow16);
25
+ color: var(--colorNeutralForeground1);
26
+ font-family: var(--fontFamilyBase);
27
+ font-size: var(--fontSizeBase300);
28
+ line-height: var(--lineHeightBase300);
29
+ min-width: 120px;
30
+ padding: var(--spacingVerticalXS) 0;
31
+ z-index: 1000;
32
+ white-space: nowrap;
33
+ }
34
+
35
+ .arrow {
36
+ display: none;
37
+ position: absolute;
38
+ top: calc(-1 * var(--strokeWidthThick));
39
+ left: 50%;
40
+ translate: -50% 0;
41
+ width: 0;
42
+ height: 0;
43
+ border-left: 8px solid transparent;
44
+ border-right: 8px solid transparent;
45
+ border-bottom: 8px solid var(--colorNeutralStroke1);
46
+ }
47
+
48
+ .arrow::after {
49
+ content: '';
50
+ position: absolute;
51
+ top: var(--strokeWidthThin);
52
+ left: -8px;
53
+ width: 0;
54
+ height: 0;
55
+ border-left: 8px solid transparent;
56
+ border-right: 8px solid transparent;
57
+ border-bottom: 8px solid var(--colorNeutralBackground1);
58
+ }
59
+
60
+ :host([positioning='above']) .popover {
61
+ top: unset;
62
+ bottom: calc(100% + var(--spacingVerticalS));
63
+ }
64
+
65
+ :host([positioning='above']) .arrow {
66
+ top: unset;
67
+ bottom: calc(-1 * var(--strokeWidthThick));
68
+ border-bottom: none;
69
+ border-top: 8px solid var(--colorNeutralStroke1);
70
+ }
71
+
72
+ :host([positioning='above']) .arrow::after {
73
+ top: calc(-1 * (var(--strokeWidthThick) + var(--strokeWidthThin)));
74
+ border-bottom: none;
75
+ border-top: 8px solid var(--colorNeutralBackground1);
76
+ }
77
+
78
+ :host([positioning='before']) .popover {
79
+ left: unset;
80
+ top: 50%;
81
+ right: calc(100% + var(--spacingHorizontalS));
82
+ translate: 0 -50%;
83
+ }
84
+
85
+ :host([positioning='after']) .popover {
86
+ left: calc(100% + var(--spacingHorizontalS));
87
+ top: 50%;
88
+ translate: 0 -50%;
89
+ }
90
+
91
+ @media (forced-colors: active) {
92
+ .popover {
93
+ border-color: ButtonText;
94
+ }
95
+ }