bareframe 0.1.0 → 0.1.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 (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,19 +1,14 @@
1
1
  :host {
2
2
  --bf-breadcrumb-font: var(--bf-theme-font-family, inherit);
3
- --bf-breadcrumb-radius: var(--bf-theme-radius-md, 8px);
4
- --bf-breadcrumb-border-width: var(--bf-theme-border-width, 1px);
5
- --bf-breadcrumb-border-style: var(--bf-theme-border-style, solid);
3
+ --bf-breadcrumb-radius: var(--bf-theme-radius-md, 7px);
6
4
  --bf-breadcrumb-border-color: var(--bf-theme-breadcrumb-border-color, var(--bf-theme-border-1, #cbd5e1));
7
- --bf-breadcrumb-bg: var(--bf-theme-breadcrumb-bg, var(--bf-theme-surface-1, #ffffff));
5
+ --bf-breadcrumb-bg: var(--bf-theme-breadcrumb-bg, transparent);
8
6
  --bf-breadcrumb-color: var(--bf-theme-breadcrumb-color, var(--bf-theme-text-1, #0f172a));
9
- --bf-breadcrumb-padding-y: var(--bf-theme-space-2, 0.6rem);
10
- --bf-breadcrumb-padding-x: var(--bf-theme-space-3, 0.9rem);
11
- --bf-breadcrumb-transition:
12
- var(--bf-theme-transition-bg, background-color 120ms ease),
13
- var(--bf-theme-transition-color, color 120ms ease),
14
- var(--bf-theme-transition-border, border-color 120ms ease);
15
-
16
- display: block;
7
+ --bf-breadcrumb-muted: var(--bf-theme-text-2, #64748b);
8
+ --bf-breadcrumb-link: var(--bf-theme-button-primary-bg, #2563eb);
9
+ --bf-breadcrumb-pill-bg: var(--bf-theme-surface-1, #ffffff);
10
+
11
+ display: inline-block;
17
12
  font: var(--bf-breadcrumb-font);
18
13
  color: var(--bf-breadcrumb-color);
19
14
  }
@@ -21,10 +16,61 @@
21
16
  .root {
22
17
  background: var(--bf-breadcrumb-bg);
23
18
  color: var(--bf-breadcrumb-color);
24
- border-width: var(--bf-breadcrumb-border-width);
25
- border-style: var(--bf-breadcrumb-border-style);
26
- border-color: var(--bf-breadcrumb-border-color);
19
+ }
20
+
21
+ .list {
22
+ list-style: none;
23
+ margin: 0;
24
+ padding: 0;
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ align-items: center;
28
+ gap: 0.45rem;
29
+ }
30
+
31
+ .item {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ gap: 0.45rem;
35
+ color: inherit;
36
+ }
37
+
38
+ .item a,
39
+ .item span {
40
+ font: inherit;
41
+ color: inherit;
42
+ text-decoration: none;
43
+ }
44
+
45
+ .item a {
46
+ color: var(--bf-breadcrumb-link);
47
+ }
48
+
49
+ .item .current {
50
+ color: var(--bf-breadcrumb-color);
51
+ font-weight: 600;
52
+ }
53
+
54
+ .item .ellipsis {
55
+ color: var(--bf-breadcrumb-muted);
56
+ }
57
+
58
+ .separator {
59
+ color: var(--bf-breadcrumb-muted);
60
+ }
61
+
62
+ .root[data-variant='minimal'] .separator {
63
+ opacity: 0.55;
64
+ }
65
+
66
+ .root[data-variant='pills'] .item a,
67
+ .root[data-variant='pills'] .item span {
68
+ border: 1px solid var(--bf-breadcrumb-border-color);
69
+ background: var(--bf-breadcrumb-pill-bg);
27
70
  border-radius: var(--bf-breadcrumb-radius);
28
- padding: var(--bf-breadcrumb-padding-y) var(--bf-breadcrumb-padding-x);
29
- transition: var(--bf-breadcrumb-transition);
71
+ padding: 0.2rem 0.5rem;
72
+ }
73
+
74
+ .root[data-variant='pills'] .separator {
75
+ margin-inline: -0.1rem 0.1rem;
30
76
  }
@@ -1,11 +1,15 @@
1
1
  class BfBreadcrumb extends HTMLElement {
2
+ static observedAttributes = ['variant', 'separator', 'separator-icon', 'max'];
3
+
2
4
  constructor() {
3
5
  super();
4
6
  this.attachShadow({ mode: 'open' });
7
+ this._onSlotChange = this._onSlotChange.bind(this);
5
8
  }
6
9
 
7
10
  connectedCallback() {
8
11
  if (this._initialized) {
12
+ this._render();
9
13
  return;
10
14
  }
11
15
  this._initialized = true;
@@ -15,16 +19,147 @@ class BfBreadcrumb 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('nav');
19
23
  root.className = 'root';
20
24
  root.setAttribute('part', 'root');
21
- root.innerHTML = '<slot></slot>';
25
+ root.setAttribute('aria-label', 'Breadcrumb');
26
+ root.innerHTML = `
27
+ <ol class="list" part="list"></ol>
28
+ <slot hidden></slot>
29
+ `;
22
30
 
23
- if (!this.innerHTML.trim()) {
24
- root.textContent = 'breadcrumb';
31
+ this.shadowRoot.replaceChildren(link, root);
32
+ this._root = root;
33
+ this._list = root.querySelector('.list');
34
+ this._slot = root.querySelector('slot');
35
+ this._slot.addEventListener('slotchange', this._onSlotChange);
36
+ this._render();
37
+ }
38
+
39
+ attributeChangedCallback() {
40
+ this._render();
41
+ }
42
+
43
+ _onSlotChange() {
44
+ this._render();
45
+ }
46
+
47
+ _variant() {
48
+ const value = (this.getAttribute('variant') || 'default').toLowerCase();
49
+ if (['default', 'pills', 'minimal'].includes(value)) {
50
+ return value;
25
51
  }
52
+ return 'default';
53
+ }
26
54
 
27
- this.shadowRoot.replaceChildren(link, root);
55
+ _separatorText() {
56
+ const icon = this.getAttribute('separator-icon');
57
+ if (icon) {
58
+ return icon;
59
+ }
60
+ const raw = this.getAttribute('separator');
61
+ if (!raw) {
62
+ return '›';
63
+ }
64
+ const value = raw.toLowerCase();
65
+ if (value === 'chevron') {
66
+ return '›';
67
+ }
68
+ if (value === 'slash') {
69
+ return '/';
70
+ }
71
+ if (value === 'dot') {
72
+ return '•';
73
+ }
74
+ if (value === 'pipe') {
75
+ return '|';
76
+ }
77
+ return raw;
78
+ }
79
+
80
+ _max() {
81
+ const parsed = Number.parseInt(this.getAttribute('max') || '', 10);
82
+ if (Number.isFinite(parsed) && parsed >= 2) {
83
+ return parsed;
84
+ }
85
+ return Infinity;
86
+ }
87
+
88
+ _itemsFromDom() {
89
+ const items = [];
90
+ const children = [...this.children];
91
+ for (const child of children) {
92
+ const text = child.textContent?.trim();
93
+ if (!text) {
94
+ continue;
95
+ }
96
+ const isLink = child.tagName.toLowerCase() === 'a' && child.getAttribute('href');
97
+ items.push({
98
+ label: text,
99
+ href: isLink ? child.getAttribute('href') : '',
100
+ current: child.hasAttribute('current'),
101
+ });
102
+ }
103
+ return items;
104
+ }
105
+
106
+ _applyCollapse(items) {
107
+ const max = this._max();
108
+ if (max === Infinity || items.length <= max || max < 3) {
109
+ return items;
110
+ }
111
+ const head = items[0];
112
+ const tailCount = max - 2;
113
+ const tail = items.slice(items.length - tailCount);
114
+ return [head, { label: '...', href: '', current: false, ellipsis: true }, ...tail];
115
+ }
116
+
117
+ _render() {
118
+ if (!this._list || !this._root) {
119
+ return;
120
+ }
121
+ const items = this._applyCollapse(this._itemsFromDom());
122
+ this._root.setAttribute('data-variant', this._variant());
123
+
124
+ this._list.replaceChildren();
125
+ if (!items.length) {
126
+ const fallback = document.createElement('li');
127
+ fallback.className = 'item';
128
+ fallback.textContent = 'breadcrumb';
129
+ this._list.append(fallback);
130
+ return;
131
+ }
132
+
133
+ items.forEach((item, index) => {
134
+ const li = document.createElement('li');
135
+ li.className = 'item';
136
+ if (item.ellipsis) {
137
+ const span = document.createElement('span');
138
+ span.className = 'ellipsis';
139
+ span.textContent = item.label;
140
+ li.append(span);
141
+ } else if (item.href && !item.current) {
142
+ const link = document.createElement('a');
143
+ link.href = item.href;
144
+ link.textContent = item.label;
145
+ li.append(link);
146
+ } else {
147
+ const span = document.createElement('span');
148
+ span.className = item.current || index === items.length - 1 ? 'current' : '';
149
+ span.textContent = item.label;
150
+ if (item.current || index === items.length - 1) {
151
+ span.setAttribute('aria-current', 'page');
152
+ }
153
+ li.append(span);
154
+ }
155
+ if (index < items.length - 1) {
156
+ const sep = document.createElement('span');
157
+ sep.className = 'separator';
158
+ sep.textContent = this._separatorText();
159
+ li.append(sep);
160
+ }
161
+ this._list.append(li);
162
+ });
28
163
  }
29
164
  }
30
165
 
@@ -134,3 +134,7 @@ button.secondary {
134
134
  --bf-button-secondary-active-border-color
135
135
  );
136
136
  }
137
+
138
+ button.is-selected {
139
+ box-shadow: inset 0 0 0 2px var(--bf-theme-focus-ring-color, #93c5fd);
140
+ }
@@ -1,12 +1,19 @@
1
1
  class BfButton extends HTMLElement {
2
+ static observedAttributes = ['selected', 'disabled', 'variant', 'label', 'group'];
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() {
11
+ if (this._initialized) {
12
+ this._syncState();
13
+ return;
14
+ }
15
+ this._initialized = true;
8
16
  const label = this.getAttribute('label') || 'Button';
9
- const variant = this.getAttribute('variant') || 'primary';
10
17
  const cssUrl = new URL('./button.css', import.meta.url);
11
18
 
12
19
  this.shadowRoot.innerHTML = '';
@@ -16,26 +23,99 @@ class BfButton extends HTMLElement {
16
23
  link.href = cssUrl.href;
17
24
 
18
25
  const button = document.createElement('button');
19
- button.className = variant;
26
+ button.className = this.getAttribute('variant') || 'primary';
20
27
  button.type = 'button';
21
28
  button.textContent = label;
22
29
  button.append(document.createElement('slot'));
23
30
 
24
31
  this.shadowRoot.append(link, button);
32
+ this._button = button;
33
+ button.addEventListener('click', this._onClick);
34
+ this._syncState();
35
+ }
36
+
37
+ attributeChangedCallback() {
38
+ this._syncState();
39
+ }
40
+
41
+ get selected() {
42
+ return this.hasAttribute('selected');
43
+ }
44
+
45
+ set selected(value) {
46
+ if (value) {
47
+ this.setAttribute('selected', '');
48
+ return;
49
+ }
50
+ this.removeAttribute('selected');
51
+ }
52
+
53
+ _onClick() {
54
+ this._applyGroupSelection();
55
+ const label = this.getAttribute('label') || 'Button';
56
+ const variant = this.getAttribute('variant') || 'primary';
57
+ this.dispatchEvent(
58
+ new CustomEvent('bf-click', {
59
+ bubbles: true,
60
+ composed: true,
61
+ detail: {
62
+ label,
63
+ variant,
64
+ selected: this.selected,
65
+ group: this.getAttribute('group') || '',
66
+ at: new Date().toISOString(),
67
+ },
68
+ }),
69
+ );
70
+ }
71
+
72
+ _groupLimit() {
73
+ const multiple = this.getAttribute('multiple');
74
+ if (multiple === null) {
75
+ return 1;
76
+ }
77
+ if (multiple === '') {
78
+ return Infinity;
79
+ }
80
+ const parsed = Number.parseInt(multiple, 10);
81
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
82
+ }
83
+
84
+ _applyGroupSelection() {
85
+ const group = this.getAttribute('group');
86
+ if (!group) {
87
+ return;
88
+ }
89
+ const peers = [...document.querySelectorAll(`bf-button[group="${CSS.escape(group)}"]`)];
90
+ const cap = this._groupLimit();
91
+
92
+ if (cap === 1) {
93
+ for (const peer of peers) {
94
+ peer.selected = peer === this;
95
+ }
96
+ return;
97
+ }
98
+
99
+ if (this.selected) {
100
+ this.selected = false;
101
+ return;
102
+ }
103
+
104
+ const selectedCount = peers.filter((peer) => peer.selected).length;
105
+ if (selectedCount >= cap) {
106
+ return;
107
+ }
108
+ this.selected = true;
109
+ }
25
110
 
26
- button.addEventListener('click', () => {
27
- this.dispatchEvent(
28
- new CustomEvent('bf-click', {
29
- bubbles: true,
30
- composed: true,
31
- detail: {
32
- label,
33
- variant,
34
- at: new Date().toISOString(),
35
- },
36
- }),
37
- );
38
- });
111
+ _syncState() {
112
+ if (!this._button) {
113
+ return;
114
+ }
115
+ this._button.className = this.getAttribute('variant') || 'primary';
116
+ this._button.disabled = this.hasAttribute('disabled');
117
+ this._button.setAttribute('aria-pressed', String(this.selected));
118
+ this._button.classList.toggle('is-selected', this.selected);
39
119
  }
40
120
  }
41
121
 
@@ -1,17 +1,11 @@
1
1
  :host {
2
2
  --bf-chart-font: var(--bf-theme-font-family, inherit);
3
- --bf-chart-radius: var(--bf-theme-radius-md, 8px);
4
- --bf-chart-border-width: var(--bf-theme-border-width, 1px);
5
- --bf-chart-border-style: var(--bf-theme-border-style, solid);
3
+ --bf-chart-radius: var(--bf-theme-radius-md, 10px);
6
4
  --bf-chart-border-color: var(--bf-theme-chart-border-color, var(--bf-theme-border-1, #cbd5e1));
7
5
  --bf-chart-bg: var(--bf-theme-chart-bg, var(--bf-theme-surface-1, #ffffff));
8
6
  --bf-chart-color: var(--bf-theme-chart-color, var(--bf-theme-text-1, #0f172a));
9
- --bf-chart-padding-y: var(--bf-theme-space-2, 0.6rem);
10
- --bf-chart-padding-x: var(--bf-theme-space-3, 0.9rem);
11
- --bf-chart-transition:
12
- var(--bf-theme-transition-bg, background-color 120ms ease),
13
- var(--bf-theme-transition-color, color 120ms ease),
14
- var(--bf-theme-transition-border, border-color 120ms ease);
7
+ --bf-chart-accent: var(--bf-theme-button-primary-bg, #2563eb);
8
+ --bf-chart-muted: var(--bf-theme-surface-2, #e2e8f0);
15
9
 
16
10
  display: block;
17
11
  font: var(--bf-chart-font);
@@ -21,10 +15,165 @@
21
15
  .root {
22
16
  background: var(--bf-chart-bg);
23
17
  color: var(--bf-chart-color);
24
- border-width: var(--bf-chart-border-width);
25
- border-style: var(--bf-chart-border-style);
26
- border-color: var(--bf-chart-border-color);
18
+ border: 1px solid var(--bf-chart-border-color);
27
19
  border-radius: var(--bf-chart-radius);
28
- padding: var(--bf-chart-padding-y) var(--bf-chart-padding-x);
29
- transition: var(--bf-chart-transition);
20
+ padding: 0.75rem;
21
+ display: grid;
22
+ gap: 0.55rem;
23
+ }
24
+
25
+ .viz {
26
+ display: grid;
27
+ height: 130px;
28
+ align-items: end;
29
+ }
30
+
31
+ .bar {
32
+ display: none;
33
+ background: var(--bf-chart-accent);
34
+ border-radius: 6px 6px 0 0;
35
+ }
36
+
37
+ .viz .bar:nth-child(1) { height: 35%; }
38
+ .viz .bar:nth-child(2) { height: 52%; }
39
+ .viz .bar:nth-child(3) { height: 74%; }
40
+ .viz .bar:nth-child(4) { height: 46%; }
41
+ .viz .bar:nth-child(5) { height: 62%; }
42
+
43
+ .line,
44
+ .pie,
45
+ .donut,
46
+ .gauge,
47
+ .heatmap,
48
+ .treemap,
49
+ .gantt,
50
+ .graph {
51
+ display: none;
52
+ }
53
+
54
+ .root[data-variant='bar'] .viz,
55
+ .root[data-variant='sparkline'] .viz {
56
+ grid-template-columns: repeat(5, 1fr);
57
+ gap: 0.45rem;
58
+ }
59
+
60
+ .root[data-variant='bar'] .bar {
61
+ display: block;
62
+ }
63
+
64
+ .root[data-variant='line'] .line,
65
+ .root[data-variant='sparkline'] .line {
66
+ display: block;
67
+ height: 70%;
68
+ border-bottom: 3px solid var(--bf-chart-accent);
69
+ border-left: 3px solid transparent;
70
+ border-right: 3px solid transparent;
71
+ clip-path: polygon(0 70%, 18% 48%, 36% 58%, 56% 28%, 76% 42%, 100% 12%, 100% 100%, 0 100%);
72
+ background: linear-gradient(to top, color-mix(in srgb, var(--bf-chart-accent) 20%, transparent), transparent);
73
+ }
74
+
75
+ .root[data-variant='line'] .viz,
76
+ .root[data-variant='sparkline'] .viz {
77
+ grid-template-columns: 1fr;
78
+ }
79
+
80
+ .root[data-variant='pie'] .pie,
81
+ .root[data-variant='donut'] .donut,
82
+ .root[data-variant='gauge'] .gauge {
83
+ display: block;
84
+ justify-self: center;
85
+ width: 108px;
86
+ height: 108px;
87
+ border-radius: 50%;
88
+ }
89
+
90
+ .root[data-variant='pie'] .pie {
91
+ background: conic-gradient(var(--bf-chart-accent) 0 62%, var(--bf-chart-muted) 62% 100%);
92
+ }
93
+
94
+ .root[data-variant='donut'] .donut {
95
+ background:
96
+ radial-gradient(circle at center, var(--bf-chart-bg) 46%, transparent 47%),
97
+ conic-gradient(var(--bf-chart-accent) 0 68%, var(--bf-chart-muted) 68% 100%);
98
+ }
99
+
100
+ .root[data-variant='gauge'] .gauge {
101
+ height: 64px;
102
+ border-radius: 64px 64px 0 0;
103
+ background: conic-gradient(from 180deg at 50% 100%, var(--bf-chart-accent) 0 58%, var(--bf-chart-muted) 58% 100%);
104
+ }
105
+
106
+ .root[data-variant='heatmap'] .heatmap {
107
+ display: grid;
108
+ grid-template-columns: repeat(4, 1fr);
109
+ gap: 0.35rem;
110
+ }
111
+
112
+ .root[data-variant='heatmap'] .heatmap span {
113
+ aspect-ratio: 1;
114
+ border-radius: 4px;
115
+ background: color-mix(in srgb, var(--bf-chart-accent) 20%, var(--bf-chart-muted));
116
+ }
117
+
118
+ .root[data-variant='heatmap'] .heatmap span:nth-child(3n) {
119
+ background: color-mix(in srgb, var(--bf-chart-accent) 60%, var(--bf-chart-muted));
120
+ }
121
+
122
+ .root[data-variant='treemap'] .treemap {
123
+ display: grid;
124
+ grid-template-columns: 1.2fr 1fr;
125
+ gap: 0.35rem;
126
+ }
127
+
128
+ .root[data-variant='treemap'] .treemap span {
129
+ min-height: 56px;
130
+ background: color-mix(in srgb, var(--bf-chart-accent) 35%, var(--bf-chart-muted));
131
+ border-radius: 6px;
132
+ }
133
+
134
+ .root[data-variant='treemap'] .treemap span:first-child {
135
+ grid-row: span 2;
136
+ }
137
+
138
+ .root[data-variant='gantt'] .gantt {
139
+ display: grid;
140
+ gap: 0.45rem;
141
+ }
142
+
143
+ .root[data-variant='gantt'] .gantt span {
144
+ height: 0.65rem;
145
+ border-radius: 999px;
146
+ background: var(--bf-chart-muted);
147
+ position: relative;
148
+ }
149
+
150
+ .root[data-variant='gantt'] .gantt span::after {
151
+ content: '';
152
+ position: absolute;
153
+ inset: 0 auto 0 0;
154
+ width: 45%;
155
+ border-radius: inherit;
156
+ background: var(--bf-chart-accent);
157
+ }
158
+
159
+ .root[data-variant='gantt'] .gantt span:nth-child(2)::after { width: 70%; }
160
+ .root[data-variant='gantt'] .gantt span:nth-child(3)::after { width: 32%; }
161
+
162
+ .root[data-variant='graph'] .graph {
163
+ display: grid;
164
+ grid-template-columns: repeat(3, 1fr);
165
+ align-items: center;
166
+ justify-items: center;
167
+ gap: 0.45rem;
168
+ }
169
+
170
+ .root[data-variant='graph'] .graph span {
171
+ width: 1rem;
172
+ height: 1rem;
173
+ border-radius: 50%;
174
+ background: var(--bf-chart-accent);
175
+ }
176
+
177
+ .meta:empty {
178
+ display: none;
30
179
  }
@@ -1,4 +1,18 @@
1
1
  class BfChart extends HTMLElement {
2
+ static observedAttributes = [
3
+ 'variant',
4
+ 'bar',
5
+ 'line',
6
+ 'pie',
7
+ 'donut',
8
+ 'graph',
9
+ 'sparkline',
10
+ 'gauge',
11
+ 'heatmap',
12
+ 'treemap',
13
+ 'gantt',
14
+ ];
15
+
2
16
  constructor() {
3
17
  super();
4
18
  this.attachShadow({ mode: 'open' });
@@ -6,6 +20,7 @@ class BfChart extends HTMLElement {
6
20
 
7
21
  connectedCallback() {
8
22
  if (this._initialized) {
23
+ this._sync();
9
24
  return;
10
25
  }
11
26
  this._initialized = true;
@@ -18,13 +33,53 @@ class BfChart extends HTMLElement {
18
33
  const root = document.createElement('div');
19
34
  root.className = 'root';
20
35
  root.setAttribute('part', 'root');
21
- root.innerHTML = '<slot></slot>';
36
+ root.innerHTML = `
37
+ <div class="viz" part="viz">
38
+ <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
39
+ <div class="line"></div>
40
+ <div class="pie"></div>
41
+ <div class="donut"></div>
42
+ <div class="gauge"></div>
43
+ <div class="heatmap">
44
+ <span></span><span></span><span></span><span></span>
45
+ <span></span><span></span><span></span><span></span>
46
+ <span></span><span></span><span></span><span></span>
47
+ </div>
48
+ <div class="treemap"><span></span><span></span><span></span></div>
49
+ <div class="gantt"><span></span><span></span><span></span></div>
50
+ <div class="graph"><span></span><span></span><span></span></div>
51
+ </div>
52
+ <div class="meta" part="meta"><slot></slot></div>
53
+ `;
54
+
55
+ this.shadowRoot.replaceChildren(link, root);
56
+ this._root = root;
57
+ this._sync();
58
+ }
59
+
60
+ attributeChangedCallback() {
61
+ this._sync();
62
+ }
22
63
 
23
- if (!this.innerHTML.trim()) {
24
- root.textContent = 'chart';
64
+ _variant() {
65
+ const explicit = (this.getAttribute('variant') || '').toLowerCase();
66
+ const valid = ['bar', 'line', 'pie', 'donut', 'graph', 'sparkline', 'gauge', 'heatmap', 'treemap', 'gantt'];
67
+ if (valid.includes(explicit)) {
68
+ return explicit;
25
69
  }
70
+ for (const item of valid) {
71
+ if (this.hasAttribute(item)) {
72
+ return item;
73
+ }
74
+ }
75
+ return 'bar';
76
+ }
26
77
 
27
- this.shadowRoot.replaceChildren(link, root);
78
+ _sync() {
79
+ if (!this._root) {
80
+ return;
81
+ }
82
+ this._root.setAttribute('data-variant', this._variant());
28
83
  }
29
84
  }
30
85