basecoat-css 0.3.8 → 0.3.10-beta.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.
- package/dist/basecoat.cdn.css +21 -3
- package/dist/basecoat.cdn.min.css +1 -1
- package/dist/basecoat.css +10 -2
- package/dist/js/all.js +159 -25
- package/dist/js/all.min.js +1 -1
- package/dist/js/carousel.js +192 -0
- package/dist/js/carousel.min.js +1 -0
- package/dist/js/select.js +159 -25
- package/dist/js/select.min.js +1 -1
- package/package.json +1 -1
package/dist/js/select.js
CHANGED
|
@@ -3,23 +3,31 @@
|
|
|
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)
|
|
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
|
+
let selectedValues = isMultiple ? new Set() : null;
|
|
26
|
+
let placeholder = null;
|
|
27
|
+
|
|
28
|
+
if (isMultiple) {
|
|
29
|
+
placeholder = selectComponent.dataset.placeholder || '';
|
|
30
|
+
}
|
|
23
31
|
|
|
24
32
|
const setActiveOption = (index) => {
|
|
25
33
|
if (activeIndex > -1 && options[activeIndex]) {
|
|
@@ -46,23 +54,92 @@
|
|
|
46
54
|
return parseFloat(style.transitionDuration) > 0 || parseFloat(style.transitionDelay) > 0;
|
|
47
55
|
};
|
|
48
56
|
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
57
|
+
const syncMultipleInputs = () => {
|
|
58
|
+
if (!isMultiple) return;
|
|
59
|
+
const values = Array.from(selectedValues);
|
|
60
|
+
const inputs = Array.from(selectComponent.querySelectorAll(':scope > input[type="hidden"]'));
|
|
61
|
+
inputs.slice(1).forEach(inp => inp.remove());
|
|
62
|
+
|
|
63
|
+
if (values.length === 0) {
|
|
64
|
+
input.value = '';
|
|
65
|
+
} else {
|
|
66
|
+
input.value = values[0];
|
|
67
|
+
let insertAfter = input;
|
|
68
|
+
for (let i = 1; i < values.length; i++) {
|
|
69
|
+
const clone = input.cloneNode(true);
|
|
70
|
+
clone.removeAttribute('id');
|
|
71
|
+
clone.value = values[i];
|
|
72
|
+
insertAfter.after(clone);
|
|
73
|
+
insertAfter = clone;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const updateMultipleLabel = () => {
|
|
79
|
+
if (!isMultiple) return;
|
|
80
|
+
const selected = options.filter(opt => selectedValues.has(opt.dataset.value));
|
|
81
|
+
if (selected.length === 0) {
|
|
82
|
+
selectedLabel.textContent = placeholder;
|
|
83
|
+
selectedLabel.classList.add('text-muted-foreground');
|
|
84
|
+
} else {
|
|
85
|
+
selectedLabel.textContent = selected.map(opt => opt.dataset.label || opt.textContent.trim()).join(', ');
|
|
86
|
+
selectedLabel.classList.remove('text-muted-foreground');
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const updateValue = (optionOrOptions, triggerEvent = true) => {
|
|
91
|
+
let value;
|
|
92
|
+
|
|
93
|
+
if (isMultiple) {
|
|
94
|
+
const opts = Array.isArray(optionOrOptions) ? optionOrOptions : [];
|
|
95
|
+
selectedValues = new Set(opts.map(opt => opt.dataset.value));
|
|
96
|
+
options.forEach(opt => {
|
|
97
|
+
if (selectedValues.has(opt.dataset.value)) {
|
|
98
|
+
opt.setAttribute('aria-selected', 'true');
|
|
99
|
+
} else {
|
|
100
|
+
opt.removeAttribute('aria-selected');
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
updateMultipleLabel();
|
|
104
|
+
syncMultipleInputs();
|
|
105
|
+
value = Array.from(selectedValues);
|
|
106
|
+
} else {
|
|
107
|
+
const option = optionOrOptions;
|
|
108
|
+
if (!option) return;
|
|
51
109
|
selectedLabel.innerHTML = option.innerHTML;
|
|
52
110
|
input.value = option.dataset.value;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
111
|
+
options.forEach(opt => {
|
|
112
|
+
if (opt === option) {
|
|
113
|
+
opt.setAttribute('aria-selected', 'true');
|
|
114
|
+
} else {
|
|
115
|
+
opt.removeAttribute('aria-selected');
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
value = option.dataset.value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (triggerEvent) {
|
|
122
|
+
selectComponent.dispatchEvent(new CustomEvent('change', {
|
|
123
|
+
detail: { value },
|
|
124
|
+
bubbles: true
|
|
125
|
+
}));
|
|
63
126
|
}
|
|
64
127
|
};
|
|
65
128
|
|
|
129
|
+
const toggleMultipleValue = (value, triggerEvent = true) => {
|
|
130
|
+
if (!isMultiple || value == null) return;
|
|
131
|
+
|
|
132
|
+
const newValues = new Set(selectedValues);
|
|
133
|
+
if (newValues.has(value)) {
|
|
134
|
+
newValues.delete(value);
|
|
135
|
+
} else {
|
|
136
|
+
newValues.add(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
|
|
140
|
+
updateValue(selectedOptions, triggerEvent);
|
|
141
|
+
};
|
|
142
|
+
|
|
66
143
|
const closePopover = (focusOnTrigger = true) => {
|
|
67
144
|
if (popover.getAttribute('aria-hidden') === 'true') return;
|
|
68
145
|
|
|
@@ -101,7 +178,27 @@
|
|
|
101
178
|
|
|
102
179
|
const selectByValue = (value) => {
|
|
103
180
|
const option = options.find(opt => opt.dataset.value === value);
|
|
104
|
-
|
|
181
|
+
if (isMultiple) {
|
|
182
|
+
if (value != null && selectedValues.has(value)) return;
|
|
183
|
+
if (option && value != null) {
|
|
184
|
+
const newValues = new Set(selectedValues);
|
|
185
|
+
newValues.add(value);
|
|
186
|
+
const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
|
|
187
|
+
updateValue(selectedOptions);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
selectOption(option);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const selectAll = () => {
|
|
195
|
+
if (!isMultiple) return;
|
|
196
|
+
updateValue(options.filter(opt => opt.dataset.value != null));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const selectNone = () => {
|
|
200
|
+
if (!isMultiple) return;
|
|
201
|
+
updateValue([]);
|
|
105
202
|
};
|
|
106
203
|
|
|
107
204
|
if (filter) {
|
|
@@ -137,13 +234,32 @@
|
|
|
137
234
|
filter.addEventListener('input', filterOptions);
|
|
138
235
|
}
|
|
139
236
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
237
|
+
if (isMultiple) {
|
|
238
|
+
const validValues = new Set(options.map(opt => opt.dataset.value).filter(v => v != null));
|
|
239
|
+
const inputs = Array.from(selectComponent.querySelectorAll(':scope > input[type="hidden"]'));
|
|
240
|
+
const initialValues = inputs
|
|
241
|
+
.map(inp => inp.value)
|
|
242
|
+
.filter(v => v != null && validValues.has(v));
|
|
243
|
+
|
|
244
|
+
let initialOptions;
|
|
245
|
+
if (initialValues.length > 0) {
|
|
246
|
+
initialOptions = options.filter(opt => initialValues.includes(opt.dataset.value));
|
|
247
|
+
} else {
|
|
248
|
+
initialOptions = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
updateValue(initialOptions, false);
|
|
252
|
+
} else {
|
|
253
|
+
let initialOption = options.find(opt => opt.dataset.value === input.value);
|
|
145
254
|
|
|
146
|
-
|
|
255
|
+
if (!initialOption) {
|
|
256
|
+
initialOption = options.find(opt => opt.dataset.value !== undefined) ?? options[0];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (initialOption) {
|
|
260
|
+
updateValue(initialOption, false);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
147
263
|
|
|
148
264
|
const handleKeyNavigation = (event) => {
|
|
149
265
|
const isPopoverOpen = popover.getAttribute('aria-hidden') === 'false';
|
|
@@ -169,7 +285,11 @@
|
|
|
169
285
|
|
|
170
286
|
if (event.key === 'Enter') {
|
|
171
287
|
if (activeIndex > -1) {
|
|
172
|
-
|
|
288
|
+
if (isMultiple) {
|
|
289
|
+
toggleMultipleValue(options[activeIndex].dataset.value);
|
|
290
|
+
} else {
|
|
291
|
+
selectOption(options[activeIndex]);
|
|
292
|
+
}
|
|
173
293
|
}
|
|
174
294
|
return;
|
|
175
295
|
}
|
|
@@ -268,7 +388,17 @@
|
|
|
268
388
|
listbox.addEventListener('click', (event) => {
|
|
269
389
|
const clickedOption = event.target.closest('[role="option"]');
|
|
270
390
|
if (clickedOption) {
|
|
271
|
-
|
|
391
|
+
if (isMultiple) {
|
|
392
|
+
toggleMultipleValue(clickedOption.dataset.value);
|
|
393
|
+
setActiveOption(options.indexOf(clickedOption));
|
|
394
|
+
if (filter) {
|
|
395
|
+
filter.focus();
|
|
396
|
+
} else {
|
|
397
|
+
trigger.focus();
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
selectOption(clickedOption);
|
|
401
|
+
}
|
|
272
402
|
}
|
|
273
403
|
});
|
|
274
404
|
|
|
@@ -285,8 +415,12 @@
|
|
|
285
415
|
});
|
|
286
416
|
|
|
287
417
|
popover.setAttribute('aria-hidden', 'true');
|
|
288
|
-
|
|
418
|
+
|
|
289
419
|
selectComponent.selectByValue = selectByValue;
|
|
420
|
+
if (isMultiple) {
|
|
421
|
+
selectComponent.selectAll = selectAll;
|
|
422
|
+
selectComponent.selectNone = selectNone;
|
|
423
|
+
}
|
|
290
424
|
selectComponent.dataset.selectInitialized = true;
|
|
291
425
|
selectComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
|
292
426
|
};
|
package/dist/js/select.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{const e=e=>{const t=e.querySelector(":scope > button"),
|
|
1
|
+
(()=>{const e=e=>{const t=e.querySelector(":scope > button"),a=t.querySelector(":scope > span"),r=e.querySelector(":scope > [data-popover]"),n=r?r.querySelector('[role="listbox"]'):null,i=e.querySelector(':scope > input[type="hidden"]'),s=e.querySelector('header input[type="text"]');if(!(t&&r&&n&&i)){const a=[];return t||a.push("trigger"),r||a.push("popover"),n||a.push("listbox"),i||a.push("input"),void console.error(`Select component initialisation failed. Missing element(s): ${a.join(", ")}`,e)}const o=Array.from(n.querySelectorAll('[role="option"]')),l=o.filter((e=>"true"!==e.getAttribute("aria-disabled")));let d=[...l],u=-1;const c="true"===n.getAttribute("aria-multiselectable");let f=c?new Set:null,v=null;c&&(v=e.dataset.placeholder||"");const p=e=>{if(u>-1&&l[u]&&l[u].classList.remove("active"),u=e,u>-1){const e=l[u];e.classList.add("active"),e.id?t.setAttribute("aria-activedescendant",e.id):t.removeAttribute("aria-activedescendant")}else t.removeAttribute("aria-activedescendant")},h=()=>{const e=getComputedStyle(r);return parseFloat(e.transitionDuration)>0||parseFloat(e.transitionDelay)>0},b=(t,r=!0)=>{let n;if(c){const r=Array.isArray(t)?t:[];f=new Set(r.map((e=>e.dataset.value))),l.forEach((e=>{f.has(e.dataset.value)?e.setAttribute("aria-selected","true"):e.removeAttribute("aria-selected")})),(()=>{if(!c)return;const e=l.filter((e=>f.has(e.dataset.value)));0===e.length?(a.textContent=v,a.classList.add("text-muted-foreground")):(a.textContent=e.map((e=>e.dataset.label||e.textContent.trim())).join(", "),a.classList.remove("text-muted-foreground"))})(),(()=>{if(!c)return;const t=Array.from(f);if(Array.from(e.querySelectorAll(':scope > input[type="hidden"]')).slice(1).forEach((e=>e.remove())),0===t.length)i.value="";else{i.value=t[0];let e=i;for(let a=1;a<t.length;a++){const r=i.cloneNode(!0);r.removeAttribute("id"),r.value=t[a],e.after(r),e=r}}})(),n=Array.from(f)}else{const e=t;if(!e)return;a.innerHTML=e.innerHTML,i.value=e.dataset.value,l.forEach((t=>{t===e?t.setAttribute("aria-selected","true"):t.removeAttribute("aria-selected")})),n=e.dataset.value}r&&e.dispatchEvent(new CustomEvent("change",{detail:{value:n},bubbles:!0}))},m=(e,t=!0)=>{if(!c||null==e)return;const a=new Set(f);a.has(e)?a.delete(e):a.add(e);const r=l.filter((e=>a.has(e.dataset.value)));b(r,t)},A=(e=!0)=>{if("true"!==r.getAttribute("aria-hidden")){if(s){const e=()=>{s.value="",d=[...l],o.forEach((e=>e.setAttribute("aria-hidden","false")))};h()?r.addEventListener("transitionend",e,{once:!0}):e()}e&&t.focus(),r.setAttribute("aria-hidden","true"),t.setAttribute("aria-expanded","false"),p(-1)}},y=e=>{if(!e)return;const t=i.value,a=e.dataset.value;null!=a&&a!==t&&b(e),A()},E=()=>{c&&b(l.filter((e=>null!=e.dataset.value)))},w=()=>{c&&b([])};if(s){const e=()=>{const e=s.value.trim().toLowerCase();p(-1),d=[],o.forEach((t=>{if(t.hasAttribute("data-force"))return t.setAttribute("aria-hidden","false"),void(l.includes(t)&&d.push(t));const a=(t.dataset.filter||t.textContent).trim().toLowerCase(),r=(t.dataset.keywords||"").toLowerCase().split(/[\s,]+/).filter(Boolean).some((t=>t.includes(e))),n=a.includes(e)||r;t.setAttribute("aria-hidden",String(!n)),n&&l.includes(t)&&d.push(t)}))};s.addEventListener("input",e)}if(c){const t=new Set(l.map((e=>e.dataset.value)).filter((e=>null!=e))),a=Array.from(e.querySelectorAll(':scope > input[type="hidden"]')).map((e=>e.value)).filter((e=>null!=e&&t.has(e)));let r;r=a.length>0?l.filter((e=>a.includes(e.dataset.value))):l.filter((e=>"true"===e.getAttribute("aria-selected"))),b(r,!1)}else{let e=l.find((e=>e.dataset.value===i.value));e||(e=l.find((e=>void 0!==e.dataset.value))??l[0]),e&&b(e,!1)}const g=e=>{const a="false"===r.getAttribute("aria-hidden");if(!["ArrowDown","ArrowUp","Enter","Home","End","Escape"].includes(e.key))return;if(!a)return void("Enter"!==e.key&&"Escape"!==e.key&&(e.preventDefault(),t.click()));if(e.preventDefault(),"Escape"===e.key)return void A();if("Enter"===e.key)return void(u>-1&&(c?m(l[u].dataset.value):y(l[u])));if(0===d.length)return;const n=u>-1?d.indexOf(l[u]):-1;let i=n;switch(e.key){case"ArrowDown":n<d.length-1&&(i=n+1);break;case"ArrowUp":n>0?i=n-1:-1===n&&(i=0);break;case"Home":i=0;break;case"End":i=d.length-1}if(i!==n){const e=d[i];p(l.indexOf(e)),e.scrollIntoView({block:"nearest",behavior:"smooth"})}};n.addEventListener("mousemove",(e=>{const t=e.target.closest('[role="option"]');if(t&&d.includes(t)){const e=l.indexOf(t);e!==u&&p(e)}})),n.addEventListener("mouseleave",(()=>{const e=n.querySelector('[role="option"][aria-selected="true"]');p(e?l.indexOf(e):-1)})),t.addEventListener("keydown",g),s&&s.addEventListener("keydown",g);t.addEventListener("click",(()=>{"true"===t.getAttribute("aria-expanded")?A():(()=>{document.dispatchEvent(new CustomEvent("basecoat:popover",{detail:{source:e}})),s&&(h()?r.addEventListener("transitionend",(()=>{s.focus()}),{once:!0}):s.focus()),r.setAttribute("aria-hidden","false"),t.setAttribute("aria-expanded","true");const a=n.querySelector('[role="option"][aria-selected="true"]');a&&(p(l.indexOf(a)),a.scrollIntoView({block:"nearest"}))})()})),n.addEventListener("click",(e=>{const a=e.target.closest('[role="option"]');a&&(c?(m(a.dataset.value),p(l.indexOf(a)),s?s.focus():t.focus()):y(a))})),document.addEventListener("click",(t=>{e.contains(t.target)||A(!1)})),document.addEventListener("basecoat:popover",(t=>{t.detail.source!==e&&A(!1)})),r.setAttribute("aria-hidden","true"),e.selectByValue=e=>{const t=l.find((t=>t.dataset.value===e));if(c){if(null!=e&&f.has(e))return;if(t&&null!=e){const t=new Set(f);t.add(e);const a=l.filter((e=>t.has(e.dataset.value)));b(a)}}else y(t)},c&&(e.selectAll=E,e.selectNone=w),e.dataset.selectInitialized=!0,e.dispatchEvent(new CustomEvent("basecoat:initialized"))};window.basecoat&&window.basecoat.register("select","div.select:not([data-select-initialized])",e)})();
|