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,227 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: inline-flex; }
4
+
5
+ .label {
6
+ display: flex;
7
+ color: var(--colorNeutralForeground1);
8
+ padding-bottom: var(--spacingVerticalXS);
9
+ flex-shrink: 0;
10
+ padding-inline-end: var(--spacingHorizontalXS);
11
+ }
12
+
13
+ .label[hidden],
14
+ :host(:empty) .label {
15
+ display: none;
16
+ }
17
+
18
+ .root {
19
+ align-items: center;
20
+ background-color: var(--colorNeutralBackground1);
21
+ border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
22
+ border-bottom-color: var(--colorNeutralStrokeAccessible);
23
+ border-radius: var(--borderRadiusMedium);
24
+ box-sizing: border-box;
25
+ height: 32px;
26
+ display: inline-flex;
27
+ flex-direction: row;
28
+ font-family: var(--fontFamilyBase);
29
+ font-size: var(--fontSizeBase300);
30
+ font-weight: var(--fontWeightRegular);
31
+ gap: var(--spacingHorizontalXXS);
32
+ line-height: var(--lineHeightBase300);
33
+ max-width: 400px;
34
+ padding: 0 var(--spacingHorizontalMNudge);
35
+ position: relative;
36
+ width: 100%;
37
+ }
38
+
39
+ .root::after {
40
+ box-sizing: border-box;
41
+ content: '';
42
+ position: absolute;
43
+ left: -1px;
44
+ bottom: 0px;
45
+ right: -1px;
46
+ height: max(2px, var(--borderRadiusMedium));
47
+ border-radius: 0 0 var(--borderRadiusMedium) var(--borderRadiusMedium);
48
+ border-bottom: 2px solid var(--colorCompoundBrandStroke);
49
+ clip-path: inset(calc(100% - 2px) 1px 0px);
50
+ transform: scaleX(0);
51
+ transition-property: transform;
52
+ transition-duration: var(--durationUltraFast);
53
+ transition-delay: var(--curveAccelerateMid);
54
+ }
55
+
56
+ .control {
57
+ width: 100%;
58
+ height: 100%;
59
+ box-sizing: border-box;
60
+ color: var(--colorNeutralForeground1);
61
+ border-radius: var(--borderRadiusMedium);
62
+ background: var(--colorTransparentBackground);
63
+ font-family: var(--fontFamilyBase);
64
+ font-weight: var(--fontWeightRegular);
65
+ font-size: var(--fontSizeBase300);
66
+ border: none;
67
+ vertical-align: center;
68
+ }
69
+
70
+ .control:focus-visible {
71
+ outline: 0;
72
+ border: 0;
73
+ }
74
+
75
+ .control::placeholder {
76
+ color: var(--colorNeutralForeground4);
77
+ }
78
+
79
+ :host ::slotted([slot='start']),
80
+ :host ::slotted([slot='end']) {
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ color: var(--colorNeutralForeground3);
85
+ font-size: var(--fontSizeBase500);
86
+ }
87
+
88
+ :host ::slotted([slot='start']) {
89
+ padding-right: var(--spacingHorizontalXXS);
90
+ }
91
+
92
+ :host ::slotted([slot='end']) {
93
+ padding-left: var(--spacingHorizontalXXS);
94
+ gap: var(--spacingHorizontalXS);
95
+ }
96
+
97
+ .root:hover {
98
+ border-color: var(--colorNeutralStroke1Hover);
99
+ border-bottom-color: var(--colorNeutralStrokeAccessibleHover);
100
+ }
101
+
102
+ .root:active {
103
+ border-color: var(--colorNeutralStroke1Pressed);
104
+ }
105
+
106
+ .root:focus-within {
107
+ outline: transparent solid 2px;
108
+ border-bottom: 0;
109
+ }
110
+
111
+ .root:focus-within::after {
112
+ transform: scaleX(1);
113
+ transition-property: transform;
114
+ transition-duration: var(--durationNormal);
115
+ transition-delay: var(--curveDecelerateMid);
116
+ }
117
+
118
+ .root:focus-within:active::after {
119
+ border-bottom-color: var(--colorCompoundBrandStrokePressed);
120
+ }
121
+
122
+ .root:user-invalid {
123
+ border-color: var(--colorPaletteRedBorder2);
124
+ }
125
+
126
+ .root:focus-within .control {
127
+ color: var(--colorNeutralForeground1);
128
+ }
129
+
130
+ :host([disabled]) .root {
131
+ background: var(--colorTransparentBackground);
132
+ border: var(--strokeWidthThin) solid var(--colorNeutralStrokeDisabled);
133
+ }
134
+
135
+ :host([disabled]) .control::placeholder,
136
+ :host([disabled]) ::slotted([slot='start']),
137
+ :host([disabled]) ::slotted([slot='end']) {
138
+ color: var(--colorNeutralForegroundDisabled);
139
+ }
140
+
141
+ ::selection {
142
+ color: var(--colorNeutralForegroundInverted);
143
+ background-color: var(--colorNeutralBackgroundInverted);
144
+ }
145
+
146
+ :host([control-size='small']) .control {
147
+ font-size: var(--fontSizeBase200);
148
+ font-weight: var(--fontWeightRegular);
149
+ line-height: var(--lineHeightBase200);
150
+ }
151
+
152
+ :host([control-size='small']) .root {
153
+ height: 24px;
154
+ gap: var(--spacingHorizontalXXS);
155
+ padding: 0 var(--spacingHorizontalSNudge);
156
+ }
157
+
158
+ :host([control-size='small']) ::slotted([slot='start']),
159
+ :host([control-size='small']) ::slotted([slot='end']) {
160
+ font-size: var(--fontSizeBase400);
161
+ }
162
+
163
+ :host([control-size='large']) .control {
164
+ font-size: var(--fontSizeBase400);
165
+ font-weight: var(--fontWeightRegular);
166
+ line-height: var(--lineHeightBase400);
167
+ }
168
+
169
+ :host([control-size='large']) .root {
170
+ height: 40px;
171
+ gap: var(--spacingHorizontalS);
172
+ padding: 0 var(--spacingHorizontalM);
173
+ }
174
+
175
+ :host([control-size='large']) ::slotted([slot='start']),
176
+ :host([control-size='large']) ::slotted([slot='end']) {
177
+ font-size: var(--fontSizeBase600);
178
+ }
179
+
180
+ :host([appearance='underline']) .root {
181
+ background: var(--colorTransparentBackground);
182
+ border: 0;
183
+ border-radius: 0;
184
+ border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStrokeAccessible);
185
+ }
186
+
187
+ :host([appearance='underline']) .root:hover {
188
+ border-bottom-color: var(--colorNeutralStrokeAccessibleHover);
189
+ }
190
+
191
+ :host([appearance='underline']) .root:active {
192
+ border-bottom-color: var(--colorNeutralStrokeAccessiblePressed);
193
+ }
194
+
195
+ :host([appearance='underline']) .root:focus-within {
196
+ border: 0;
197
+ border-bottom-color: var(--colorNeutralStrokeAccessiblePressed);
198
+ }
199
+
200
+ :host([appearance='underline'][disabled]) .root {
201
+ border-bottom-color: var(--colorNeutralStrokeDisabled);
202
+ }
203
+
204
+ :host([appearance='filled-lighter']) .root,
205
+ :host([appearance='filled-darker']) .root {
206
+ border: var(--strokeWidthThin) solid var(--colorTransparentStroke);
207
+ box-shadow: var(--shadow2);
208
+ }
209
+
210
+ :host([appearance='filled-lighter']) .root {
211
+ background: var(--colorNeutralBackground1);
212
+ }
213
+
214
+ :host([appearance='filled-darker']) .root {
215
+ background: var(--colorNeutralBackground3);
216
+ }
217
+
218
+ :host([appearance='filled-lighter']) .root:hover,
219
+ :host([appearance='filled-darker']) .root:hover {
220
+ border-color: var(--colorTransparentStrokeInteractive);
221
+ }
222
+
223
+ :host([appearance='filled-lighter']) .root:active,
224
+ :host([appearance='filled-darker']) .root:active {
225
+ border-color: var(--colorTransparentStrokeInteractive);
226
+ background: var(--colorNeutralBackground3);
227
+ }
@@ -0,0 +1,298 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-text-input.css', import.meta.url).href;
4
+
5
+ class FluentTextInput extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static formAssociated = true;
8
+
9
+ static template = `
10
+ <label part="label" for="control" class="label">
11
+ <slot></slot>
12
+ </label>
13
+ <div class="root" part="root">
14
+ <slot name="start"></slot>
15
+ <input
16
+ class="control"
17
+ part="control"
18
+ id="control"
19
+ />
20
+ <slot name="end"></slot>
21
+ </div>
22
+ `;
23
+
24
+ static get observedAttributes() {
25
+ return [
26
+ 'disabled', 'readonly', 'placeholder', 'type', 'value',
27
+ 'appearance', 'control-size', 'autofocus', 'list', 'maxlength',
28
+ 'name', 'minlength', 'required', 'autocomplete', 'spellcheck',
29
+ 'pattern', 'multiple', 'size', 'dirname', 'form'
30
+ ];
31
+ }
32
+
33
+ constructor() {
34
+ super();
35
+ this._internals = this.attachInternals();
36
+ this._dirtyValue = false;
37
+ this._boundInputHandler = this._inputHandler.bind(this);
38
+ this._boundChangeHandler = this._changeHandler.bind(this);
39
+ this._boundFocusinHandler = this._focusinHandler.bind(this);
40
+ this._boundKeydownHandler = this._keydownHandler.bind(this);
41
+ }
42
+
43
+ connectedCallback() {
44
+ super.connectedCallback();
45
+ this._shadowRoot = this._root;
46
+
47
+ const t = this.getAttribute('tabindex');
48
+ this.tabIndex = Number(t ?? 0) < 0 ? -1 : 0;
49
+
50
+ const input = this._shadowRoot.querySelector('.control');
51
+ if (input) {
52
+ this._input = input;
53
+ this._syncAllAttrs();
54
+ this._setFormValue(this.value);
55
+ this._setValidity();
56
+
57
+ input.addEventListener('input', this._boundInputHandler);
58
+ input.addEventListener('change', this._boundChangeHandler);
59
+ input.addEventListener('select', () => this.dispatchEvent(new Event('select')));
60
+ }
61
+
62
+ this.addEventListener('focusin', this._boundFocusinHandler);
63
+ this.addEventListener('keydown', this._boundKeydownHandler);
64
+ this._updateLabelVisibility();
65
+ }
66
+
67
+ changed(name, oldVal, newVal) {
68
+ const input = this._input;
69
+ if (!input) return;
70
+
71
+ switch (name) {
72
+ case 'value':
73
+ if (!this._dirtyValue) {
74
+ input.value = newVal || '';
75
+ }
76
+ break;
77
+ case 'disabled':
78
+ input.disabled = newVal !== null;
79
+ break;
80
+ case 'readonly':
81
+ input.readOnly = newVal !== null;
82
+ this._internals.ariaReadOnly = newVal !== null ? 'true' : 'false';
83
+ break;
84
+ case 'placeholder':
85
+ input.placeholder = newVal || '';
86
+ break;
87
+ case 'type':
88
+ input.type = newVal || 'text';
89
+ break;
90
+ case 'autofocus':
91
+ if (newVal !== null) input.autofocus = true;
92
+ break;
93
+ case 'list':
94
+ input.setAttribute('list', newVal || '');
95
+ break;
96
+ case 'maxlength':
97
+ input.maxLength = newVal ? parseInt(newVal) : -1;
98
+ break;
99
+ case 'minlength':
100
+ input.minLength = newVal ? parseInt(newVal) : -1;
101
+ break;
102
+ case 'name':
103
+ input.name = newVal || '';
104
+ break;
105
+ case 'required':
106
+ input.required = newVal !== null;
107
+ this._internals.ariaRequired = newVal !== null ? 'true' : 'false';
108
+ this._setValidity();
109
+ break;
110
+ case 'autocomplete':
111
+ input.autocomplete = newVal || '';
112
+ break;
113
+ case 'spellcheck':
114
+ input.spellcheck = newVal !== null;
115
+ break;
116
+ case 'pattern':
117
+ input.pattern = newVal || '';
118
+ break;
119
+ case 'multiple':
120
+ input.multiple = newVal !== null;
121
+ break;
122
+ case 'size':
123
+ input.size = newVal ? parseInt(newVal) : 20;
124
+ break;
125
+ case 'dirname':
126
+ input.setAttribute('dirname', newVal || '');
127
+ break;
128
+ case 'appearance':
129
+ case 'control-size':
130
+ break;
131
+ }
132
+ }
133
+
134
+ get value() {
135
+ return this._input ? this._input.value : (this.getAttribute('value') || '');
136
+ }
137
+
138
+ set value(val) {
139
+ if (this._input) {
140
+ this._input.value = val;
141
+ this._setFormValue(val);
142
+ this._setValidity();
143
+ }
144
+ }
145
+
146
+ get disabled() {
147
+ return this.hasAttribute('disabled');
148
+ }
149
+
150
+ get form() {
151
+ return this._internals.form;
152
+ }
153
+
154
+ get validity() {
155
+ return this._internals.validity;
156
+ }
157
+
158
+ get validationMessage() {
159
+ return this._internals.validationMessage || (this._input ? this._input.validationMessage : '');
160
+ }
161
+
162
+ get willValidate() {
163
+ return this._internals.willValidate;
164
+ }
165
+
166
+ get labels() {
167
+ return Object.freeze(Array.from(this._internals.labels));
168
+ }
169
+
170
+ get type() {
171
+ return this._input ? this._input.type : (this.getAttribute('type') || 'text');
172
+ }
173
+
174
+ get textLength() {
175
+ return this._input ? this._input.textLength : 0;
176
+ }
177
+
178
+ checkValidity() {
179
+ return this._internals.checkValidity();
180
+ }
181
+
182
+ reportValidity() {
183
+ return this._internals.reportValidity();
184
+ }
185
+
186
+ setCustomValidity(message) {
187
+ this._internals.setValidity({ customError: !!message }, message);
188
+ this.reportValidity();
189
+ }
190
+
191
+ select() {
192
+ if (this._input) {
193
+ this._input.select();
194
+ this.dispatchEvent(new Event('select'));
195
+ }
196
+ }
197
+
198
+ _syncAllAttrs() {
199
+ const input = this._input;
200
+ if (!input) return;
201
+ input.disabled = this.hasAttribute('disabled');
202
+ input.readOnly = this.hasAttribute('readonly');
203
+ input.placeholder = this.getAttribute('placeholder') || '';
204
+ input.type = this.getAttribute('type') || 'text';
205
+ input.name = this.getAttribute('name') || '';
206
+ input.required = this.hasAttribute('required');
207
+ input.autocomplete = this.getAttribute('autocomplete') || '';
208
+ input.spellcheck = this.hasAttribute('spellcheck');
209
+ input.multiple = this.hasAttribute('multiple');
210
+ input.pattern = this.getAttribute('pattern') || '';
211
+ input.value = this.getAttribute('value') || '';
212
+ const ml = this.getAttribute('maxlength');
213
+ if (ml) input.maxLength = parseInt(ml);
214
+ const minl = this.getAttribute('minlength');
215
+ if (minl) input.minLength = parseInt(minl);
216
+ const sz = this.getAttribute('size');
217
+ if (sz) input.size = parseInt(sz);
218
+ const list = this.getAttribute('list');
219
+ if (list) input.setAttribute('list', list);
220
+ const dirname = this.getAttribute('dirname');
221
+ if (dirname) input.setAttribute('dirname', dirname);
222
+ if (this.hasAttribute('autofocus')) input.autofocus = true;
223
+ this._internals.ariaReadOnly = this.hasAttribute('readonly') ? 'true' : 'false';
224
+ this._internals.ariaRequired = this.hasAttribute('required') ? 'true' : 'false';
225
+ }
226
+
227
+ _inputHandler() {
228
+ this._dirtyValue = true;
229
+ this._setFormValue(this._input.value);
230
+ this._setValidity();
231
+ }
232
+
233
+ _changeHandler(e) {
234
+ this._setValidity();
235
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
236
+ }
237
+
238
+ _focusinHandler(e) {
239
+ if (e.target === this && this._input) {
240
+ this._input.focus();
241
+ }
242
+ }
243
+
244
+ _keydownHandler(e) {
245
+ if (e.key === 'Enter' && this._internals.form) {
246
+ const form = this._internals.form;
247
+ if (form.elements.length === 1) {
248
+ form.requestSubmit();
249
+ }
250
+ }
251
+ }
252
+
253
+ _setFormValue(value) {
254
+ this._internals.setFormValue(value, value);
255
+ }
256
+
257
+ _setValidity() {
258
+ if (!this.isConnected || !this._input) return;
259
+ if (this.disabled) {
260
+ this._internals.setValidity({});
261
+ return;
262
+ }
263
+ const { valid, valueMissing, typeMismatch, patternMismatch, tooLong, tooShort, rangeUnderflow, rangeOverflow, stepMismatch, badInput } = this._input.validity;
264
+ const hasError = !valid;
265
+ if (!hasError) {
266
+ this._internals.setValidity({});
267
+ return;
268
+ }
269
+ const msg = this._input.validationMessage || 'Invalid value';
270
+ this._internals.setValidity(
271
+ { valueMissing, typeMismatch, patternMismatch, tooLong, tooShort, rangeUnderflow, rangeOverflow, stepMismatch, badInput },
272
+ msg,
273
+ this._input
274
+ );
275
+ }
276
+
277
+ _updateLabelVisibility() {
278
+ const label = this._shadowRoot.querySelector('.label');
279
+ if (!label) return;
280
+ const slot = this._root.querySelector('slot:not([name])');
281
+ if (!slot) return;
282
+ const nodes = slot.assignedNodes();
283
+ const hasContent = nodes.some(n =>
284
+ n.nodeType === Node.ELEMENT_NODE ||
285
+ (n.nodeType === Node.TEXT_NODE && n.textContent.trim())
286
+ );
287
+ label.hidden = !hasContent;
288
+ }
289
+
290
+ formResetCallback() {
291
+ this._dirtyValue = false;
292
+ if (this._input) {
293
+ this._input.value = this.getAttribute('value') || '';
294
+ }
295
+ }
296
+ }
297
+
298
+ customElements.define('fluent-text-input', FluentTextInput);