bareframe 0.1.0 → 0.1.2

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 (154) hide show
  1. package/README.md +179 -0
  2. package/dist/bareframe.min.js +119 -0
  3. package/dist/components/accordion.js +66 -0
  4. package/dist/components/autocomplete.css +78 -15
  5. package/dist/components/autocomplete.js +220 -10
  6. package/dist/components/avatar.css +129 -17
  7. package/dist/components/avatar.js +47 -10
  8. package/dist/components/breadcrumb.css +63 -17
  9. package/dist/components/breadcrumb.js +140 -5
  10. package/dist/components/button.css +4 -0
  11. package/dist/components/button.js +95 -15
  12. package/dist/components/chart.css +163 -14
  13. package/dist/components/chart.js +59 -4
  14. package/dist/components/checkbox.css +43 -1
  15. package/dist/components/checkbox.js +98 -5
  16. package/dist/components/dialog.css +95 -0
  17. package/dist/components/dialog.js +172 -4
  18. package/dist/components/divider.css +18 -22
  19. package/dist/components/divider.js +31 -3
  20. package/dist/components/drawer.css +68 -18
  21. package/dist/components/drawer.js +84 -4
  22. package/dist/components/edge.css +54 -0
  23. package/dist/components/edge.js +55 -0
  24. package/dist/components/file-upload.css +72 -3
  25. package/dist/components/file-upload.js +186 -4
  26. package/dist/components/input.css +59 -0
  27. package/dist/components/input.js +369 -4
  28. package/dist/components/list.css +11 -0
  29. package/dist/components/list.js +45 -0
  30. package/dist/components/menu.css +20 -0
  31. package/dist/components/menu.js +144 -0
  32. package/dist/components/modal.css +30 -17
  33. package/dist/components/modal.js +68 -4
  34. package/dist/components/nav.css +39 -0
  35. package/dist/components/progress.css +196 -0
  36. package/dist/components/progress.js +304 -0
  37. package/dist/components/radio.css +35 -1
  38. package/dist/components/radio.js +86 -5
  39. package/dist/components/range.css +91 -0
  40. package/dist/components/range.js +250 -0
  41. package/dist/components/select.css +35 -1
  42. package/dist/components/select.js +255 -4
  43. package/dist/components/skeleton.css +108 -21
  44. package/dist/components/skeleton.js +57 -4
  45. package/dist/components/tab.css +9 -1
  46. package/dist/components/tab.js +66 -1
  47. package/dist/components/tag.css +36 -3
  48. package/dist/components/tag.js +32 -0
  49. package/dist/components/toast.css +113 -0
  50. package/dist/components/toast.js +265 -4
  51. package/dist/components/toggle.css +53 -0
  52. package/dist/components/toggle.js +73 -5
  53. package/dist/components/wizard.css +79 -14
  54. package/dist/components/wizard.js +141 -4
  55. package/dist/index.js +5147 -110
  56. package/dist/manifest.json +5 -42
  57. package/dist/themes/aurora.css +47 -0
  58. package/dist/themes/dark.css +12 -2
  59. package/dist/themes/desert.css +37 -0
  60. package/dist/themes/future.css +47 -0
  61. package/dist/themes/layout.css +191 -0
  62. package/dist/themes/light.css +12 -0
  63. package/dist/themes/matrix.css +37 -0
  64. package/dist/themes/modern.css +64 -0
  65. package/dist/themes/nature.css +47 -0
  66. package/dist/themes/nebula.css +37 -0
  67. package/dist/themes/noir.css +37 -0
  68. package/dist/themes/oceanic.css +37 -0
  69. package/dist/themes/retro.css +47 -0
  70. package/dist/themes/simple.css +47 -0
  71. package/dist/themes/sprint.css +12 -0
  72. package/dist/themes/sunrise.css +37 -0
  73. package/dist/themes/system.css +13 -0
  74. package/package.json +9 -2
  75. package/dist/components/alert.css +0 -30
  76. package/dist/components/alert.js +0 -31
  77. package/dist/components/badge.css +0 -30
  78. package/dist/components/badge.js +0 -31
  79. package/dist/components/banner.css +0 -30
  80. package/dist/components/banner.js +0 -31
  81. package/dist/components/bar-chart.css +0 -30
  82. package/dist/components/bar-chart.js +0 -31
  83. package/dist/components/bottom-sheet.css +0 -30
  84. package/dist/components/bottom-sheet.js +0 -31
  85. package/dist/components/button-group.css +0 -30
  86. package/dist/components/button-group.js +0 -31
  87. package/dist/components/chip.css +0 -30
  88. package/dist/components/chip.js +0 -31
  89. package/dist/components/color-picker.css +0 -30
  90. package/dist/components/color-picker.js +0 -31
  91. package/dist/components/context-menu.css +0 -30
  92. package/dist/components/context-menu.js +0 -31
  93. package/dist/components/donut-chart.css +0 -30
  94. package/dist/components/donut-chart.js +0 -31
  95. package/dist/components/expanded-panel.css +0 -30
  96. package/dist/components/expanded-panel.js +0 -31
  97. package/dist/components/footer.css +0 -30
  98. package/dist/components/footer.js +0 -31
  99. package/dist/components/gantt-chart.css +0 -30
  100. package/dist/components/gantt-chart.js +0 -31
  101. package/dist/components/gauge.css +0 -30
  102. package/dist/components/gauge.js +0 -31
  103. package/dist/components/graph.css +0 -30
  104. package/dist/components/graph.js +0 -31
  105. package/dist/components/header.css +0 -30
  106. package/dist/components/header.js +0 -31
  107. package/dist/components/heatmap.css +0 -30
  108. package/dist/components/heatmap.js +0 -31
  109. package/dist/components/line-chart.css +0 -30
  110. package/dist/components/line-chart.js +0 -31
  111. package/dist/components/list-item.css +0 -30
  112. package/dist/components/list-item.js +0 -31
  113. package/dist/components/menu-item.css +0 -30
  114. package/dist/components/menu-item.js +0 -31
  115. package/dist/components/multi-select.css +0 -30
  116. package/dist/components/multi-select.js +0 -31
  117. package/dist/components/notification.css +0 -30
  118. package/dist/components/notification.js +0 -31
  119. package/dist/components/pie-chart.css +0 -30
  120. package/dist/components/pie-chart.js +0 -31
  121. package/dist/components/popover.css +0 -30
  122. package/dist/components/popover.js +0 -31
  123. package/dist/components/progress-bar.css +0 -30
  124. package/dist/components/progress-bar.js +0 -31
  125. package/dist/components/progress-circle.css +0 -30
  126. package/dist/components/progress-circle.js +0 -31
  127. package/dist/components/radio-group.css +0 -30
  128. package/dist/components/radio-group.js +0 -31
  129. package/dist/components/range-slider.css +0 -30
  130. package/dist/components/range-slider.js +0 -31
  131. package/dist/components/rating.css +0 -30
  132. package/dist/components/rating.js +0 -31
  133. package/dist/components/sheet.css +0 -30
  134. package/dist/components/sheet.js +0 -31
  135. package/dist/components/slider.css +0 -30
  136. package/dist/components/slider.js +0 -31
  137. package/dist/components/snackbar.css +0 -30
  138. package/dist/components/snackbar.js +0 -31
  139. package/dist/components/sparkline.css +0 -30
  140. package/dist/components/sparkline.js +0 -31
  141. package/dist/components/stepper.css +0 -30
  142. package/dist/components/stepper.js +0 -31
  143. package/dist/components/switch.css +0 -30
  144. package/dist/components/switch.js +0 -31
  145. package/dist/components/tab-group.css +0 -30
  146. package/dist/components/tab-group.js +0 -31
  147. package/dist/components/textfield.css +0 -30
  148. package/dist/components/textfield.js +0 -31
  149. package/dist/components/tooltip.css +0 -30
  150. package/dist/components/tooltip.js +0 -31
  151. package/dist/components/treemap.css +0 -30
  152. package/dist/components/treemap.js +0 -31
  153. package/dist/components/upload-dropzone.css +0 -30
  154. package/dist/components/upload-dropzone.js +0 -31
