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,438 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-slider.css', import.meta.url).href;
4
+
5
+ function limit(min, max, value) {
6
+ return Math.max(min, Math.min(max, value));
7
+ }
8
+
9
+ function convertPixelToPercent(pixelPos, minPosition, maxPosition, direction) {
10
+ let pct = limit(0, 1, (pixelPos - minPosition) / (maxPosition - minPosition));
11
+ if (direction === 'rtl') {
12
+ pct = 1 - pct;
13
+ }
14
+ return pct;
15
+ }
16
+
17
+ class FluentSlider extends FluentElement {
18
+ static stylesUrl = stylesUrl;
19
+ static formAssociated = true;
20
+
21
+ static template = `
22
+ <div class="root">
23
+ <div part="track-container" class="track" ref="track"></div>
24
+ <div part="thumb-container" class="thumb-container" ref="thumbContainer">
25
+ <slot name="thumb">
26
+ <div class="thumb"></div>
27
+ </slot>
28
+ </div>
29
+ </div>
30
+ `;
31
+
32
+ static get observedAttributes() {
33
+ return ['min', 'max', 'step', 'value', 'disabled', 'orientation', 'size', 'mode', 'name', 'autofocus'];
34
+ }
35
+
36
+ constructor() {
37
+ super();
38
+ this._internals = this.attachInternals();
39
+ this._internals.role = 'slider';
40
+ this._internals.ariaOrientation = 'horizontal';
41
+ this._value = '';
42
+ this._isDragging = false;
43
+ this._stepMultiplier = 1;
44
+ this._direction = 'ltr';
45
+ this._trackWidth = 0;
46
+ this._trackMinWidth = 0;
47
+ this._trackHeight = 0;
48
+ this._trackMinHeight = 0;
49
+ this._trackLeft = 0;
50
+ this._boundPointerDown = this._handlePointerDown.bind(this);
51
+ this._boundPointerMove = this._handlePointerMove.bind(this);
52
+ this._boundPointerUp = this._handleWindowPointerUp.bind(this);
53
+ this._boundKeydown = this._handleKeydown.bind(this);
54
+ }
55
+
56
+ connectedCallback() {
57
+ super.connectedCallback();
58
+ this._shadowRoot = this._root;
59
+ requestAnimationFrame(() => {
60
+ if (!this.isConnected) return;
61
+ this._direction = getComputedStyle(this).direction;
62
+ this._updateDisabled();
63
+ this._updateStepMultiplier();
64
+ this._setupTrackConstraints();
65
+ this._setupDefaultValue();
66
+ this._setSliderPosition();
67
+ this._handleStepStyles();
68
+ this.addEventListener('pointerdown', this._boundPointerDown);
69
+ this.addEventListener('keydown', this._boundKeydown);
70
+ });
71
+ }
72
+
73
+ changed(name, oldVal, newVal) {
74
+ switch (name) {
75
+ case 'min':
76
+ this._internals.ariaValueMin = this._minAsNumber + '';
77
+ if (this.isConnected && this._minAsNumber > this._valueAsNumber) {
78
+ this.value = this.getAttribute('min');
79
+ }
80
+ this._setSliderPosition();
81
+ break;
82
+ case 'max':
83
+ this._internals.ariaValueMax = this._maxAsNumber + '';
84
+ if (this.isConnected && this._maxAsNumber < this._valueAsNumber) {
85
+ this.value = this.getAttribute('max');
86
+ }
87
+ this._setSliderPosition();
88
+ break;
89
+ case 'step':
90
+ this._updateStepMultiplier();
91
+ if (this.isConnected) {
92
+ this.value = this._value;
93
+ }
94
+ this._handleStepStyles();
95
+ break;
96
+ case 'value':
97
+ if (!this._dirtyValue) {
98
+ this._value = newVal || '';
99
+ this._setupDefaultValue();
100
+ this._setSliderPosition();
101
+ }
102
+ break;
103
+ case 'disabled':
104
+ this._updateDisabled();
105
+ break;
106
+ case 'orientation':
107
+ this._internals.ariaOrientation = newVal || 'horizontal';
108
+ if (this.isConnected) this._setSliderPosition();
109
+ break;
110
+ case 'size':
111
+ case 'mode':
112
+ break;
113
+ case 'name':
114
+ break;
115
+ case 'autofocus':
116
+ if (newVal !== null && !this.disabled) {
117
+ this.focus();
118
+ }
119
+ break;
120
+ }
121
+ }
122
+
123
+ get value() {
124
+ return this._value || '';
125
+ }
126
+
127
+ set value(val) {
128
+ if (!this.isConnected) {
129
+ this._value = val;
130
+ return;
131
+ }
132
+ const nextAsNumber = parseFloat(val) || 0;
133
+ const constrained = this._convertToConstrainedValue(nextAsNumber);
134
+ const newVal = limit(this._minAsNumber, this._maxAsNumber, constrained).toString();
135
+
136
+ if (newVal !== val && !isNaN(parseFloat(val))) {
137
+ this.value = newVal;
138
+ return;
139
+ }
140
+
141
+ this._value = val;
142
+ this._internals.ariaValueNow = this._value;
143
+ this._setSliderPosition();
144
+ this._setFormValue(this._value);
145
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
146
+ }
147
+
148
+ get valueAsNumber() {
149
+ return parseFloat(this.value) || 0;
150
+ }
151
+
152
+ set valueAsNumber(next) {
153
+ this.value = next.toString();
154
+ }
155
+
156
+ get disabled() {
157
+ return this.hasAttribute('disabled');
158
+ }
159
+
160
+ get form() {
161
+ return this._internals.form;
162
+ }
163
+
164
+ get labels() {
165
+ return Object.freeze(Array.from(this._internals.labels));
166
+ }
167
+
168
+ get validity() {
169
+ return this._internals.validity;
170
+ }
171
+
172
+ get validationMessage() {
173
+ return this._internals.validationMessage;
174
+ }
175
+
176
+ get willValidate() {
177
+ return this._internals.willValidate;
178
+ }
179
+
180
+ get orientation() {
181
+ return this.getAttribute('orientation') || 'horizontal';
182
+ }
183
+
184
+ checkValidity() {
185
+ return this._internals.checkValidity();
186
+ }
187
+
188
+ reportValidity() {
189
+ return this._internals.reportValidity();
190
+ }
191
+
192
+ setCustomValidity(message) {
193
+ this._setValidity({ customError: !!message }, message);
194
+ }
195
+
196
+ increment() {
197
+ const newVal = this._direction === 'rtl' && this.orientation !== 'vertical'
198
+ ? this.valueAsNumber - this._stepAsNumber
199
+ : this.valueAsNumber + this._stepAsNumber;
200
+ this.value = Math.min(this._maxAsNumber, this._convertToConstrainedValue(newVal)).toString();
201
+ }
202
+
203
+ decrement() {
204
+ const newVal = this._direction === 'rtl' && this.orientation !== 'vertical'
205
+ ? this.valueAsNumber + this._stepAsNumber
206
+ : this.valueAsNumber - this._stepAsNumber;
207
+ this.value = Math.max(this._minAsNumber, this._convertToConstrainedValue(newVal)).toString();
208
+ }
209
+
210
+ _handleKeydown(e) {
211
+ if (this.disabled) return;
212
+ switch (e.key) {
213
+ case 'Home':
214
+ e.preventDefault();
215
+ this.value = this._direction !== 'rtl' && this.orientation !== 'vertical'
216
+ ? this._minAsNumber.toString()
217
+ : this._maxAsNumber.toString();
218
+ break;
219
+ case 'End':
220
+ e.preventDefault();
221
+ this.value = this._direction !== 'rtl' && this.orientation !== 'vertical'
222
+ ? this._maxAsNumber.toString()
223
+ : this._minAsNumber.toString();
224
+ break;
225
+ case 'ArrowRight':
226
+ case 'ArrowUp':
227
+ if (!e.shiftKey) {
228
+ e.preventDefault();
229
+ this.increment();
230
+ }
231
+ break;
232
+ case 'ArrowLeft':
233
+ case 'ArrowDown':
234
+ if (!e.shiftKey) {
235
+ e.preventDefault();
236
+ this.decrement();
237
+ }
238
+ break;
239
+ }
240
+ }
241
+
242
+ _handlePointerDown(event) {
243
+ if (event === null || this.disabled) return;
244
+
245
+ const windowFn = event !== null ? window.addEventListener : window.removeEventListener;
246
+ windowFn('pointerup', this._boundPointerUp);
247
+ document.addEventListener('mouseleave', this._boundPointerUp);
248
+ windowFn('pointermove', this._boundPointerMove);
249
+
250
+ this._setupTrackConstraints();
251
+ const track = this._shadowRoot.querySelector('.track');
252
+ if (!track) return;
253
+
254
+ const thumbEl = this._shadowRoot.querySelector('.thumb') || this._shadowRoot.querySelector('.thumb-container');
255
+ const thumbWidth = thumbEl ? thumbEl.getBoundingClientRect().width : 0;
256
+
257
+ const controlValue = this.orientation === 'vertical'
258
+ ? event.pageY - document.documentElement.scrollTop
259
+ : event.pageX - document.documentElement.scrollLeft - this._trackLeft - thumbWidth / 2;
260
+
261
+ this.value = this._calculateNewValue(controlValue).toString();
262
+ this._isDragging = true;
263
+ }
264
+
265
+ _handlePointerMove(event) {
266
+ if (this.disabled || event.defaultPrevented) return;
267
+ const sourceEvent = window.TouchEvent && event instanceof TouchEvent ? event.touches[0] : event;
268
+
269
+ const track = this._shadowRoot.querySelector('.track');
270
+ const thumbEl = this._shadowRoot.querySelector('.thumb') || this._shadowRoot.querySelector('.thumb-container');
271
+ if (!track || !thumbEl) return;
272
+
273
+ const thumbWidth = thumbEl.getBoundingClientRect().width;
274
+
275
+ const eventValue = this.orientation === 'vertical'
276
+ ? sourceEvent.pageY - document.documentElement.scrollTop
277
+ : sourceEvent.pageX - document.documentElement.scrollLeft - this._trackLeft - thumbWidth / 2;
278
+
279
+ this.value = this._calculateNewValue(eventValue).toString();
280
+ }
281
+
282
+ _handleWindowPointerUp() {
283
+ this._isDragging = false;
284
+ window.removeEventListener('pointerup', this._boundPointerUp);
285
+ window.removeEventListener('pointermove', this._boundPointerMove);
286
+ document.removeEventListener('mouseleave', this._boundPointerUp);
287
+ }
288
+
289
+ _calculateNewValue(rawValue) {
290
+ this._setupTrackConstraints();
291
+ const newPosition = convertPixelToPercent(
292
+ rawValue,
293
+ this.orientation === 'vertical' ? this._trackMinHeight : this._trackMinWidth,
294
+ this.orientation === 'vertical' ? this._trackHeight : this._trackWidth,
295
+ this.orientation === 'vertical' ? undefined : this._direction
296
+ );
297
+ const newValue = (this._maxAsNumber - this._minAsNumber) * newPosition + this._minAsNumber;
298
+ return this._convertToConstrainedValue(newValue);
299
+ }
300
+
301
+ _convertToConstrainedValue(value) {
302
+ if (isNaN(value)) value = this._minAsNumber;
303
+ let constrained = value - this._minAsNumber;
304
+ const rounded = Math.round(constrained / this._stepAsNumber);
305
+ const remainder = constrained - (rounded * (this._stepMultiplier * this._stepAsNumber)) / this._stepMultiplier;
306
+ constrained = remainder >= this._stepAsNumber / 2
307
+ ? constrained - remainder + this._stepAsNumber
308
+ : constrained - remainder;
309
+ return constrained + this._minAsNumber;
310
+ }
311
+
312
+ _setSliderPosition() {
313
+ const pct = convertPixelToPercent(
314
+ parseFloat(this._value) || 0,
315
+ this._minAsNumber,
316
+ this._maxAsNumber,
317
+ this.orientation === 'vertical' ? undefined : this._direction
318
+ );
319
+ const percentage = pct * 100;
320
+ const root = this._root.querySelector('.root');
321
+ if (root) {
322
+ root.style.setProperty('--slider-thumb', percentage + '%');
323
+ root.style.setProperty('--slider-progress', percentage + '%');
324
+ }
325
+ this.style.setProperty('--slider-thumb', percentage + '%');
326
+ this.style.setProperty('--slider-progress', percentage + '%');
327
+ }
328
+
329
+ _setupTrackConstraints() {
330
+ const track = this._shadowRoot.querySelector('.track');
331
+ if (!track) return;
332
+ const clientRect = track.getBoundingClientRect();
333
+ this._trackWidth = track.clientWidth || 1;
334
+ this._trackMinWidth = track.clientLeft;
335
+ this._trackHeight = clientRect.top;
336
+ this._trackMinHeight = clientRect.bottom;
337
+ const root = this._root.querySelector('.root');
338
+ this._trackLeft = root ? root.getBoundingClientRect().left : clientRect.left;
339
+ }
340
+
341
+ _setupDefaultValue() {
342
+ if (!this._value) {
343
+ const initialVal = this.getAttribute('value');
344
+ this._value = initialVal || this._midpoint;
345
+ }
346
+ if (!isNaN(this._valueAsNumber) && (this._valueAsNumber < this._minAsNumber || this._valueAsNumber > this._maxAsNumber)) {
347
+ this._value = this._midpoint;
348
+ }
349
+ this._internals.ariaValueNow = this._value;
350
+ }
351
+
352
+ _updateStepMultiplier() {
353
+ const stepStr = this._stepAsNumber + '';
354
+ const decimalPlaces = !!(this._stepAsNumber % 1) ? stepStr.length - stepStr.indexOf('.') - 1 : 0;
355
+ this._stepMultiplier = Math.pow(10, decimalPlaces);
356
+ }
357
+
358
+ _updateDisabled() {
359
+ const d = this.disabled;
360
+ this._internals.ariaDisabled = d.toString();
361
+ this.tabIndex = d ? -1 : 0;
362
+ }
363
+
364
+ _handleStepStyles() {
365
+ const root = this._root.querySelector('.root');
366
+ if (this.hasAttribute('step')) {
367
+ const totalSteps = (100 / Math.floor((this._maxAsNumber - this._minAsNumber) / this._stepAsNumber));
368
+ this.style.setProperty('--step-rate', totalSteps + '%');
369
+ if (root) root.style.setProperty('--step-rate', totalSteps + '%');
370
+ } else {
371
+ this.style.removeProperty('--step-rate');
372
+ if (root) root.style.removeProperty('--step-rate');
373
+ }
374
+ }
375
+
376
+ _setFormValue(value) {
377
+ this._internals.setFormValue(value, value);
378
+ }
379
+
380
+ _setValidity(flags, message) {
381
+ if (this.isConnected) {
382
+ if (this.disabled) {
383
+ this._internals.setValidity({});
384
+ return;
385
+ }
386
+ const msg = message || this.validationMessage;
387
+ this._internals.setValidity(
388
+ { customError: !!msg, ...flags },
389
+ msg || undefined
390
+ );
391
+ }
392
+ }
393
+
394
+ formResetCallback() {
395
+ this.value = this.getAttribute('value') || this._midpoint;
396
+ }
397
+
398
+ formDisabledCallback(disabled) {
399
+ this._updateDisabled();
400
+ }
401
+
402
+ get _minAsNumber() {
403
+ const min = this.getAttribute('min');
404
+ if (min !== null && min !== undefined) {
405
+ const parsed = parseFloat(min);
406
+ if (!isNaN(parsed)) return parsed;
407
+ }
408
+ return 0;
409
+ }
410
+
411
+ get _maxAsNumber() {
412
+ const max = this.getAttribute('max');
413
+ if (max !== null && max !== undefined) {
414
+ const parsed = parseFloat(max);
415
+ if (!isNaN(parsed)) return parsed;
416
+ }
417
+ return 100;
418
+ }
419
+
420
+ get _stepAsNumber() {
421
+ const step = this.getAttribute('step');
422
+ if (step !== null && step !== undefined) {
423
+ const parsed = parseFloat(step);
424
+ if (!isNaN(parsed) && parsed > 0) return parsed;
425
+ }
426
+ return 1;
427
+ }
428
+
429
+ get _valueAsNumber() {
430
+ return parseFloat(this._value);
431
+ }
432
+
433
+ get _midpoint() {
434
+ return this._convertToConstrainedValue((this._maxAsNumber + this._minAsNumber) / 2).toString();
435
+ }
436
+ }
437
+
438
+ customElements.define('fluent-slider', FluentSlider);
@@ -0,0 +1,160 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: inline-flex; }
4
+
5
+ :host([hidden]) .root {
6
+ display: none;
7
+ }
8
+
9
+ .root {
10
+ position: relative;
11
+ display: inline-flex;
12
+ --duration: 1.5s;
13
+ --indicatorSize: var(--strokeWidthThicker);
14
+ --size: 32px;
15
+ height: var(--size);
16
+ width: var(--size);
17
+ }
18
+
19
+ :host([size='tiny']) .root {
20
+ --indicatorSize: var(--strokeWidthThick);
21
+ --size: 20px;
22
+ }
23
+ :host([size='extra-small']) .root {
24
+ --indicatorSize: var(--strokeWidthThick);
25
+ --size: 24px;
26
+ }
27
+ :host([size='small']) .root {
28
+ --indicatorSize: var(--strokeWidthThick);
29
+ --size: 28px;
30
+ }
31
+ :host([size='large']) .root {
32
+ --indicatorSize: var(--strokeWidthThicker);
33
+ --size: 36px;
34
+ }
35
+ :host([size='extra-large']) .root {
36
+ --indicatorSize: var(--strokeWidthThicker);
37
+ --size: 40px;
38
+ }
39
+ :host([size='huge']) .root {
40
+ --indicatorSize: var(--strokeWidthThickest);
41
+ --size: 44px;
42
+ }
43
+
44
+ .progress,
45
+ .background,
46
+ .spinner,
47
+ .start,
48
+ .end,
49
+ .indicator {
50
+ position: absolute;
51
+ inset: 0;
52
+ }
53
+
54
+ .progress,
55
+ .spinner,
56
+ .indicator {
57
+ animation: none var(--duration) infinite var(--curveEasyEase);
58
+ }
59
+
60
+ .progress {
61
+ animation-timing-function: linear;
62
+ animation-name: spin-linear;
63
+ }
64
+
65
+ .background {
66
+ border: var(--indicatorSize) solid var(--colorBrandStroke2);
67
+ border-radius: 50%;
68
+ }
69
+
70
+ :host([appearance='inverted']) .root .background {
71
+ border-color: rgba(255, 255, 255, 0.2);
72
+ }
73
+
74
+ .spinner {
75
+ animation-name: spin-swing;
76
+ }
77
+
78
+ .start {
79
+ overflow: hidden;
80
+ right: 50%;
81
+ }
82
+
83
+ .end {
84
+ overflow: hidden;
85
+ left: 50%;
86
+ }
87
+
88
+ .indicator {
89
+ color: var(--colorBrandStroke1);
90
+ box-sizing: border-box;
91
+ border-radius: 50%;
92
+ border: var(--indicatorSize) solid transparent;
93
+ border-block-start-color: currentcolor;
94
+ border-right-color: currentcolor;
95
+ }
96
+
97
+ :host([appearance='inverted']) .root .indicator {
98
+ color: var(--colorNeutralStrokeOnBrand2);
99
+ }
100
+
101
+ .start .indicator {
102
+ rotate: 135deg;
103
+ inset: 0 -100% 0 0;
104
+ animation-name: spin-start;
105
+ }
106
+
107
+ .end .indicator {
108
+ rotate: 135deg;
109
+ inset: 0 0 0 -100%;
110
+ animation-name: spin-end;
111
+ }
112
+
113
+ @keyframes spin-linear {
114
+ 100% {
115
+ transform: rotate(360deg);
116
+ }
117
+ }
118
+
119
+ @keyframes spin-swing {
120
+ 0% {
121
+ transform: rotate(-135deg);
122
+ }
123
+ 50% {
124
+ transform: rotate(0deg);
125
+ }
126
+ 100% {
127
+ transform: rotate(225deg);
128
+ }
129
+ }
130
+
131
+ @keyframes spin-start {
132
+ 0%,
133
+ 100% {
134
+ transform: rotate(0deg);
135
+ }
136
+ 50% {
137
+ transform: rotate(-80deg);
138
+ }
139
+ }
140
+
141
+ @keyframes spin-end {
142
+ 0%,
143
+ 100% {
144
+ transform: rotate(0deg);
145
+ }
146
+ 50% {
147
+ transform: rotate(70deg);
148
+ }
149
+ }
150
+
151
+ @media (forced-colors: active) {
152
+ .background {
153
+ display: none;
154
+ }
155
+ .indicator {
156
+ border-color: Canvas;
157
+ border-block-start-color: Highlight;
158
+ border-right-color: Highlight;
159
+ }
160
+ }
@@ -0,0 +1,30 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-spinner.css', import.meta.url).href;
4
+
5
+ class FluentSpinner extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static template = `
8
+ <div class="root">
9
+ <slot name="indicator">
10
+ <div class="background"></div>
11
+ <div class="progress">
12
+ <div class="spinner">
13
+ <div class="start">
14
+ <div class="indicator"></div>
15
+ </div>
16
+ <div class="end">
17
+ <div class="indicator"></div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </slot>
22
+ </div>
23
+ `;
24
+
25
+ static get observedAttributes() {
26
+ return ['size', 'appearance'];
27
+ }
28
+ }
29
+
30
+ customElements.define('fluent-spinner', FluentSpinner);