basecoat-css 0.3.9 → 0.3.10-beta.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.
@@ -0,0 +1,192 @@
1
+ (() => {
2
+ const initCarousel = (carouselComponent) => {
3
+ const slidesContainer = carouselComponent.querySelector('.carousel-slides');
4
+ if (!slidesContainer) return;
5
+
6
+ const slides = Array.from(carouselComponent.querySelectorAll('.carousel-item'));
7
+ const prevButton = carouselComponent.querySelector('.carousel-prev');
8
+ const nextButton = carouselComponent.querySelector('.carousel-next');
9
+ const indicators = Array.from(carouselComponent.querySelectorAll('.carousel-indicators button'));
10
+
11
+ const loop = carouselComponent.dataset.carouselLoop === 'true';
12
+ const autoplayDelay = parseInt(carouselComponent.dataset.carouselAutoplay, 10);
13
+ const orientation = carouselComponent.dataset.orientation || 'horizontal';
14
+
15
+ let currentIndex = 0;
16
+ let autoplayInterval = null;
17
+
18
+ const getScrollAmount = () => {
19
+ if (slides.length === 0) return 0;
20
+ const firstSlide = slides[0];
21
+ return orientation === 'vertical'
22
+ ? firstSlide.offsetHeight + parseInt(getComputedStyle(slidesContainer).gap || 0)
23
+ : firstSlide.offsetWidth + parseInt(getComputedStyle(slidesContainer).gap || 0);
24
+ };
25
+
26
+ const scrollToIndex = (index) => {
27
+ const scrollAmount = getScrollAmount();
28
+ if (orientation === 'vertical') {
29
+ slidesContainer.scrollTo({ top: scrollAmount * index, behavior: 'smooth' });
30
+ } else {
31
+ slidesContainer.scrollTo({ left: scrollAmount * index, behavior: 'smooth' });
32
+ }
33
+ currentIndex = index;
34
+ updateIndicators();
35
+ updateButtonStates();
36
+ };
37
+
38
+ const updateIndicators = () => {
39
+ indicators.forEach((indicator, index) => {
40
+ const isActive = index === currentIndex;
41
+ indicator.setAttribute('aria-current', isActive ? 'true' : 'false');
42
+ indicator.setAttribute('aria-label', `Slide ${index + 1}${isActive ? ' (current)' : ''}`);
43
+ });
44
+
45
+ slides.forEach((slide, index) => {
46
+ slide.setAttribute('aria-hidden', index === currentIndex ? 'false' : 'true');
47
+ });
48
+ };
49
+
50
+ const updateButtonStates = () => {
51
+ if (!prevButton || !nextButton) return;
52
+
53
+ if (loop) {
54
+ prevButton.disabled = false;
55
+ nextButton.disabled = false;
56
+ } else {
57
+ prevButton.disabled = currentIndex === 0;
58
+ nextButton.disabled = currentIndex === slides.length - 1;
59
+ }
60
+ };
61
+
62
+ const goToPrevious = () => {
63
+ if (currentIndex > 0) {
64
+ scrollToIndex(currentIndex - 1);
65
+ } else if (loop) {
66
+ scrollToIndex(slides.length - 1);
67
+ }
68
+ };
69
+
70
+ const goToNext = () => {
71
+ if (currentIndex < slides.length - 1) {
72
+ scrollToIndex(currentIndex + 1);
73
+ } else if (loop) {
74
+ scrollToIndex(0);
75
+ }
76
+ };
77
+
78
+ const startAutoplay = () => {
79
+ if (!autoplayDelay || autoplayDelay <= 0) return;
80
+
81
+ autoplayInterval = setInterval(() => {
82
+ goToNext();
83
+ }, autoplayDelay);
84
+ };
85
+
86
+ const stopAutoplay = () => {
87
+ if (autoplayInterval) {
88
+ clearInterval(autoplayInterval);
89
+ autoplayInterval = null;
90
+ }
91
+ };
92
+
93
+ const detectCurrentSlide = () => {
94
+ const scrollPosition = orientation === 'vertical'
95
+ ? slidesContainer.scrollTop
96
+ : slidesContainer.scrollLeft;
97
+ const scrollAmount = getScrollAmount();
98
+ const newIndex = Math.round(scrollPosition / scrollAmount);
99
+
100
+ if (newIndex !== currentIndex && newIndex >= 0 && newIndex < slides.length) {
101
+ currentIndex = newIndex;
102
+ updateIndicators();
103
+ updateButtonStates();
104
+ }
105
+ };
106
+
107
+ // Previous/Next button handlers
108
+ if (prevButton) {
109
+ prevButton.addEventListener('click', () => {
110
+ stopAutoplay();
111
+ goToPrevious();
112
+ });
113
+ }
114
+
115
+ if (nextButton) {
116
+ nextButton.addEventListener('click', () => {
117
+ stopAutoplay();
118
+ goToNext();
119
+ });
120
+ }
121
+
122
+ // Indicator click handlers
123
+ indicators.forEach((indicator, index) => {
124
+ indicator.addEventListener('click', () => {
125
+ stopAutoplay();
126
+ scrollToIndex(index);
127
+ });
128
+ });
129
+
130
+ // Keyboard navigation
131
+ carouselComponent.addEventListener('keydown', (event) => {
132
+ const isVertical = orientation === 'vertical';
133
+ const prevKey = isVertical ? 'ArrowUp' : 'ArrowLeft';
134
+ const nextKey = isVertical ? 'ArrowDown' : 'ArrowRight';
135
+
136
+ switch (event.key) {
137
+ case prevKey:
138
+ event.preventDefault();
139
+ stopAutoplay();
140
+ goToPrevious();
141
+ break;
142
+ case nextKey:
143
+ event.preventDefault();
144
+ stopAutoplay();
145
+ goToNext();
146
+ break;
147
+ case 'Home':
148
+ event.preventDefault();
149
+ stopAutoplay();
150
+ scrollToIndex(0);
151
+ break;
152
+ case 'End':
153
+ event.preventDefault();
154
+ stopAutoplay();
155
+ scrollToIndex(slides.length - 1);
156
+ break;
157
+ }
158
+ });
159
+
160
+ // Detect scroll position changes (for touch/manual scrolling)
161
+ let scrollTimeout;
162
+ slidesContainer.addEventListener('scroll', () => {
163
+ clearTimeout(scrollTimeout);
164
+ scrollTimeout = setTimeout(() => {
165
+ detectCurrentSlide();
166
+ }, 100);
167
+ });
168
+
169
+ // Pause autoplay on hover or focus
170
+ if (autoplayDelay) {
171
+ carouselComponent.addEventListener('mouseenter', stopAutoplay);
172
+ carouselComponent.addEventListener('mouseleave', startAutoplay);
173
+ carouselComponent.addEventListener('focusin', stopAutoplay);
174
+ carouselComponent.addEventListener('focusout', startAutoplay);
175
+ }
176
+
177
+ // Initialize
178
+ updateIndicators();
179
+ updateButtonStates();
180
+
181
+ if (autoplayDelay) {
182
+ startAutoplay();
183
+ }
184
+
185
+ carouselComponent.dataset.carouselInitialized = true;
186
+ carouselComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
187
+ };
188
+
189
+ if (window.basecoat) {
190
+ window.basecoat.register('carousel', '.carousel:not([data-carousel-initialized])', initCarousel);
191
+ }
192
+ })();
@@ -0,0 +1 @@
1
+ (()=>{const e=e=>{const t=e.querySelector(".carousel-slides");if(!t)return;const r=Array.from(e.querySelectorAll(".carousel-item")),a=e.querySelector(".carousel-prev"),o=e.querySelector(".carousel-next"),l=Array.from(e.querySelectorAll(".carousel-indicators button")),s="true"===e.dataset.carouselLoop,n=parseInt(e.dataset.carouselAutoplay,10),i=e.dataset.orientation||"horizontal";let c=0,d=null;const u=()=>{if(0===r.length)return 0;const e=r[0];return"vertical"===i?e.offsetHeight+parseInt(getComputedStyle(t).gap||0):e.offsetWidth+parseInt(getComputedStyle(t).gap||0)},v=e=>{const r=u();"vertical"===i?t.scrollTo({top:r*e,behavior:"smooth"}):t.scrollTo({left:r*e,behavior:"smooth"}),c=e,f(),h()},f=()=>{l.forEach(((e,t)=>{const r=t===c;e.setAttribute("aria-current",r?"true":"false"),e.setAttribute("aria-label",`Slide ${t+1}${r?" (current)":""}`)})),r.forEach(((e,t)=>{e.setAttribute("aria-hidden",t===c?"false":"true")}))},h=()=>{a&&o&&(s?(a.disabled=!1,o.disabled=!1):(a.disabled=0===c,o.disabled=c===r.length-1))},p=()=>{c>0?v(c-1):s&&v(r.length-1)},b=()=>{c<r.length-1?v(c+1):s&&v(0)},E=()=>{!n||n<=0||(d=setInterval((()=>{b()}),n))},g=()=>{d&&(clearInterval(d),d=null)};let m;a&&a.addEventListener("click",(()=>{g(),p()})),o&&o.addEventListener("click",(()=>{g(),b()})),l.forEach(((e,t)=>{e.addEventListener("click",(()=>{g(),v(t)}))})),e.addEventListener("keydown",(e=>{const t="vertical"===i,a=t?"ArrowUp":"ArrowLeft",o=t?"ArrowDown":"ArrowRight";switch(e.key){case a:e.preventDefault(),g(),p();break;case o:e.preventDefault(),g(),b();break;case"Home":e.preventDefault(),g(),v(0);break;case"End":e.preventDefault(),g(),v(r.length-1)}})),t.addEventListener("scroll",(()=>{clearTimeout(m),m=setTimeout((()=>{(()=>{const e="vertical"===i?t.scrollTop:t.scrollLeft,a=u(),o=Math.round(e/a);o!==c&&o>=0&&o<r.length&&(c=o,f(),h())})()}),100)})),n&&(e.addEventListener("mouseenter",g),e.addEventListener("mouseleave",E),e.addEventListener("focusin",g),e.addEventListener("focusout",E)),f(),h(),n&&E(),e.dataset.carouselInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("carousel",".carousel:not([data-carousel-initialized])",e)})();
package/dist/js/select.js CHANGED
@@ -3,31 +3,37 @@
3
3
  const trigger = selectComponent.querySelector(':scope > button');
4
4
  const selectedLabel = trigger.querySelector(':scope > span');
5
5
  const popover = selectComponent.querySelector(':scope > [data-popover]');
6
- const listbox = popover.querySelector('[role="listbox"]');
6
+ const listbox = popover ? popover.querySelector('[role="listbox"]') : null;
7
7
  const input = selectComponent.querySelector(':scope > input[type="hidden"]');
8
8
  const filter = selectComponent.querySelector('header input[type="text"]');
9
+
9
10
  if (!trigger || !popover || !listbox || !input) {
10
11
  const missing = [];
11
12
  if (!trigger) missing.push('trigger');
12
13
  if (!popover) missing.push('popover');
13
14
  if (!listbox) missing.push('listbox');
14
- if (!input) missing.push('input');
15
+ if (!input) missing.push('input');
15
16
  console.error(`Select component initialisation failed. Missing element(s): ${missing.join(', ')}`, selectComponent);
16
17
  return;
17
18
  }
18
-
19
+
19
20
  const allOptions = Array.from(listbox.querySelectorAll('[role="option"]'));
20
21
  const options = allOptions.filter(opt => opt.getAttribute('aria-disabled') !== 'true');
21
22
  let visibleOptions = [...options];
22
23
  let activeIndex = -1;
24
+ const isMultiple = listbox.getAttribute('aria-multiselectable') === 'true';
25
+ const selectedOptions = isMultiple ? new Set() : null;
26
+ const placeholder = isMultiple ? (selectComponent.dataset.placeholder || '') : null;
27
+
28
+ const getValue = (opt) => opt.dataset.value ?? opt.textContent.trim();
23
29
 
24
30
  const setActiveOption = (index) => {
25
31
  if (activeIndex > -1 && options[activeIndex]) {
26
32
  options[activeIndex].classList.remove('active');
27
33
  }
28
-
34
+
29
35
  activeIndex = index;
30
-
36
+
31
37
  if (activeIndex > -1) {
32
38
  const activeOption = options[activeIndex];
33
39
  activeOption.classList.add('active');
@@ -46,68 +52,118 @@
46
52
  return parseFloat(style.transitionDuration) > 0 || parseFloat(style.transitionDelay) > 0;
47
53
  };
48
54
 
49
- const updateValue = (option, triggerEvent = true) => {
50
- if (option) {
55
+ const updateValue = (optionOrOptions, triggerEvent = true) => {
56
+ let value;
57
+
58
+ if (isMultiple) {
59
+ const opts = Array.isArray(optionOrOptions) ? optionOrOptions : [];
60
+ selectedOptions.clear();
61
+ opts.forEach(opt => selectedOptions.add(opt));
62
+
63
+ // Get selected options in DOM order
64
+ const selected = options.filter(opt => selectedOptions.has(opt));
65
+ if (selected.length === 0) {
66
+ selectedLabel.textContent = placeholder;
67
+ selectedLabel.classList.add('text-muted-foreground');
68
+ } else {
69
+ selectedLabel.textContent = selected.map(opt => opt.dataset.label || opt.textContent.trim()).join(', ');
70
+ selectedLabel.classList.remove('text-muted-foreground');
71
+ }
72
+
73
+ value = selected.map(getValue);
74
+ input.value = JSON.stringify(value);
75
+ } else {
76
+ const option = optionOrOptions;
77
+ if (!option) return;
51
78
  selectedLabel.innerHTML = option.innerHTML;
52
- input.value = option.dataset.value;
53
- listbox.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute('aria-selected');
54
- option.setAttribute('aria-selected', 'true');
55
-
56
- if (triggerEvent) {
57
- const event = new CustomEvent('change', {
58
- detail: { value: option.dataset.value },
59
- bubbles: true
60
- });
61
- selectComponent.dispatchEvent(event);
79
+ value = getValue(option);
80
+ input.value = value;
81
+ }
82
+
83
+ options.forEach(opt => {
84
+ const isSelected = isMultiple ? selectedOptions.has(opt) : opt === optionOrOptions;
85
+ if (isSelected) {
86
+ opt.setAttribute('aria-selected', 'true');
87
+ } else {
88
+ opt.removeAttribute('aria-selected');
62
89
  }
90
+ });
91
+
92
+ if (triggerEvent) {
93
+ selectComponent.dispatchEvent(new CustomEvent('change', {
94
+ detail: { value },
95
+ bubbles: true
96
+ }));
63
97
  }
64
98
  };
65
99
 
66
100
  const closePopover = (focusOnTrigger = true) => {
67
101
  if (popover.getAttribute('aria-hidden') === 'true') return;
68
-
102
+
69
103
  if (filter) {
70
104
  const resetFilter = () => {
71
105
  filter.value = '';
72
106
  visibleOptions = [...options];
73
107
  allOptions.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
74
108
  };
75
-
109
+
76
110
  if (hasTransition()) {
77
111
  popover.addEventListener('transitionend', resetFilter, { once: true });
78
112
  } else {
79
113
  resetFilter();
80
114
  }
81
115
  }
82
-
116
+
83
117
  if (focusOnTrigger) trigger.focus();
84
118
  popover.setAttribute('aria-hidden', 'true');
85
119
  trigger.setAttribute('aria-expanded', 'false');
86
120
  setActiveOption(-1);
87
- }
121
+ };
88
122
 
89
- const selectOption = (option) => {
90
- if (!option) return;
91
-
92
- const oldValue = input.value;
93
- const newValue = option.dataset.value;
123
+ const toggleMultipleValue = (option) => {
124
+ if (selectedOptions.has(option)) {
125
+ selectedOptions.delete(option);
126
+ } else {
127
+ selectedOptions.add(option);
128
+ }
129
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
130
+ };
94
131
 
95
- if (newValue != null && newValue !== oldValue) {
96
- updateValue(option);
132
+ const select = (value) => {
133
+ if (isMultiple) {
134
+ const option = options.find(opt => getValue(opt) === value && !selectedOptions.has(opt));
135
+ if (!option) return;
136
+ selectedOptions.add(option);
137
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
138
+ } else {
139
+ const option = options.find(opt => getValue(opt) === value);
140
+ if (!option) return;
141
+ if (input.value !== value) {
142
+ updateValue(option);
143
+ }
144
+ closePopover();
97
145
  }
98
-
99
- closePopover();
100
146
  };
101
147
 
102
- const selectByValue = (value) => {
103
- const option = options.find(opt => opt.dataset.value === value);
104
- selectOption(option);
148
+ const deselect = (value) => {
149
+ if (!isMultiple) return;
150
+ const option = options.find(opt => getValue(opt) === value && selectedOptions.has(opt));
151
+ if (!option) return;
152
+ selectedOptions.delete(option);
153
+ updateValue(options.filter(opt => selectedOptions.has(opt)));
154
+ };
155
+
156
+ const toggle = (value) => {
157
+ if (!isMultiple) return;
158
+ const option = options.find(opt => getValue(opt) === value);
159
+ if (!option) return;
160
+ toggleMultipleValue(option);
105
161
  };
106
162
 
107
163
  if (filter) {
108
164
  const filterOptions = () => {
109
165
  const searchTerm = filter.value.trim().toLowerCase();
110
-
166
+
111
167
  setActiveOption(-1);
112
168
 
113
169
  visibleOptions = [];
@@ -133,17 +189,37 @@
133
189
  }
134
190
  });
135
191
  };
136
-
192
+
137
193
  filter.addEventListener('input', filterOptions);
138
194
  }
139
195
 
140
- let initialOption = options.find(opt => opt.dataset.value === input.value);
141
-
142
- if (!initialOption) {
143
- initialOption = options.find(opt => opt.dataset.value !== undefined) ?? options[0];
144
- }
196
+ // Initialization
197
+ if (isMultiple) {
198
+ const ariaSelected = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
199
+ try {
200
+ const parsed = JSON.parse(input.value || '[]');
201
+ const validValues = new Set(options.map(getValue));
202
+ const initialValues = Array.isArray(parsed) ? parsed.filter(v => validValues.has(v)) : [];
145
203
 
146
- updateValue(initialOption, false);
204
+ const initialOptions = [];
205
+ if (initialValues.length > 0) {
206
+ // Match values to options in order, allowing duplicates
207
+ initialValues.forEach(val => {
208
+ const opt = options.find(o => getValue(o) === val && !initialOptions.includes(o));
209
+ if (opt) initialOptions.push(opt);
210
+ });
211
+ } else {
212
+ initialOptions.push(...ariaSelected);
213
+ }
214
+
215
+ updateValue(initialOptions, false);
216
+ } catch (e) {
217
+ updateValue(ariaSelected, false);
218
+ }
219
+ } else {
220
+ const initialOption = options.find(opt => getValue(opt) === input.value) || options[0];
221
+ if (initialOption) updateValue(initialOption, false);
222
+ }
147
223
 
148
224
  const handleKeyNavigation = (event) => {
149
225
  const isPopoverOpen = popover.getAttribute('aria-hidden') === 'false';
@@ -159,17 +235,25 @@
159
235
  }
160
236
  return;
161
237
  }
162
-
238
+
163
239
  event.preventDefault();
164
240
 
165
241
  if (event.key === 'Escape') {
166
242
  closePopover();
167
243
  return;
168
244
  }
169
-
245
+
170
246
  if (event.key === 'Enter') {
171
247
  if (activeIndex > -1) {
172
- selectOption(options[activeIndex]);
248
+ const option = options[activeIndex];
249
+ if (isMultiple) {
250
+ toggleMultipleValue(option);
251
+ } else {
252
+ if (input.value !== getValue(option)) {
253
+ updateValue(option);
254
+ }
255
+ closePopover();
256
+ }
173
257
  }
174
258
  return;
175
259
  }
@@ -235,7 +319,7 @@
235
319
  document.dispatchEvent(new CustomEvent('basecoat:popover', {
236
320
  detail: { source: selectComponent }
237
321
  }));
238
-
322
+
239
323
  if (filter) {
240
324
  if (hasTransition()) {
241
325
  popover.addEventListener('transitionend', () => {
@@ -248,7 +332,7 @@
248
332
 
249
333
  popover.setAttribute('aria-hidden', 'false');
250
334
  trigger.setAttribute('aria-expanded', 'true');
251
-
335
+
252
336
  const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
253
337
  if (selectedOption) {
254
338
  setActiveOption(options.indexOf(selectedOption));
@@ -267,8 +351,24 @@
267
351
 
268
352
  listbox.addEventListener('click', (event) => {
269
353
  const clickedOption = event.target.closest('[role="option"]');
270
- if (clickedOption) {
271
- selectOption(clickedOption);
354
+ if (!clickedOption) return;
355
+
356
+ const option = options.find(opt => opt === clickedOption);
357
+ if (!option) return;
358
+
359
+ if (isMultiple) {
360
+ toggleMultipleValue(option);
361
+ setActiveOption(options.indexOf(option));
362
+ if (filter) {
363
+ filter.focus();
364
+ } else {
365
+ trigger.focus();
366
+ }
367
+ } else {
368
+ if (input.value !== getValue(option)) {
369
+ updateValue(option);
370
+ }
371
+ closePopover();
272
372
  }
273
373
  });
274
374
 
@@ -285,8 +385,43 @@
285
385
  });
286
386
 
287
387
  popover.setAttribute('aria-hidden', 'true');
288
-
289
- selectComponent.selectByValue = selectByValue;
388
+
389
+ // Public API
390
+ Object.defineProperty(selectComponent, 'value', {
391
+ get: () => {
392
+ if (isMultiple) {
393
+ return options.filter(opt => selectedOptions.has(opt)).map(getValue);
394
+ } else {
395
+ return input.value;
396
+ }
397
+ },
398
+ set: (val) => {
399
+ if (isMultiple) {
400
+ const values = Array.isArray(val) ? val : (val != null ? [val] : []);
401
+ const opts = [];
402
+ values.forEach(v => {
403
+ const opt = options.find(o => getValue(o) === v && !opts.includes(o));
404
+ if (opt) opts.push(opt);
405
+ });
406
+ updateValue(opts);
407
+ } else {
408
+ const option = options.find(opt => getValue(opt) === val);
409
+ if (option) {
410
+ updateValue(option);
411
+ closePopover();
412
+ }
413
+ }
414
+ }
415
+ });
416
+
417
+ selectComponent.select = select;
418
+ selectComponent.selectByValue = select; // Backward compatibility alias
419
+ if (isMultiple) {
420
+ selectComponent.deselect = deselect;
421
+ selectComponent.toggle = toggle;
422
+ selectComponent.selectAll = () => updateValue(options);
423
+ selectComponent.selectNone = () => updateValue([]);
424
+ }
290
425
  selectComponent.dataset.selectInitialized = true;
291
426
  selectComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
292
427
  };
@@ -1 +1 @@
1
- (()=>{const e=e=>{const t=e.querySelector(":scope > button"),i=t.querySelector(":scope > span"),a=e.querySelector(":scope > [data-popover]"),r=a.querySelector('[role="listbox"]'),n=e.querySelector(':scope > input[type="hidden"]'),s=e.querySelector('header input[type="text"]');if(!(t&&a&&r&&n)){const i=[];return t||i.push("trigger"),a||i.push("popover"),r||i.push("listbox"),n||i.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${i.join(", ")}`,e)}const o=Array.from(r.querySelectorAll('[role="option"]')),d=o.filter((e=>"true"!==e.getAttribute("aria-disabled")));let c=[...d],l=-1;const u=e=>{if(l>-1&&d[l]&&d[l].classList.remove("active"),l=e,l>-1){const e=d[l];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},v=()=>{const e=getComputedStyle(a);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},p=(t,a=!0)=>{if(t&&(i.innerHTML=t.innerHTML,n.value=t.dataset.value,r.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute("aria-selected"),t.setAttribute("aria-selected","true"),a)){const i=new CustomEvent("change",{detail:{value:t.dataset.value},bubbles:!0});e.dispatchEvent(i)}},f=(e=!0)=>{if("true"!==a.getAttribute("aria-hidden")){if(s){const e=()=>{s.value="",c=[...d],o.forEach((e=>e.setAttribute("aria-hidden","false")))};v()?a.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),a.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),u(-1)}},b=e=>{if(!e)return;const t=n.value,i=e.dataset.value;null!=i&&i!==t&&p(e),f()};if(s){const e=()=>{const e=s.value.trim().toLowerCase();u(-1),c=[],o.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(d.includes(t)&&c.push(t));const i=(t.dataset.filter||t.textContent).trim().toLowerCase(),a=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),r=i.includes(e)||a;t.setAttribute("aria-hidden",String(!r)),r&&d.includes(t)&&c.push(t)}))};s.addEventListener("input",e)}let h=d.find((e=>e.dataset.value===n.value));h||(h=d.find((e=>void 0!==e.dataset.value))??d[0]),p(h,!1);const E=e=>{const i="false"===a.getAttribute("aria-hidden");if(!["ArrowDown","ArrowUp","Enter","Home","End","Escape"].includes(e.key))return;if(!i)return void("Enter"!==e.key&&"Escape"!==e.key&&(e.preventDefault(),t.click()));if(e.preventDefault(),"Escape"===e.key)return void f();if("Enter"===e.key)return void(l>-1&&b(d[l]));if(0===c.length)return;const r=l>-1?c.indexOf(d[l]):-1;let n=r;switch(e.key){case"ArrowDown":r<c.length-1&&(n=r+1);break;case"ArrowUp":r>0?n=r-1:-1===r&&(n=0);break;case"Home":n=0;break;case"End":n=c.length-1}if(n!==r){const e=c[n];u(d.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};r.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&c.includes(t)){const e=d.indexOf(t);e!==l&&u(e)}})),r.addEventListener("mouseleave",(()=>{const e=r.querySelector('[role="option"][aria-selected="true"]');u(e?d.indexOf(e):-1)})),t.addEventListener("keydown",E),s&&s.addEventListener("keydown",E);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?f():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),s&&(v()?a.addEventListener("transitionend",(()=>{s.focus()}),{once:!0}):s.focus()),a.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const i=r.querySelector('[role="option"][aria-selected="true"]');i&&(u(d.indexOf(i)),i.scrollIntoView({block:"nearest"}))})()})),r.addEventListener("click",(e=>{const t=e.target.closest('[role="option"]');t&&b(t)})),document.addEventListener("click",(t=>{e.contains(t.target)||f(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&f(!1)})),a.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=d.find((t=>t.dataset.value===e));b(t)},e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})();
1
+ (()=>{const e=e=>{const t=e.querySelector(":scope > button"),r=t.querySelector(":scope > span"),n=e.querySelector(":scope > [data-popover]"),i=n?n.querySelector('[role="listbox"]'):null,a=e.querySelector(':scope > input[type="hidden"]'),s=e.querySelector('header input[type="text"]');if(!(t&&n&&i&&a)){const r=[];return t||r.push("trigger"),n||r.push("popover"),i||r.push("listbox"),a||r.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${r.join(", ")}`,e)}const o=Array.from(i.querySelectorAll('[role="option"]')),c=o.filter((e=>"true"!==e.getAttribute("aria-disabled")));let l=[...c],d=-1;const u="true"===i.getAttribute("aria-multiselectable"),f=u?new Set:null,p=u?e.dataset.placeholder||"":null,v=e=>e.dataset.value??e.textContent.trim(),h=e=>{if(d>-1&&c[d]&&c[d].classList.remove("active"),d=e,d>-1){const e=c[d];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},b=()=>{const e=getComputedStyle(n);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},m=(t,n=!0)=>{let i;if(u){const e=Array.isArray(t)?t:[];f.clear(),e.forEach((e=>f.add(e)));const n=c.filter((e=>f.has(e)));0===n.length?(r.textContent=p,r.classList.add("text-muted-foreground")):(r.textContent=n.map((e=>e.dataset.label||e.textContent.trim())).join(", "),r.classList.remove("text-muted-foreground")),i=n.map(v),a.value=JSON.stringify(i)}else{const e=t;if(!e)return;r.innerHTML=e.innerHTML,i=v(e),a.value=i}c.forEach((e=>{(u?f.has(e):e===t)?e.setAttribute("aria-selected","true"):e.removeAttribute("aria-selected")})),n&&e.dispatchEvent(new CustomEvent("change",{detail:{value:i},bubbles:!0}))},y=(e=!0)=>{if("true"!==n.getAttribute("aria-hidden")){if(s){const e=()=>{s.value="",l=[...c],o.forEach((e=>e.setAttribute("aria-hidden","false")))};b()?n.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),n.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),h(-1)}},A=e=>{f.has(e)?f.delete(e):f.add(e),m(c.filter((e=>f.has(e))))},E=e=>{if(u){const t=c.find((t=>v(t)===e&&!f.has(t)));if(!t)return;f.add(t),m(c.filter((e=>f.has(e))))}else{const t=c.find((t=>v(t)===e));if(!t)return;a.value!==e&&m(t),y()}},g=e=>{if(!u)return;const t=c.find((t=>v(t)===e&&f.has(t)));t&&(f.delete(t),m(c.filter((e=>f.has(e)))))},w=e=>{if(!u)return;const t=c.find((t=>v(t)===e));t&&A(t)};if(s){const e=()=>{const e=s.value.trim().toLowerCase();h(-1),l=[],o.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(c.includes(t)&&l.push(t));const r=(t.dataset.filter||t.textContent).trim().toLowerCase(),n=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),i=r.includes(e)||n;t.setAttribute("aria-hidden",String(!i)),i&&c.includes(t)&&l.push(t)}))};s.addEventListener("input",e)}if(u){const e=c.filter((e=>"true"===e.getAttribute("aria-selected")));try{const t=JSON.parse(a.value||"[]"),r=new Set(c.map(v)),n=Array.isArray(t)?t.filter((e=>r.has(e))):[],i=[];n.length>0?n.forEach((e=>{const t=c.find((t=>v(t)===e&&!i.includes(t)));t&&i.push(t)})):i.push(...e),m(i,!1)}catch(t){m(e,!1)}}else{const e=c.find((e=>v(e)===a.value))||c[0];e&&m(e,!1)}const L=e=>{const r="false"===n.getAttribute("aria-hidden");if(!["ArrowDown","ArrowUp","Enter","Home","End","Escape"].includes(e.key))return;if(!r)return void("Enter"!==e.key&&"Escape"!==e.key&&(e.preventDefault(),t.click()));if(e.preventDefault(),"Escape"===e.key)return void y();if("Enter"===e.key){if(d>-1){const e=c[d];u?A(e):(a.value!==v(e)&&m(e),y())}return}if(0===l.length)return;const i=d>-1?l.indexOf(c[d]):-1;let s=i;switch(e.key){case"ArrowDown":i<l.length-1&&(s=i+1);break;case"ArrowUp":i>0?s=i-1:-1===i&&(s=0);break;case"Home":s=0;break;case"End":s=l.length-1}if(s!==i){const e=l[s];h(c.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};i.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&l.includes(t)){const e=c.indexOf(t);e!==d&&h(e)}})),i.addEventListener("mouseleave",(()=>{const e=i.querySelector('[role="option"][aria-selected="true"]');h(e?c.indexOf(e):-1)})),t.addEventListener("keydown",L),s&&s.addEventListener("keydown",L);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?y():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),s&&(b()?n.addEventListener("transitionend",(()=>{s.focus()}),{once:!0}):s.focus()),n.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const r=i.querySelector('[role="option"][aria-selected="true"]');r&&(h(c.indexOf(r)),r.scrollIntoView({block:"nearest"}))})()})),i.addEventListener("click",(e=>{const r=e.target.closest('[role="option"]');if(!r)return;const n=c.find((e=>e===r));n&&(u?(A(n),h(c.indexOf(n)),s?s.focus():t.focus()):(a.value!==v(n)&&m(n),y()))})),document.addEventListener("click",(t=>{e.contains(t.target)||y(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&y(!1)})),n.setAttribute("aria-hidden","true"),Object.defineProperty(e,"value",{get:()=>u?c.filter((e=>f.has(e))).map(v):a.value,set:e=>{if(u){const t=Array.isArray(e)?e:null!=e?[e]:[],r=[];t.forEach((e=>{const t=c.find((t=>v(t)===e&&!r.includes(t)));t&&r.push(t)})),m(r)}else{const t=c.find((t=>v(t)===e));t&&(m(t),y())}}}),e.select=E,e.selectByValue=E,u&&(e.deselect=g,e.toggle=w,e.selectAll=()=>m(c),e.selectNone=()=>m([])),e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basecoat-css",
3
- "version": "0.3.9",
3
+ "version": "0.3.10-beta.2",
4
4
  "description": "Tailwind CSS for Basecoat components",
5
5
  "author": {
6
6
  "name": "hunvreus",