@@ -1,4 +1,6 @@
1
1
  class BfTag extends HTMLElement {
2
+ static observedAttributes = ['variant', 'badge', 'chip', 'pill', 'size'];
3
+
2
4
  constructor() {
3
5
  super();
4
6
  this.attachShadow({ mode: 'open' });
@@ -6,6 +8,7 @@ class BfTag extends HTMLElement {
6
8
 
7
9
  connectedCallback() {
8
10
  if (this._initialized) {
11
+ this._sync();
9
12
  return;
10
13
  }
11
14
  this._initialized = true;
@@ -25,6 +28,35 @@ class BfTag extends HTMLElement {
25
28
  }
26
29
 
27
30
  this.shadowRoot.replaceChildren(link, root);
31
+ this._root = root;
32
+ this._sync();
33
+ }
34
+
35
+ attributeChangedCallback() {
36
+ this._sync();
37
+ }
38
+
39
+ _sync() {
40
+ if (!this._root) {
41
+ return;
42
+ }
43
+
44
+ const explicit = (this.getAttribute('variant') || '').toLowerCase();
45
+ let variant = 'tag';
46
+ if (['tag', 'badge', 'chip', 'pill'].includes(explicit)) {
47
+ variant = explicit;
48
+ } else if (this.hasAttribute('badge')) {
49
+ variant = 'badge';
50
+ } else if (this.hasAttribute('chip')) {
51
+ variant = 'chip';
52
+ } else if (this.hasAttribute('pill')) {
53
+ variant = 'pill';
54
+ }
55
+
56
+ this._root.dataset.variant = variant;
57
+
58
+ const size = (this.getAttribute('size') || 'md').toLowerCase();
59
+ this._root.dataset.size = ['sm', 'md', 'lg'].includes(size) ? size : 'md';
28
60
  }
29
61
  }
30
62
 
@@ -13,12 +13,30 @@
13
13
  var(--bf-theme-transition-color, color 120ms ease),
14
14
  var(--bf-theme-transition-border, border-color 120ms ease);
15
15
 
16
+ --bf-toast-success-bg: var(--bf-theme-success-bg, #dcfce7);
17
+ --bf-toast-success-color: var(--bf-theme-success-color, #166534);
18
+ --bf-toast-warning-bg: var(--bf-theme-warning-bg, #fef3c7);
19
+ --bf-toast-warning-color: var(--bf-theme-warning-color, #92400e);
20
+ --bf-toast-error-bg: var(--bf-theme-error-bg, #fee2e2);
21
+ --bf-toast-error-color: var(--bf-theme-error-color, #991b1b);
22
+ --bf-toast-info-bg: var(--bf-theme-info-bg, var(--bf-toast-bg));
23
+ --bf-toast-info-color: var(--bf-theme-info-color, var(--bf-toast-color));
24
+ --bf-toast-snackbar-bg: var(--bf-theme-snackbar-bg, #111827);
25
+ --bf-toast-snackbar-color: var(--bf-theme-snackbar-color, #f8fafc);
26
+
16
27
  display: block;
17
28
  font: var(--bf-toast-font);
18
29
  color: var(--bf-toast-color);
30
+ z-index: 1000;
19
31
  }
20
32
 
21
33
  .root {
34
+ display: grid;
35
+ grid-template-columns: auto 1fr auto;
36
+ align-items: start;
37
+ gap: 0.5rem;
38
+ min-width: 16rem;
39
+ max-width: 30rem;
22
40
  background: var(--bf-toast-bg);
23
41
  color: var(--bf-toast-color);
24
42
  border-width: var(--bf-toast-border-width);
@@ -27,4 +45,99 @@
27
45
  border-radius: var(--bf-toast-radius);
28
46
  padding: var(--bf-toast-padding-y) var(--bf-toast-padding-x);
29
47
  transition: var(--bf-toast-transition);
48
+ box-shadow: 0 8px 28px rgba(15, 23, 42, 0.18);
49
+ }
50
+
51
+ .icon {
52
+ width: 1.1rem;
53
+ height: 1.1rem;
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ font-weight: 700;
58
+ line-height: 1;
59
+ margin-top: 0.1rem;
60
+ }
61
+
62
+ .content {
63
+ min-width: 0;
64
+ }
65
+
66
+ .close {
67
+ border: 0;
68
+ background: transparent;
69
+ color: currentColor;
70
+ font: inherit;
71
+ line-height: 1;
72
+ cursor: pointer;
73
+ padding: 0;
74
+ opacity: 0.75;
75
+ }
76
+
77
+ .close:hover {
78
+ opacity: 1;
79
+ }
80
+
81
+ .root[data-open='false'] {
82
+ display: none;
83
+ }
84
+
85
+ .root[data-variant='alert'] {
86
+ border-left-width: 4px;
87
+ }
88
+
89
+ .root[data-variant='notification'] {
90
+ border-radius: calc(var(--bf-toast-radius) + 4px);
91
+ }
92
+
93
+ .root[data-variant='snackbar'] {
94
+ background: var(--bf-toast-snackbar-bg);
95
+ color: var(--bf-toast-snackbar-color);
96
+ border-color: transparent;
97
+ border-radius: 999px;
98
+ padding-inline: 0.9rem;
99
+ min-width: 14rem;
100
+ }
101
+
102
+ .root[data-variant='snackbar'] .icon {
103
+ display: none;
104
+ }
105
+
106
+ .root[data-variant='banner'] {
107
+ width: min(100%, 64rem);
108
+ max-width: 64rem;
109
+ border-radius: 10px;
110
+ box-shadow: none;
111
+ }
112
+
113
+ .root[data-variant='banner'] .icon {
114
+ display: none;
115
+ }
116
+
117
+ .root[data-type='success'] {
118
+ background: var(--bf-toast-success-bg);
119
+ color: var(--bf-toast-success-color);
120
+ }
121
+
122
+ .root[data-type='warning'] {
123
+ background: var(--bf-toast-warning-bg);
124
+ color: var(--bf-toast-warning-color);
125
+ }
126
+
127
+ .root[data-type='error'] {
128
+ background: var(--bf-toast-error-bg);
129
+ color: var(--bf-toast-error-color);
130
+ }
131
+
132
+ .root[data-type='info'] {
133
+ background: var(--bf-toast-info-bg);
134
+ color: var(--bf-toast-info-color);
135
+ }
136
+
137
+ .root[data-variant='snackbar'][data-type='success'],
138
+ .root[data-variant='snackbar'][data-type='warning'],
139
+ .root[data-variant='snackbar'][data-type='error'],
140
+ .root[data-variant='snackbar'][data-type='info'] {
141
+ background: var(--bf-toast-snackbar-bg);
142
+ color: var(--bf-toast-snackbar-color);
30
143
  }
@@ -1,11 +1,34 @@
1
1
  class BfToast extends HTMLElement {
2
+ static observedAttributes = [
3
+ 'variant',
4
+ 'position',
5
+ 'type',
6
+ 'duration',
7
+ 'open',
8
+ 'alert',
9
+ 'notification',
10
+ 'snackbar',
11
+ 'banner',
12
+ 'top',
13
+ 'bottom',
14
+ 'left',
15
+ 'right',
16
+ 'center',
17
+ 'success',
18
+ 'warning',
19
+ 'error',
20
+ 'info',
21
+ ];
22
+
2
23
  constructor() {
3
24
  super();
4
25
  this.attachShadow({ mode: 'open' });
26
+ this._onCloseClick = this._onCloseClick.bind(this);
5
27
  }
6
28
 
7
29
  connectedCallback() {
8
30
  if (this._initialized) {
31
+ this._syncState();
9
32
  return;
10
33
  }
11
34
  this._initialized = true;
@@ -18,13 +41,251 @@ class BfToast extends HTMLElement {
18
41
  const root = document.createElement('div');
19
42
  root.className = 'root';
20
43
  root.setAttribute('part', 'root');
21
- root.innerHTML = '<slot></slot>';
44
+ root.innerHTML = `
45
+ <span class="icon" part="icon" aria-hidden="true"></span>
46
+ <div class="content" part="content"><slot></slot></div>
47
+ <button class="close" part="close" type="button" aria-label="Close">&times;</button>
48
+ `;
49
+
50
+ this.shadowRoot.replaceChildren(link, root);
51
+ this._root = root;
52
+ this._icon = root.querySelector('.icon');
53
+ this._close = root.querySelector('.close');
54
+ this._close.addEventListener('click', this._onCloseClick);
22
55
 
23
- if (!this.innerHTML.trim()) {
24
- root.textContent = 'toast';
56
+ if (!this.textContent?.trim()) {
57
+ this.textContent = 'toast';
25
58
  }
59
+ this._syncState();
60
+ }
26
61
 
27
- this.shadowRoot.replaceChildren(link, root);
62
+ disconnectedCallback() {
63
+ this._clearDurationTimer();
64
+ }
65
+
66
+ attributeChangedCallback() {
67
+ this._syncState();
68
+ }
69
+
70
+ get open() {
71
+ return this.hasAttribute('open');
72
+ }
73
+
74
+ set open(next) {
75
+ if (next) {
76
+ this.setAttribute('open', '');
77
+ return;
78
+ }
79
+ this.removeAttribute('open');
80
+ }
81
+
82
+ show() {
83
+ this.open = true;
84
+ }
85
+
86
+ hide() {
87
+ this.open = false;
88
+ }
89
+
90
+ _onCloseClick() {
91
+ this.hide();
92
+ this.dispatchEvent(
93
+ new CustomEvent('bf-close', {
94
+ bubbles: true,
95
+ composed: true,
96
+ detail: { id: this.id || '' },
97
+ }),
98
+ );
99
+ }
100
+
101
+ _variant() {
102
+ const explicit = (this.getAttribute('variant') || '').toLowerCase();
103
+ if (['toast', 'alert', 'notification', 'snackbar', 'banner'].includes(explicit)) {
104
+ return explicit;
105
+ }
106
+ if (this.hasAttribute('alert')) {
107
+ return 'alert';
108
+ }
109
+ if (this.hasAttribute('notification')) {
110
+ return 'notification';
111
+ }
112
+ if (this.hasAttribute('snackbar')) {
113
+ return 'snackbar';
114
+ }
115
+ if (this.hasAttribute('banner')) {
116
+ return 'banner';
117
+ }
118
+ return 'toast';
119
+ }
120
+
121
+ _type() {
122
+ const explicit = (this.getAttribute('type') || '').toLowerCase();
123
+ if (['success', 'warning', 'error', 'info'].includes(explicit)) {
124
+ return explicit;
125
+ }
126
+ if (this.hasAttribute('success')) {
127
+ return 'success';
128
+ }
129
+ if (this.hasAttribute('warning')) {
130
+ return 'warning';
131
+ }
132
+ if (this.hasAttribute('error')) {
133
+ return 'error';
134
+ }
135
+ return 'info';
136
+ }
137
+
138
+ _position() {
139
+ const variant = this._variant();
140
+ const explicit = (this.getAttribute('position') || '').toLowerCase().replace(/\s+/g, '-');
141
+ if (explicit && this._isValidPosition(explicit)) {
142
+ return explicit;
143
+ }
144
+ if (
145
+ variant === 'snackbar'
146
+ && !this.hasAttribute('top')
147
+ && !this.hasAttribute('bottom')
148
+ && !this.hasAttribute('left')
149
+ && !this.hasAttribute('right')
150
+ && !this.hasAttribute('center')
151
+ && !this.hasAttribute('position')
152
+ ) {
153
+ return 'bottom-center';
154
+ }
155
+ if (
156
+ variant === 'banner'
157
+ && !this.hasAttribute('top')
158
+ && !this.hasAttribute('bottom')
159
+ && !this.hasAttribute('left')
160
+ && !this.hasAttribute('right')
161
+ && !this.hasAttribute('center')
162
+ && !this.hasAttribute('position')
163
+ ) {
164
+ return 'top-center';
165
+ }
166
+ const y = this.hasAttribute('bottom') ? 'bottom' : 'top';
167
+ let x = 'right';
168
+ if (this.hasAttribute('left')) {
169
+ x = 'left';
170
+ }
171
+ if (this.hasAttribute('center')) {
172
+ x = 'center';
173
+ }
174
+ if (this.hasAttribute('right')) {
175
+ x = 'right';
176
+ }
177
+ return `${y}-${x}`;
178
+ }
179
+
180
+ _isValidPosition(position) {
181
+ return [
182
+ 'top-left',
183
+ 'top-center',
184
+ 'top-right',
185
+ 'bottom-left',
186
+ 'bottom-center',
187
+ 'bottom-right',
188
+ ].includes(position);
189
+ }
190
+
191
+ _duration() {
192
+ const parsed = Number.parseInt(this.getAttribute('duration') || '', 10);
193
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
194
+ }
195
+
196
+ _iconSymbol() {
197
+ const type = this._type();
198
+ if (type === 'success') {
199
+ return '✓';
200
+ }
201
+ if (type === 'warning') {
202
+ return '!';
203
+ }
204
+ if (type === 'error') {
205
+ return 'x';
206
+ }
207
+ return 'i';
208
+ }
209
+
210
+ _clearDurationTimer() {
211
+ if (!this._durationTimer) {
212
+ return;
213
+ }
214
+ clearTimeout(this._durationTimer);
215
+ this._durationTimer = null;
216
+ }
217
+
218
+ _scheduleAutoHide() {
219
+ this._clearDurationTimer();
220
+ const ms = this._duration();
221
+ if (!this.open || ms <= 0) {
222
+ return;
223
+ }
224
+ this._durationTimer = setTimeout(() => {
225
+ this.hide();
226
+ }, ms);
227
+ }
228
+
229
+ _syncState() {
230
+ if (!this._root) {
231
+ return;
232
+ }
233
+
234
+ const variant = this._variant();
235
+ const type = this._type();
236
+ const position = this._position();
237
+ if (!this.hasAttribute('open')) {
238
+ this.setAttribute('open', '');
239
+ }
240
+
241
+ this._root.setAttribute('data-variant', variant);
242
+ this._root.setAttribute('data-type', type);
243
+ this._root.setAttribute('data-position', position);
244
+ this._root.setAttribute('data-open', this.open ? 'true' : 'false');
245
+ this._icon.textContent = this._iconSymbol();
246
+ this._applyPosition(position);
247
+
248
+ this.setAttribute('role', variant === 'alert' ? 'alert' : 'status');
249
+ this.setAttribute('aria-live', variant === 'alert' ? 'assertive' : 'polite');
250
+
251
+ this._scheduleAutoHide();
252
+ }
253
+
254
+ _applyPosition(position) {
255
+ const anchored = this.hasAttribute('position')
256
+ || this.hasAttribute('top')
257
+ || this.hasAttribute('bottom')
258
+ || this.hasAttribute('left')
259
+ || this.hasAttribute('right')
260
+ || this.hasAttribute('center');
261
+ if (!anchored) {
262
+ this.style.position = '';
263
+ this.style.top = '';
264
+ this.style.bottom = '';
265
+ this.style.left = '';
266
+ this.style.right = '';
267
+ this.style.transform = '';
268
+ return;
269
+ }
270
+
271
+ this.style.position = 'fixed';
272
+ this.style.zIndex = '1000';
273
+ this.style.top = '';
274
+ this.style.bottom = '';
275
+ this.style.left = '';
276
+ this.style.right = '';
277
+ this.style.transform = '';
278
+
279
+ const [y, x] = position.split('-');
280
+ this.style[y] = '1rem';
281
+ if (x === 'left') {
282
+ this.style.left = '1rem';
283
+ } else if (x === 'right') {
284
+ this.style.right = '1rem';
285
+ } else {
286
+ this.style.left = '50%';
287
+ this.style.transform = 'translateX(-50%)';
288
+ }
28
289
  }
29
290
  }
30
291
 
@@ -19,6 +19,9 @@
19
19
  }
20
20
 
21
21
  .root {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: 0.55rem;
22
25
  background: var(--bf-toggle-bg);
23
26
  color: var(--bf-toggle-color);
24
27
  border-width: var(--bf-toggle-border-width);
@@ -27,4 +30,54 @@
27
30
  border-radius: var(--bf-toggle-radius);
28
31
  padding: var(--bf-toggle-padding-y) var(--bf-toggle-padding-x);
29
32
  transition: var(--bf-toggle-transition);
33
+ cursor: pointer;
34
+ }
35
+
36
+ .track {
37
+ display: none;
38
+ }
39
+
40
+ .root[data-variant='switch'] {
41
+ border-color: transparent;
42
+ background: transparent;
43
+ padding: 0;
44
+ }
45
+
46
+ .root[data-variant='switch'] .track {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ width: 2.2rem;
50
+ height: 1.3rem;
51
+ border-radius: 999px;
52
+ background: var(--bf-theme-surface-2, #cbd5e1);
53
+ padding: 0.15rem;
54
+ transition: background-color 140ms ease;
55
+ }
56
+
57
+ .root[data-variant='switch'] .thumb {
58
+ width: 1rem;
59
+ height: 1rem;
60
+ border-radius: 50%;
61
+ background: #fff;
62
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
63
+ transform: translateX(0);
64
+ transition: transform 140ms ease;
65
+ }
66
+
67
+ .root[data-variant='switch'].is-checked .track {
68
+ background: var(--bf-theme-button-primary-bg, #2563eb);
69
+ }
70
+
71
+ .root[data-variant='switch'].is-checked .thumb {
72
+ transform: translateX(0.9rem);
73
+ }
74
+
75
+ .root[data-variant='toggle'].is-checked {
76
+ background: color-mix(in srgb, var(--bf-theme-button-primary-bg, #2563eb) 12%, var(--bf-toggle-bg));
77
+ border-color: var(--bf-theme-button-primary-bg, #2563eb);
78
+ }
79
+
80
+ .root:disabled {
81
+ opacity: 0.55;
82
+ cursor: not-allowed;
30
83
  }
@@ -1,11 +1,15 @@
1
1
  class BfToggle extends HTMLElement {
2
+ static observedAttributes = ['checked', 'disabled', 'label', 'switch', 'variant'];
3
+
2
4
  constructor() {
3
5
  super();
4
6
  this.attachShadow({ mode: 'open' });
7
+ this._onClick = this._onClick.bind(this);
5
8
  }
6
9
 
7
10
  connectedCallback() {
8
11
  if (this._initialized) {
12
+ this._sync();
9
13
  return;
10
14
  }
11
15
  this._initialized = true;
@@ -15,16 +19,80 @@ class BfToggle extends HTMLElement {
15
19
  link.rel = 'stylesheet';
16
20
  link.href = cssUrl.href;
17
21
 
18
- const root = document.createElement('div');
22
+ const root = document.createElement('button');
19
23
  root.className = 'root';
20
24
  root.setAttribute('part', 'root');
21
- root.innerHTML = '<slot></slot>';
25
+ root.type = 'button';
26
+ root.innerHTML = `
27
+ <span class="track" part="track"><span class="thumb" part="thumb"></span></span>
28
+ <span class="text" part="text"><slot></slot></span>
29
+ `;
22
30
 
23
- if (!this.innerHTML.trim()) {
24
- root.textContent = 'toggle';
31
+ this.shadowRoot.replaceChildren(link, root);
32
+ this._root = root;
33
+ this._text = root.querySelector('.text');
34
+ this._root.addEventListener('click', this._onClick);
35
+ if (!this.textContent?.trim()) {
36
+ this.textContent = 'toggle';
25
37
  }
38
+ this._sync();
39
+ }
26
40
 
27
- this.shadowRoot.replaceChildren(link, root);
41
+ attributeChangedCallback() {
42
+ this._sync();
43
+ }
44
+
45
+ get checked() {
46
+ return this.hasAttribute('checked');
47
+ }
48
+
49
+ set checked(next) {
50
+ if (next) {
51
+ this.setAttribute('checked', '');
52
+ return;
53
+ }
54
+ this.removeAttribute('checked');
55
+ }
56
+
57
+ _onClick() {
58
+ if (this.hasAttribute('disabled')) {
59
+ return;
60
+ }
61
+ this.checked = !this.checked;
62
+ this.dispatchEvent(
63
+ new CustomEvent('bf-change', {
64
+ bubbles: true,
65
+ composed: true,
66
+ detail: {
67
+ checked: this.checked,
68
+ },
69
+ }),
70
+ );
71
+ }
72
+
73
+ _variant() {
74
+ const variant = (this.getAttribute('variant') || '').toLowerCase();
75
+ if (variant === 'switch' || this.hasAttribute('switch')) {
76
+ return 'switch';
77
+ }
78
+ return 'toggle';
79
+ }
80
+
81
+ _sync() {
82
+ if (!this._root) {
83
+ return;
84
+ }
85
+ const variant = this._variant();
86
+ this._root.setAttribute('data-variant', variant);
87
+ this._root.setAttribute('aria-pressed', String(this.checked));
88
+ this._root.setAttribute('role', 'switch');
89
+ this._root.setAttribute('aria-checked', String(this.checked));
90
+ this._root.disabled = this.hasAttribute('disabled');
91
+ this._root.classList.toggle('is-checked', this.checked);
92
+ if (this.getAttribute('label')) {
93
+ this._root.setAttribute('aria-label', this.getAttribute('label'));
94
+ }
95
+ this._text.hidden = variant === 'switch' && !(this.textContent || '').trim();
28
96
  }
29
97
  }
30
98