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,245 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-select.css', import.meta.url).href;
4
+
5
+ class FluentSelect extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static formAssociated = true;
8
+
9
+ static template = `
10
+ <label for="control" part="label">
11
+ <slot name="label"></slot>
12
+ </label>
13
+ <div class="root" part="root">
14
+ <select id="control" class="control" part="control"></select>
15
+ <span class="icon" part="icon">
16
+ <slot name="icon">
17
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true">
18
+ <path d="M2.15 4.65a.5.5 0 0 1 .7-.7L6 7.09l3.15-3.14a.5.5 0 1 1 .7.7l-3.5 3.5a.5.5 0 0 1-.7 0l-3.5-3.5Z"></path>
19
+ </svg>
20
+ </slot>
21
+ </span>
22
+ </div>
23
+ <div hidden>
24
+ <slot></slot>
25
+ </div>
26
+ `;
27
+
28
+ static get observedAttributes() {
29
+ return [
30
+ 'disabled', 'required', 'name', 'value',
31
+ 'appearance', 'control-size', 'autofocus', 'autocomplete',
32
+ 'aria-label', 'aria-labelledby', 'aria-describedby'
33
+ ];
34
+ }
35
+
36
+ constructor() {
37
+ super();
38
+ this._internals = this.attachInternals();
39
+ this._userInteracted = false;
40
+ this._boundHandleInput = this._handleInput.bind(this);
41
+ this._boundHandleChange = this._handleChange.bind(this);
42
+ }
43
+
44
+ connectedCallback() {
45
+ super.connectedCallback();
46
+ this._shadowRoot = this._root;
47
+
48
+ requestAnimationFrame(() => {
49
+ if (!this.isConnected) return;
50
+
51
+ const selectEl = this._shadowRoot.querySelector('.control');
52
+ if (!selectEl) return;
53
+ this._selectEl = selectEl;
54
+
55
+ this._moveOptionsToNativeSelect();
56
+ this._syncAllAttrs();
57
+
58
+ const initialVal = this.getAttribute('value') || '';
59
+ if (initialVal) {
60
+ this._selectEl.value = initialVal;
61
+ }
62
+
63
+ this._setFormValue(this._selectEl.value);
64
+ this._setValidity();
65
+
66
+ this._selectEl.addEventListener('input', this._boundHandleInput);
67
+ this._selectEl.addEventListener('change', this._boundHandleChange);
68
+
69
+ this._updateLabelVisibility();
70
+ });
71
+ }
72
+
73
+ changed(name, oldVal, newVal) {
74
+ if (!this._selectEl) return;
75
+ switch (name) {
76
+ case 'disabled':
77
+ this._selectEl.disabled = newVal !== null;
78
+ this._internals.ariaDisabled = newVal !== null ? 'true' : 'false';
79
+ break;
80
+ case 'required':
81
+ this._selectEl.required = newVal !== null;
82
+ this._internals.ariaRequired = newVal !== null ? 'true' : 'false';
83
+ this._setValidity();
84
+ break;
85
+ case 'name':
86
+ this._selectEl.name = newVal || '';
87
+ break;
88
+ case 'value':
89
+ if (!this._userInteracted && newVal !== null) {
90
+ this._selectEl.value = newVal;
91
+ }
92
+ break;
93
+ case 'autocomplete':
94
+ this._selectEl.autocomplete = newVal || 'off';
95
+ break;
96
+ case 'autofocus':
97
+ if (newVal !== null) {
98
+ this._selectEl.focus();
99
+ }
100
+ break;
101
+ case 'aria-label':
102
+ case 'aria-labelledby':
103
+ case 'aria-describedby':
104
+ case 'appearance':
105
+ case 'control-size':
106
+ break;
107
+ }
108
+ }
109
+
110
+ get value() {
111
+ return this._selectEl ? this._selectEl.value : (this.getAttribute('value') || '');
112
+ }
113
+
114
+ set value(val) {
115
+ if (this._selectEl) {
116
+ this._selectEl.value = val;
117
+ this._setFormValue(val);
118
+ }
119
+ }
120
+
121
+ get disabled() {
122
+ return this.hasAttribute('disabled');
123
+ }
124
+
125
+ get form() {
126
+ return this._internals.form;
127
+ }
128
+
129
+ get labels() {
130
+ return this._internals.labels;
131
+ }
132
+
133
+ get validity() {
134
+ return this._internals.validity;
135
+ }
136
+
137
+ get validationMessage() {
138
+ return this._internals.validationMessage || (this._selectEl ? this._selectEl.validationMessage : '');
139
+ }
140
+
141
+ get willValidate() {
142
+ return this._internals.willValidate;
143
+ }
144
+
145
+ checkValidity() {
146
+ return this._internals.checkValidity();
147
+ }
148
+
149
+ reportValidity() {
150
+ return this._internals.reportValidity();
151
+ }
152
+
153
+ setCustomValidity(message) {
154
+ this._internals.setValidity({ customError: !!message }, message || undefined);
155
+ this.reportValidity();
156
+ }
157
+
158
+ _moveOptionsToNativeSelect() {
159
+ const defaultSlot = this._root.querySelector('slot:not([name])');
160
+ if (!defaultSlot) return;
161
+
162
+ const nodes = defaultSlot.assignedNodes();
163
+ nodes.forEach(node => {
164
+ if (node.tagName === 'OPTION' || node.tagName === 'OPTGROUP') {
165
+ this._selectEl.appendChild(node.cloneNode(true));
166
+ }
167
+ });
168
+ }
169
+
170
+ _syncAllAttrs() {
171
+ const sel = this._selectEl;
172
+ if (!sel) return;
173
+ sel.disabled = this.hasAttribute('disabled');
174
+ sel.required = this.hasAttribute('required');
175
+ sel.name = this.getAttribute('name') || '';
176
+ sel.autocomplete = this.getAttribute('autocomplete') || 'off';
177
+ if (this.hasAttribute('autofocus')) sel.autofocus = true;
178
+ this._internals.ariaDisabled = this.hasAttribute('disabled') ? 'true' : 'false';
179
+ this._internals.ariaRequired = this.hasAttribute('required') ? 'true' : 'false';
180
+ }
181
+
182
+ _handleInput(e) {
183
+ this._userInteracted = true;
184
+ this._setFormValue(this._selectEl.value);
185
+ this._setValidity();
186
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
187
+ }
188
+
189
+ _handleChange(e) {
190
+ this._setValidity();
191
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
192
+ }
193
+
194
+ _setFormValue(value) {
195
+ this._internals.setFormValue(value, value);
196
+ }
197
+
198
+ _setValidity() {
199
+ if (!this.isConnected) return;
200
+ if (this.disabled) {
201
+ this._internals.setValidity({});
202
+ return;
203
+ }
204
+ if (this._selectEl) {
205
+ const { valid, valueMissing } = this._selectEl.validity;
206
+ if (!valid) {
207
+ const msg = this._selectEl.validationMessage || 'Invalid value';
208
+ this._internals.setValidity({ valueMissing }, msg, this._selectEl);
209
+ } else {
210
+ this._internals.setValidity({});
211
+ }
212
+ }
213
+ }
214
+
215
+ _updateLabelVisibility() {
216
+ const label = this._shadowRoot.querySelector('label');
217
+ if (!label) return;
218
+ const labelSlot = this._root.querySelector('slot[name="label"]');
219
+ if (!labelSlot) return;
220
+ const nodes = labelSlot.assignedNodes();
221
+ const hasContent = nodes.some(n =>
222
+ n.nodeType === Node.ELEMENT_NODE ||
223
+ (n.nodeType === Node.TEXT_NODE && n.textContent.trim())
224
+ );
225
+ label.hidden = !hasContent;
226
+ }
227
+
228
+ formResetCallback() {
229
+ if (this._selectEl) {
230
+ this._selectEl.value = this.getAttribute('value') || '';
231
+ this._userInteracted = false;
232
+ this._setValidity();
233
+ }
234
+ }
235
+
236
+ formDisabledCallback(disabled) {
237
+ if (this._selectEl) {
238
+ this._selectEl.disabled = disabled;
239
+ }
240
+ this._internals.ariaDisabled = disabled ? 'true' : 'false';
241
+ this._setValidity();
242
+ }
243
+ }
244
+
245
+ customElements.define('fluent-select', FluentSelect);
@@ -0,0 +1,199 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: inline-grid; }
4
+
5
+ :host([hidden]) .root {
6
+ display: none;
7
+ }
8
+
9
+ .root {
10
+ --thumb-size: 20px;
11
+ --track-margin-inline: calc(var(--thumb-size) / 2);
12
+ --track-size: 4px;
13
+ --track-overhang: calc(var(--track-size) / -2);
14
+ --rail-color: var(--colorCompoundBrandBackground);
15
+ --track-color: var(--colorNeutralStrokeAccessible);
16
+ --slider-direction: 90deg;
17
+ --border-radius: var(--borderRadiusMedium);
18
+ --step-marker-inset: var(--track-overhang) -1px;
19
+ --slider-thumb: 50%;
20
+ --slider-progress: 50%;
21
+ --step-rate: 0%;
22
+
23
+ position: relative;
24
+ align-items: center;
25
+ justify-content: center;
26
+ box-sizing: border-box;
27
+ outline: none;
28
+ user-select: none;
29
+ touch-action: none;
30
+ min-width: 120px;
31
+ min-height: 32px;
32
+ display: inline-grid;
33
+ grid-template-rows: 1fr var(--thumb-size) 1fr;
34
+ grid-template-columns: var(--track-margin-inline) 1fr var(--track-margin-inline);
35
+ }
36
+
37
+ .root:hover {
38
+ --rail-color: var(--colorCompoundBrandBackgroundHover);
39
+ }
40
+
41
+ .root:active {
42
+ --rail-color: var(--colorCompoundBrandBackgroundPressed);
43
+ }
44
+
45
+ :host([disabled]) .root {
46
+ --rail-color: var(--colorNeutralForegroundDisabled);
47
+ --track-color: var(--colorNeutralBackgroundDisabled);
48
+ }
49
+
50
+ :host(:not([disabled])) .root {
51
+ cursor: pointer;
52
+ }
53
+
54
+ :host(:dir(rtl)) .root {
55
+ --slider-direction: -90deg;
56
+ }
57
+
58
+ :host([size='small']) .root {
59
+ --thumb-size: 16px;
60
+ --track-overhang: -1px;
61
+ --track-size: 2px;
62
+ --border-radius: var(--borderRadiusSmall);
63
+ }
64
+
65
+ :host([orientation='vertical']) .root {
66
+ --slider-direction: 0deg;
67
+ --step-marker-inset: -1px var(--track-overhang);
68
+ min-height: 120px;
69
+ grid-template-rows: var(--track-margin-inline) 1fr var(--track-margin-inline);
70
+ grid-template-columns: 1fr var(--thumb-size) 1fr;
71
+ width: unset;
72
+ min-width: 32px;
73
+ justify-items: center;
74
+ }
75
+
76
+ :host(:not([slot='input'])) .root:focus-visible {
77
+ box-shadow: 0 0 0 2pt var(--colorStrokeFocus2);
78
+ outline: 1px solid var(--colorStrokeFocus1);
79
+ }
80
+
81
+ .root::after,
82
+ .track {
83
+ height: var(--track-size);
84
+ width: 100%;
85
+ }
86
+
87
+ .root::after {
88
+ background-image: linear-gradient(
89
+ var(--slider-direction),
90
+ var(--rail-color) 0%,
91
+ var(--rail-color) 50%,
92
+ var(--track-color) 50.1%,
93
+ var(--track-color) 100%
94
+ );
95
+ border-radius: var(--border-radius);
96
+ content: '';
97
+ grid-row: 1 / -1;
98
+ grid-column: 1 / -1;
99
+ }
100
+
101
+ .track {
102
+ position: relative;
103
+ background-color: var(--track-color);
104
+ grid-row: 2 / 2;
105
+ grid-column: 2 / 2;
106
+ forced-color-adjust: none;
107
+ overflow: hidden;
108
+ }
109
+
110
+ :host([orientation='vertical']) .root::after,
111
+ :host([orientation='vertical']) .root .track {
112
+ height: 100%;
113
+ width: var(--track-size);
114
+ }
115
+
116
+ .track::before {
117
+ content: '';
118
+ position: absolute;
119
+ height: 100%;
120
+ border-radius: inherit;
121
+ inset-inline-start: 0;
122
+ width: var(--slider-progress);
123
+ }
124
+
125
+ :host(:dir(rtl)) .root .track::before {
126
+ width: calc(100% - var(--slider-progress));
127
+ }
128
+
129
+ :host([orientation='vertical']) .root .track::before {
130
+ width: 100%;
131
+ bottom: 0;
132
+ height: var(--slider-progress);
133
+ }
134
+
135
+ :host([step]) .root .track::after {
136
+ content: '';
137
+ position: absolute;
138
+ border-radius: inherit;
139
+ inset: var(--step-marker-inset);
140
+ background-image: repeating-linear-gradient(
141
+ var(--slider-direction),
142
+ #0000 0%,
143
+ #0000 calc(var(--step-rate) - 1px),
144
+ var(--colorNeutralBackground1) calc(var(--step-rate) - 1px),
145
+ var(--colorNeutralBackground1) var(--step-rate)
146
+ );
147
+ }
148
+
149
+ .thumb-container {
150
+ position: absolute;
151
+ grid-row: 2 / 2;
152
+ grid-column: 2 / 2;
153
+ transform: translateX(-50%);
154
+ left: var(--slider-thumb);
155
+ }
156
+
157
+ :host([orientation='vertical']) .root .thumb-container {
158
+ transform: translateY(50%);
159
+ left: unset;
160
+ bottom: var(--slider-thumb);
161
+ }
162
+
163
+ :host(:not(:active)) .root :is(.thumb-container, .track::before) {
164
+ transition: all 0.2s ease;
165
+ }
166
+
167
+ .thumb {
168
+ width: var(--thumb-size);
169
+ height: var(--thumb-size);
170
+ border-radius: var(--borderRadiusCircular);
171
+ box-shadow: 0 0 0 calc(var(--thumb-size) * 0.2) var(--colorNeutralBackground1) inset;
172
+ border: calc(var(--thumb-size) * 0.05) solid var(--colorNeutralStroke1);
173
+ box-sizing: border-box;
174
+ }
175
+
176
+ .thumb,
177
+ .track::before {
178
+ background-color: var(--rail-color);
179
+ }
180
+
181
+ @media (forced-colors: active) {
182
+ .track:hover,
183
+ .track:active,
184
+ .track {
185
+ background: WindowText;
186
+ }
187
+
188
+ .thumb:hover,
189
+ .thumb:active,
190
+ .thumb {
191
+ background: ButtonText;
192
+ }
193
+
194
+ .root:hover .track::before,
195
+ .root:active .track::before,
196
+ .track::before {
197
+ background: Highlight;
198
+ }
199
+ }