mithril-materialized 3.5.9 → 3.6.0
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/collapsible.d.ts +2 -0
- package/dist/components.css +81 -45
- package/dist/core.css +15 -4
- package/dist/dropdown.d.ts +2 -0
- package/dist/forms.css +15 -4
- package/dist/index.css +106 -49
- package/dist/index.esm.js +204 -114
- package/dist/index.js +204 -113
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +204 -113
- package/dist/material-box.d.ts +1 -1
- package/dist/pickers.css +10 -0
- package/dist/search-select.d.ts +12 -1
- package/dist/select.d.ts +4 -0
- package/dist/timepicker.d.ts +0 -4
- package/dist/utils.d.ts +19 -0
- package/package.json +2 -3
- package/sass/components/_collapsible.scss +69 -17
- package/sass/components/_datatable.scss +7 -19
- package/sass/components/_materialbox.scss +23 -14
- package/sass/components/_theme-variables.scss +7 -0
- package/sass/components/_timepicker.scss +10 -0
- package/sass/components/forms/_input-fields.scss +10 -5
- package/sass/components/forms/_select.scss +23 -19
package/dist/index.umd.js
CHANGED
|
@@ -66,6 +66,28 @@
|
|
|
66
66
|
};
|
|
67
67
|
/** Check if a string or number is numeric. @see https://stackoverflow.com/a/9716488/319711 */
|
|
68
68
|
const isNumeric = (n) => !isNaN(parseFloat(n)) && isFinite(n);
|
|
69
|
+
/**
|
|
70
|
+
* Sort options array based on sorting configuration
|
|
71
|
+
* @param options - Array of options to sort
|
|
72
|
+
* @param sortConfig - Sort configuration: 'asc', 'desc', 'none', or custom comparator function
|
|
73
|
+
* @returns Sorted array (or original if 'none' or undefined)
|
|
74
|
+
*/
|
|
75
|
+
const sortOptions = (options, sortConfig) => {
|
|
76
|
+
if (!sortConfig || sortConfig === 'none') {
|
|
77
|
+
return options;
|
|
78
|
+
}
|
|
79
|
+
const sorted = [...options]; // Create a copy to avoid mutating original
|
|
80
|
+
if (typeof sortConfig === 'function') {
|
|
81
|
+
return sorted.sort(sortConfig);
|
|
82
|
+
}
|
|
83
|
+
// Sort by label, fallback to id if no label
|
|
84
|
+
return sorted.sort((a, b) => {
|
|
85
|
+
const aLabel = (a.label || a.id.toString()).toLowerCase();
|
|
86
|
+
const bLabel = (b.label || b.id.toString()).toLowerCase();
|
|
87
|
+
const comparison = aLabel.localeCompare(bLabel);
|
|
88
|
+
return sortConfig === 'asc' ? comparison : -comparison;
|
|
89
|
+
});
|
|
90
|
+
};
|
|
69
91
|
/**
|
|
70
92
|
* Pad left, default width 2 with a '0'
|
|
71
93
|
*
|
|
@@ -1413,19 +1435,18 @@
|
|
|
1413
1435
|
onclick: onToggle,
|
|
1414
1436
|
}, [
|
|
1415
1437
|
iconName ? m('i.material-icons', iconName) : undefined,
|
|
1416
|
-
header
|
|
1438
|
+
header
|
|
1439
|
+
? typeof header === 'string'
|
|
1440
|
+
? m('span.collapsible-header-text', header)
|
|
1441
|
+
: m('.collapsible-header-content', header)
|
|
1442
|
+
: undefined,
|
|
1417
1443
|
])
|
|
1418
1444
|
: undefined,
|
|
1419
1445
|
m('.collapsible-body', {
|
|
1420
1446
|
style: {
|
|
1421
1447
|
display: isActive ? 'block' : 'none',
|
|
1422
|
-
transition: 'display 0.3s ease',
|
|
1423
1448
|
},
|
|
1424
|
-
},
|
|
1425
|
-
m('.collapsible-body-content', {
|
|
1426
|
-
style: { padding: '2rem' },
|
|
1427
|
-
}, body ? (typeof body === 'string' ? m('div', { innerHTML: body }) : body) : undefined),
|
|
1428
|
-
]),
|
|
1449
|
+
}, body ? (typeof body === 'string' ? m('div', { innerHTML: body }) : body) : undefined),
|
|
1429
1450
|
]);
|
|
1430
1451
|
},
|
|
1431
1452
|
};
|
|
@@ -1448,7 +1469,7 @@
|
|
|
1448
1469
|
});
|
|
1449
1470
|
},
|
|
1450
1471
|
view: ({ attrs }) => {
|
|
1451
|
-
const { items, accordion = true, class: c, className, style, id } = attrs;
|
|
1472
|
+
const { items, header, accordion = true, class: c, className, style, id } = attrs;
|
|
1452
1473
|
const toggleItem = (index) => {
|
|
1453
1474
|
if (accordion) {
|
|
1454
1475
|
// Accordion mode: only one item can be active
|
|
@@ -1470,12 +1491,22 @@
|
|
|
1470
1491
|
}
|
|
1471
1492
|
}
|
|
1472
1493
|
};
|
|
1494
|
+
const collapsibleItems = items.map((item, index) => m(CollapsibleItem, Object.assign(Object.assign({}, item), { key: index, isActive: state.activeItems.has(index), onToggle: () => toggleItem(index) })));
|
|
1473
1495
|
return items && items.length > 0
|
|
1474
|
-
?
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1496
|
+
? header
|
|
1497
|
+
? m('ul.collapsible.with-header', {
|
|
1498
|
+
class: c || className,
|
|
1499
|
+
style,
|
|
1500
|
+
id,
|
|
1501
|
+
}, [
|
|
1502
|
+
m('li.collapsible-main-header', m('h4', typeof header === 'string' ? header : header)),
|
|
1503
|
+
collapsibleItems,
|
|
1504
|
+
])
|
|
1505
|
+
: m('ul.collapsible', {
|
|
1506
|
+
class: c || className,
|
|
1507
|
+
style,
|
|
1508
|
+
id,
|
|
1509
|
+
}, collapsibleItems)
|
|
1479
1510
|
: undefined;
|
|
1480
1511
|
},
|
|
1481
1512
|
};
|
|
@@ -1579,7 +1610,7 @@
|
|
|
1579
1610
|
};
|
|
1580
1611
|
};
|
|
1581
1612
|
|
|
1582
|
-
const defaultI18n$
|
|
1613
|
+
const defaultI18n$3 = {
|
|
1583
1614
|
cancel: 'Cancel',
|
|
1584
1615
|
clear: 'Clear',
|
|
1585
1616
|
done: 'Ok',
|
|
@@ -1687,9 +1718,9 @@
|
|
|
1687
1718
|
else if (attrs.displayFormat) {
|
|
1688
1719
|
finalFormat = attrs.displayFormat;
|
|
1689
1720
|
}
|
|
1690
|
-
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n$
|
|
1721
|
+
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n$3, onSelect: null, onOpen: null, onClose: null }, attrs);
|
|
1691
1722
|
// Merge i18n properly
|
|
1692
|
-
merged.i18n = Object.assign(Object.assign({}, defaultI18n$
|
|
1723
|
+
merged.i18n = Object.assign(Object.assign({}, defaultI18n$3), attrs.i18n);
|
|
1693
1724
|
return merged;
|
|
1694
1725
|
};
|
|
1695
1726
|
const toString = (date, format) => {
|
|
@@ -2140,11 +2171,11 @@
|
|
|
2140
2171
|
prevMonth();
|
|
2141
2172
|
},
|
|
2142
2173
|
}, m('svg', {
|
|
2143
|
-
fill: '#000000',
|
|
2144
2174
|
height: '24',
|
|
2145
2175
|
viewBox: '0 0 24 24',
|
|
2146
2176
|
width: '24',
|
|
2147
2177
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
2178
|
+
style: 'fill: var(--mm-text-primary, rgba(0, 0, 0, 0.87));',
|
|
2148
2179
|
}, [
|
|
2149
2180
|
m('path', { d: 'M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z' }),
|
|
2150
2181
|
m('path', { d: 'M0-.5h24v24H0z', fill: 'none' }),
|
|
@@ -2206,11 +2237,11 @@
|
|
|
2206
2237
|
nextMonth();
|
|
2207
2238
|
},
|
|
2208
2239
|
}, m('svg', {
|
|
2209
|
-
fill: '#000000',
|
|
2210
2240
|
height: '24',
|
|
2211
2241
|
viewBox: '0 0 24 24',
|
|
2212
2242
|
width: '24',
|
|
2213
2243
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
2244
|
+
style: 'fill: var(--mm-text-primary, rgba(0, 0, 0, 0.87));',
|
|
2214
2245
|
}, [
|
|
2215
2246
|
m('path', { d: 'M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z' }),
|
|
2216
2247
|
m('path', { d: 'M0-.25h24v24H0z', fill: 'none' }),
|
|
@@ -3223,19 +3254,29 @@
|
|
|
3223
3254
|
if (!controlled && attrs.defaultValue !== undefined) {
|
|
3224
3255
|
textarea.value = String(attrs.defaultValue);
|
|
3225
3256
|
}
|
|
3226
|
-
// Height will be calculated by hidden div
|
|
3227
3257
|
// Update character count state for counter component
|
|
3228
3258
|
if (maxLength) {
|
|
3229
3259
|
state.currentLength = textarea.value.length;
|
|
3230
3260
|
}
|
|
3261
|
+
// Calculate initial height after DOM is fully ready
|
|
3262
|
+
// Use requestAnimationFrame to ensure layout is complete
|
|
3263
|
+
if (state.hiddenDiv) {
|
|
3264
|
+
requestAnimationFrame(() => {
|
|
3265
|
+
if (state.hiddenDiv) {
|
|
3266
|
+
updateHeight(textarea, state.hiddenDiv);
|
|
3267
|
+
m.redraw();
|
|
3268
|
+
}
|
|
3269
|
+
});
|
|
3270
|
+
}
|
|
3231
3271
|
}, onupdate: ({ dom }) => {
|
|
3232
3272
|
const textarea = dom;
|
|
3233
|
-
|
|
3234
|
-
textarea.style.height = state.height;
|
|
3235
|
-
// Trigger height recalculation when value changes programmatically
|
|
3273
|
+
// Recalculate and apply height
|
|
3236
3274
|
if (state.hiddenDiv) {
|
|
3237
3275
|
updateHeight(textarea, state.hiddenDiv);
|
|
3238
3276
|
}
|
|
3277
|
+
if (state.height) {
|
|
3278
|
+
textarea.style.height = state.height;
|
|
3279
|
+
}
|
|
3239
3280
|
}, onfocus: () => {
|
|
3240
3281
|
state.active = true;
|
|
3241
3282
|
}, oninput: (e) => {
|
|
@@ -4524,7 +4565,7 @@
|
|
|
4524
4565
|
// Create dropdown with proper positioning
|
|
4525
4566
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
4526
4567
|
tabindex: 0,
|
|
4527
|
-
style: getPortalStyles(state.inputRef),
|
|
4568
|
+
style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
|
|
4528
4569
|
oncreate: ({ dom }) => {
|
|
4529
4570
|
state.dropdownRef = dom;
|
|
4530
4571
|
},
|
|
@@ -4636,7 +4677,7 @@
|
|
|
4636
4677
|
onremove: () => {
|
|
4637
4678
|
state.dropdownRef = null;
|
|
4638
4679
|
},
|
|
4639
|
-
style: getDropdownStyles(state.inputRef, true, items, true),
|
|
4680
|
+
style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, items, true)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
|
|
4640
4681
|
}, items.map((item) => {
|
|
4641
4682
|
if (item.divider) {
|
|
4642
4683
|
return m('li.divider');
|
|
@@ -4776,7 +4817,7 @@
|
|
|
4776
4817
|
|
|
4777
4818
|
/**
|
|
4778
4819
|
* Pure TypeScript MaterialBox - creates an image lightbox that fills the screen when clicked
|
|
4779
|
-
*
|
|
4820
|
+
* Uses CSS classes from _materialbox.scss
|
|
4780
4821
|
*/
|
|
4781
4822
|
const MaterialBox = () => {
|
|
4782
4823
|
const state = {
|
|
@@ -4792,21 +4833,11 @@
|
|
|
4792
4833
|
state.originalImage = img;
|
|
4793
4834
|
if (attrs.onOpenStart)
|
|
4794
4835
|
attrs.onOpenStart();
|
|
4836
|
+
const inDuration = attrs.inDuration || 275;
|
|
4795
4837
|
// Create overlay
|
|
4796
4838
|
const overlay = document.createElement('div');
|
|
4797
4839
|
overlay.className = 'materialbox-overlay';
|
|
4798
|
-
overlay.style.
|
|
4799
|
-
position: fixed;
|
|
4800
|
-
top: 0;
|
|
4801
|
-
left: 0;
|
|
4802
|
-
right: 0;
|
|
4803
|
-
bottom: 0;
|
|
4804
|
-
background-color: rgba(0, 0, 0, 0.85);
|
|
4805
|
-
z-index: 1000;
|
|
4806
|
-
opacity: 0;
|
|
4807
|
-
transition: opacity ${attrs.inDuration || 275}ms ease;
|
|
4808
|
-
cursor: zoom-out;
|
|
4809
|
-
`;
|
|
4840
|
+
overlay.style.transition = `opacity ${inDuration}ms ease`;
|
|
4810
4841
|
// Create enlarged image
|
|
4811
4842
|
const enlargedImg = document.createElement('img');
|
|
4812
4843
|
enlargedImg.src = img.src;
|
|
@@ -4827,36 +4858,18 @@
|
|
|
4827
4858
|
finalWidth = maxHeight * aspectRatio;
|
|
4828
4859
|
}
|
|
4829
4860
|
// Set initial position and size (same as original image)
|
|
4830
|
-
enlargedImg.style.
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
height: ${imgRect.height}px;
|
|
4836
|
-
transition: all ${attrs.inDuration || 275}ms ease;
|
|
4837
|
-
cursor: zoom-out;
|
|
4838
|
-
max-width: none;
|
|
4839
|
-
z-index: 1001;
|
|
4840
|
-
`;
|
|
4861
|
+
enlargedImg.style.top = `${imgRect.top}px`;
|
|
4862
|
+
enlargedImg.style.left = `${imgRect.left}px`;
|
|
4863
|
+
enlargedImg.style.width = `${imgRect.width}px`;
|
|
4864
|
+
enlargedImg.style.height = `${imgRect.height}px`;
|
|
4865
|
+
enlargedImg.style.transition = `all ${inDuration}ms ease`;
|
|
4841
4866
|
// Add caption if provided
|
|
4842
4867
|
let caption = null;
|
|
4843
4868
|
if (attrs.caption) {
|
|
4844
4869
|
caption = document.createElement('div');
|
|
4845
4870
|
caption.className = 'materialbox-caption';
|
|
4846
4871
|
caption.textContent = attrs.caption;
|
|
4847
|
-
caption.style.
|
|
4848
|
-
position: fixed;
|
|
4849
|
-
bottom: 20px;
|
|
4850
|
-
left: 50%;
|
|
4851
|
-
transform: translateX(-50%);
|
|
4852
|
-
color: white;
|
|
4853
|
-
font-size: 16px;
|
|
4854
|
-
text-align: center;
|
|
4855
|
-
opacity: 0;
|
|
4856
|
-
transition: opacity ${attrs.inDuration || 275}ms ease ${attrs.inDuration || 275}ms;
|
|
4857
|
-
z-index: 1002;
|
|
4858
|
-
pointer-events: none;
|
|
4859
|
-
`;
|
|
4872
|
+
caption.style.transition = `opacity ${inDuration}ms ease ${inDuration}ms`;
|
|
4860
4873
|
}
|
|
4861
4874
|
// Add to DOM
|
|
4862
4875
|
document.body.appendChild(overlay);
|
|
@@ -4891,7 +4904,7 @@
|
|
|
4891
4904
|
setTimeout(() => {
|
|
4892
4905
|
if (attrs.onOpenEnd)
|
|
4893
4906
|
attrs.onOpenEnd();
|
|
4894
|
-
},
|
|
4907
|
+
}, inDuration);
|
|
4895
4908
|
};
|
|
4896
4909
|
const closeBox = (attrs) => {
|
|
4897
4910
|
if (!state.isOpen || !state.originalImage || !state.overlay || !state.overlayImage)
|
|
@@ -4948,8 +4961,14 @@
|
|
|
4948
4961
|
},
|
|
4949
4962
|
view: ({ attrs }) => {
|
|
4950
4963
|
const { src, alt, width, height, caption, className, style } = attrs, otherAttrs = __rest(attrs, ["src", "alt", "width", "height", "caption", "className", "style"]);
|
|
4964
|
+
// Build style attribute - handle both string and object styles
|
|
4965
|
+
let imgStyle = style || {};
|
|
4966
|
+
if (typeof style !== 'string') {
|
|
4967
|
+
// If style is an object or undefined, add default styles as object
|
|
4968
|
+
imgStyle = Object.assign({ cursor: 'zoom-in', transition: 'opacity 200ms ease' }, (style || {}));
|
|
4969
|
+
}
|
|
4951
4970
|
return m('img.materialboxed', Object.assign(Object.assign({}, otherAttrs), { src, alt: alt || '', width,
|
|
4952
|
-
height, className: ['materialboxed', className].filter(Boolean).join(' ') || undefined, style:
|
|
4971
|
+
height, className: ['materialboxed', className].filter(Boolean).join(' ') || undefined, style: imgStyle, onclick: (e) => {
|
|
4953
4972
|
e.preventDefault();
|
|
4954
4973
|
openBox(e.target, attrs);
|
|
4955
4974
|
} }));
|
|
@@ -5279,6 +5298,11 @@
|
|
|
5279
5298
|
};
|
|
5280
5299
|
};
|
|
5281
5300
|
|
|
5301
|
+
const defaultI18n$2 = {
|
|
5302
|
+
cancel: 'Cancel',
|
|
5303
|
+
clear: 'Clear',
|
|
5304
|
+
done: 'Ok',
|
|
5305
|
+
};
|
|
5282
5306
|
const defaultOptions = {
|
|
5283
5307
|
dialRadius: 135,
|
|
5284
5308
|
outerRadius: 105,
|
|
@@ -5289,11 +5313,7 @@
|
|
|
5289
5313
|
defaultTime: 'now',
|
|
5290
5314
|
fromNow: 0,
|
|
5291
5315
|
showClearBtn: false,
|
|
5292
|
-
i18n:
|
|
5293
|
-
cancel: 'Cancel',
|
|
5294
|
-
clear: 'Clear',
|
|
5295
|
-
done: 'Ok',
|
|
5296
|
-
},
|
|
5316
|
+
i18n: defaultI18n$2,
|
|
5297
5317
|
autoClose: false,
|
|
5298
5318
|
twelveHour: true,
|
|
5299
5319
|
vibrate: true,
|
|
@@ -5443,7 +5463,7 @@
|
|
|
5443
5463
|
state.hours = hours;
|
|
5444
5464
|
state.minutes = minutes;
|
|
5445
5465
|
if (state.spanHours) {
|
|
5446
|
-
state.spanHours.innerHTML = state.hours
|
|
5466
|
+
state.spanHours.innerHTML = addLeadingZero(state.hours);
|
|
5447
5467
|
}
|
|
5448
5468
|
if (state.spanMinutes) {
|
|
5449
5469
|
state.spanMinutes.innerHTML = addLeadingZero(state.minutes);
|
|
@@ -5551,7 +5571,7 @@
|
|
|
5551
5571
|
}
|
|
5552
5572
|
state[state.currentView] = value;
|
|
5553
5573
|
if (isHours && state.spanHours) {
|
|
5554
|
-
state.spanHours.innerHTML = value
|
|
5574
|
+
state.spanHours.innerHTML = addLeadingZero(value);
|
|
5555
5575
|
}
|
|
5556
5576
|
else if (!isHours && state.spanMinutes) {
|
|
5557
5577
|
state.spanMinutes.innerHTML = addLeadingZero(value);
|
|
@@ -5685,7 +5705,7 @@
|
|
|
5685
5705
|
const TimepickerModal = () => {
|
|
5686
5706
|
return {
|
|
5687
5707
|
view: ({ attrs }) => {
|
|
5688
|
-
const {
|
|
5708
|
+
const { i18n, showClearBtn } = attrs;
|
|
5689
5709
|
return [
|
|
5690
5710
|
m('.modal-content.timepicker-container', [
|
|
5691
5711
|
m('.timepicker-digital-display', [
|
|
@@ -5697,7 +5717,7 @@
|
|
|
5697
5717
|
oncreate: (vnode) => {
|
|
5698
5718
|
state.spanHours = vnode.dom;
|
|
5699
5719
|
},
|
|
5700
|
-
}, state.hours
|
|
5720
|
+
}, addLeadingZero(state.hours)),
|
|
5701
5721
|
':',
|
|
5702
5722
|
m('span.timepicker-span-minutes', {
|
|
5703
5723
|
class: state.currentView === 'minutes' ? 'text-primary' : '',
|
|
@@ -5777,18 +5797,18 @@
|
|
|
5777
5797
|
tabindex: options.twelveHour ? '3' : '1',
|
|
5778
5798
|
style: showClearBtn ? '' : 'visibility: hidden;',
|
|
5779
5799
|
onclick: () => clear(),
|
|
5780
|
-
},
|
|
5800
|
+
}, i18n.clear),
|
|
5781
5801
|
m('.confirmation-btns', [
|
|
5782
5802
|
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
5783
5803
|
type: 'button',
|
|
5784
5804
|
tabindex: options.twelveHour ? '3' : '1',
|
|
5785
5805
|
onclick: () => close(),
|
|
5786
|
-
},
|
|
5806
|
+
}, i18n.cancel),
|
|
5787
5807
|
m('button.btn-flat.timepicker-close.waves-effect', {
|
|
5788
5808
|
type: 'button',
|
|
5789
5809
|
tabindex: options.twelveHour ? '3' : '1',
|
|
5790
5810
|
onclick: () => done(),
|
|
5791
|
-
},
|
|
5811
|
+
}, i18n.done),
|
|
5792
5812
|
]),
|
|
5793
5813
|
]),
|
|
5794
5814
|
]),
|
|
@@ -5805,7 +5825,6 @@
|
|
|
5805
5825
|
}
|
|
5806
5826
|
};
|
|
5807
5827
|
const renderPickerToPortal = (attrs) => {
|
|
5808
|
-
const { showClearBtn = false, clearLabel = 'Clear', closeLabel = 'Cancel' } = attrs;
|
|
5809
5828
|
const pickerModal = m('.timepicker-modal-wrapper', {
|
|
5810
5829
|
style: {
|
|
5811
5830
|
position: 'fixed',
|
|
@@ -5848,10 +5867,8 @@
|
|
|
5848
5867
|
},
|
|
5849
5868
|
}, [
|
|
5850
5869
|
m(TimepickerModal, {
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
closeLabel,
|
|
5854
|
-
doneLabel: 'OK',
|
|
5870
|
+
i18n: options.i18n,
|
|
5871
|
+
showClearBtn: options.showClearBtn,
|
|
5855
5872
|
}),
|
|
5856
5873
|
]),
|
|
5857
5874
|
]);
|
|
@@ -5911,7 +5928,7 @@
|
|
|
5911
5928
|
}
|
|
5912
5929
|
},
|
|
5913
5930
|
view: ({ attrs }) => {
|
|
5914
|
-
const { id = state.id, label, placeholder, disabled, readonly, required, iconName, helperText, onchange, oninput, useModal = true,
|
|
5931
|
+
const { id = state.id, label, placeholder, disabled, readonly, required, iconName, helperText, onchange, oninput, useModal = true, twelveHour, className: cn1, class: cn2, } = attrs;
|
|
5915
5932
|
const className = cn1 || cn2 || 'col s12';
|
|
5916
5933
|
// Format time value for display
|
|
5917
5934
|
const formatTime = (hours, minutes, use12Hour) => {
|
|
@@ -6357,7 +6374,7 @@
|
|
|
6357
6374
|
// Render ungrouped options first
|
|
6358
6375
|
attrs.options
|
|
6359
6376
|
.filter((option) => !option.group)
|
|
6360
|
-
.map((option) => m('li', Object.assign({ class: option.disabled ? 'disabled' : state.focusedIndex === attrs.options.indexOf(option) ? 'focused' : '' }, (option.disabled
|
|
6377
|
+
.map((option) => m('li', Object.assign({ class: `${option.disabled ? 'disabled' : ''}${isSelected(option.id, selectedIds) ? ' selected' : ''}${state.focusedIndex === attrs.options.indexOf(option) ? ' focused' : ''}` }, (option.disabled
|
|
6361
6378
|
? {}
|
|
6362
6379
|
: {
|
|
6363
6380
|
onclick: () => {
|
|
@@ -6434,7 +6451,7 @@
|
|
|
6434
6451
|
// Create dropdown with proper positioning
|
|
6435
6452
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
6436
6453
|
tabindex: 0,
|
|
6437
|
-
style: getPortalStyles(state.inputRef),
|
|
6454
|
+
style: Object.assign(Object.assign({}, getPortalStyles(state.inputRef)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
|
|
6438
6455
|
oncreate: ({ dom }) => {
|
|
6439
6456
|
state.dropdownRef = dom;
|
|
6440
6457
|
},
|
|
@@ -6503,7 +6520,8 @@
|
|
|
6503
6520
|
selectedIds = state.internalSelectedIds;
|
|
6504
6521
|
}
|
|
6505
6522
|
const finalClassName = newRow ? `${className} clear` : className;
|
|
6506
|
-
const
|
|
6523
|
+
const selectedOptionsUnsorted = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
6524
|
+
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
6507
6525
|
// Update portal dropdown when inside modal
|
|
6508
6526
|
if (state.isInsideModal) {
|
|
6509
6527
|
updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
|
|
@@ -6548,7 +6566,7 @@
|
|
|
6548
6566
|
onremove: () => {
|
|
6549
6567
|
state.dropdownRef = null;
|
|
6550
6568
|
},
|
|
6551
|
-
style: getDropdownStyles(state.inputRef, true, options),
|
|
6569
|
+
style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef, true, options)), (attrs.maxHeight ? { maxHeight: attrs.maxHeight } : {})),
|
|
6552
6570
|
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder)),
|
|
6553
6571
|
m(MaterialIcon, {
|
|
6554
6572
|
name: 'caret',
|
|
@@ -6798,7 +6816,7 @@
|
|
|
6798
6816
|
]),
|
|
6799
6817
|
};
|
|
6800
6818
|
const DropdownOption = {
|
|
6801
|
-
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver } }) => {
|
|
6819
|
+
view: ({ attrs: { option, index, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox } }) => {
|
|
6802
6820
|
const checkboxId = `search-select-option-${option.id}`;
|
|
6803
6821
|
const optionLabel = option.label || option.id.toString();
|
|
6804
6822
|
return m('li', {
|
|
@@ -6815,11 +6833,12 @@
|
|
|
6815
6833
|
}
|
|
6816
6834
|
},
|
|
6817
6835
|
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6836
|
+
showCheckbox &&
|
|
6837
|
+
m('input', {
|
|
6838
|
+
type: 'checkbox',
|
|
6839
|
+
id: checkboxId,
|
|
6840
|
+
checked: selectedIds.includes(option.id),
|
|
6841
|
+
}),
|
|
6823
6842
|
m('span', optionLabel),
|
|
6824
6843
|
]));
|
|
6825
6844
|
},
|
|
@@ -6837,6 +6856,7 @@
|
|
|
6837
6856
|
dropdownRef: null,
|
|
6838
6857
|
focusedIndex: -1,
|
|
6839
6858
|
internalSelectedIds: [],
|
|
6859
|
+
createdOptions: [],
|
|
6840
6860
|
};
|
|
6841
6861
|
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
6842
6862
|
const componentId = uniqueId();
|
|
@@ -6879,7 +6899,10 @@
|
|
|
6879
6899
|
// Handle add new option
|
|
6880
6900
|
return 'addNew';
|
|
6881
6901
|
}
|
|
6882
|
-
else if (state.focusedIndex < filteredOptions.length)
|
|
6902
|
+
else if (state.focusedIndex < filteredOptions.length) {
|
|
6903
|
+
// This will be handled in the view method where attrs are available
|
|
6904
|
+
return 'selectOption';
|
|
6905
|
+
}
|
|
6883
6906
|
}
|
|
6884
6907
|
break;
|
|
6885
6908
|
case 'Escape':
|
|
@@ -6890,11 +6913,22 @@
|
|
|
6890
6913
|
}
|
|
6891
6914
|
return null;
|
|
6892
6915
|
};
|
|
6916
|
+
// Create new option and add to state
|
|
6917
|
+
const createAndSelectOption = async (attrs) => {
|
|
6918
|
+
if (!attrs.oncreateNewOption || !state.searchTerm)
|
|
6919
|
+
return;
|
|
6920
|
+
const newOption = await attrs.oncreateNewOption(state.searchTerm);
|
|
6921
|
+
// Store the created option internally
|
|
6922
|
+
state.createdOptions.push(newOption);
|
|
6923
|
+
// Select the new option
|
|
6924
|
+
toggleOption(newOption, attrs);
|
|
6925
|
+
};
|
|
6893
6926
|
// Toggle option selection
|
|
6894
6927
|
const toggleOption = (option, attrs) => {
|
|
6895
6928
|
if (option.disabled)
|
|
6896
6929
|
return;
|
|
6897
6930
|
const controlled = isControlled(attrs);
|
|
6931
|
+
const { maxSelectedOptions } = attrs;
|
|
6898
6932
|
// Get current selected IDs from props or internal state
|
|
6899
6933
|
const currentSelectedIds = controlled
|
|
6900
6934
|
? attrs.checkedId !== undefined
|
|
@@ -6903,9 +6937,29 @@
|
|
|
6903
6937
|
: [attrs.checkedId]
|
|
6904
6938
|
: []
|
|
6905
6939
|
: state.internalSelectedIds;
|
|
6906
|
-
const
|
|
6907
|
-
|
|
6908
|
-
|
|
6940
|
+
const isSelected = currentSelectedIds.includes(option.id);
|
|
6941
|
+
let newIds;
|
|
6942
|
+
if (isSelected) {
|
|
6943
|
+
// Remove if already selected
|
|
6944
|
+
newIds = currentSelectedIds.filter((id) => id !== option.id);
|
|
6945
|
+
}
|
|
6946
|
+
else {
|
|
6947
|
+
// Check if we've reached the max selection limit
|
|
6948
|
+
if (maxSelectedOptions && currentSelectedIds.length >= maxSelectedOptions) {
|
|
6949
|
+
// If max=1, replace the selection
|
|
6950
|
+
if (maxSelectedOptions === 1) {
|
|
6951
|
+
newIds = [option.id];
|
|
6952
|
+
}
|
|
6953
|
+
else {
|
|
6954
|
+
// Otherwise, don't add more
|
|
6955
|
+
return;
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
else {
|
|
6959
|
+
// Add to selection
|
|
6960
|
+
newIds = [...currentSelectedIds, option.id];
|
|
6961
|
+
}
|
|
6962
|
+
}
|
|
6909
6963
|
// Update internal state for uncontrolled mode
|
|
6910
6964
|
if (!controlled) {
|
|
6911
6965
|
state.internalSelectedIds = newIds;
|
|
@@ -6967,21 +7021,32 @@
|
|
|
6967
7021
|
: [attrs.checkedId]
|
|
6968
7022
|
: []
|
|
6969
7023
|
: state.internalSelectedIds;
|
|
6970
|
-
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, } = attrs;
|
|
7024
|
+
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
|
|
6971
7025
|
// Use i18n values if provided, otherwise use defaults
|
|
6972
7026
|
const texts = {
|
|
6973
7027
|
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
6974
7028
|
addNewPrefix: i18n.addNewPrefix || '+',
|
|
7029
|
+
showingXofY: i18n.showingXofY || 'Showing {shown} of {total} options',
|
|
7030
|
+
maxSelectionsReached: i18n.maxSelectionsReached || 'Maximum {max} selections reached',
|
|
6975
7031
|
};
|
|
7032
|
+
// Check if max selections is reached
|
|
7033
|
+
const isMaxSelectionsReached = maxSelectedOptions && selectedIds.length >= maxSelectedOptions;
|
|
7034
|
+
// Merge provided options with internally created options
|
|
7035
|
+
const allOptions = [...options, ...state.createdOptions];
|
|
6976
7036
|
// Get selected options for display
|
|
6977
|
-
const
|
|
7037
|
+
const selectedOptionsUnsorted = allOptions.filter((opt) => selectedIds.includes(opt.id));
|
|
7038
|
+
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
6978
7039
|
// Safely filter options
|
|
6979
|
-
const filteredOptions =
|
|
7040
|
+
const filteredOptions = allOptions.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
6980
7041
|
!selectedIds.includes(option.id));
|
|
7042
|
+
// Apply display limit if configured
|
|
7043
|
+
const totalFilteredCount = filteredOptions.length;
|
|
7044
|
+
const displayedOptions = maxDisplayedOptions ? filteredOptions.slice(0, maxDisplayedOptions) : filteredOptions;
|
|
7045
|
+
const isTruncated = maxDisplayedOptions && totalFilteredCount > maxDisplayedOptions;
|
|
6981
7046
|
// Check if we should show the "add new option" element
|
|
6982
7047
|
const showAddNew = oncreateNewOption &&
|
|
6983
7048
|
state.searchTerm &&
|
|
6984
|
-
!
|
|
7049
|
+
!displayedOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
|
|
6985
7050
|
// Render the dropdown
|
|
6986
7051
|
return m('.input-field.multi-select-dropdown', { className }, [
|
|
6987
7052
|
m('.chips.chips-initial.chips-container', {
|
|
@@ -7054,7 +7119,7 @@
|
|
|
7054
7119
|
onremove: () => {
|
|
7055
7120
|
state.dropdownRef = null;
|
|
7056
7121
|
},
|
|
7057
|
-
style: getDropdownStyles(state.inputRef),
|
|
7122
|
+
style: Object.assign(Object.assign({}, getDropdownStyles(state.inputRef)), (maxHeight ? { maxHeight } : {})),
|
|
7058
7123
|
}, [
|
|
7059
7124
|
m('li', // Search Input
|
|
7060
7125
|
{
|
|
@@ -7074,41 +7139,65 @@
|
|
|
7074
7139
|
state.focusedIndex = -1; // Reset focus when typing
|
|
7075
7140
|
},
|
|
7076
7141
|
onkeydown: async (e) => {
|
|
7077
|
-
const result = handleKeyDown(e,
|
|
7142
|
+
const result = handleKeyDown(e, displayedOptions, !!showAddNew);
|
|
7078
7143
|
if (result === 'addNew' && oncreateNewOption) {
|
|
7079
|
-
|
|
7080
|
-
toggleOption(option, attrs);
|
|
7144
|
+
await createAndSelectOption(attrs);
|
|
7081
7145
|
}
|
|
7082
|
-
else if (
|
|
7083
|
-
state.focusedIndex
|
|
7084
|
-
state.focusedIndex < filteredOptions.length) {
|
|
7085
|
-
toggleOption(filteredOptions[state.focusedIndex], attrs);
|
|
7146
|
+
else if (result === 'selectOption' && state.focusedIndex < displayedOptions.length) {
|
|
7147
|
+
toggleOption(displayedOptions[state.focusedIndex], attrs);
|
|
7086
7148
|
}
|
|
7087
7149
|
},
|
|
7088
7150
|
class: 'search-select-input',
|
|
7089
7151
|
}),
|
|
7090
7152
|
]),
|
|
7091
7153
|
// No options found message or list of options
|
|
7092
|
-
...(
|
|
7154
|
+
...(displayedOptions.length === 0 && !showAddNew
|
|
7093
7155
|
? [m('li.search-select-no-options', texts.noOptionsFound)]
|
|
7094
7156
|
: []),
|
|
7157
|
+
// Truncation message
|
|
7158
|
+
...(isTruncated
|
|
7159
|
+
? [
|
|
7160
|
+
m('li.search-select-truncation-info', {
|
|
7161
|
+
style: {
|
|
7162
|
+
fontStyle: 'italic',
|
|
7163
|
+
color: 'var(--mm-text-hint, #9e9e9e)',
|
|
7164
|
+
padding: '8px 16px',
|
|
7165
|
+
cursor: 'default',
|
|
7166
|
+
},
|
|
7167
|
+
}, texts.showingXofY
|
|
7168
|
+
.replace('{shown}', displayedOptions.length.toString())
|
|
7169
|
+
.replace('{total}', totalFilteredCount.toString())),
|
|
7170
|
+
]
|
|
7171
|
+
: []),
|
|
7172
|
+
// Max selections reached message
|
|
7173
|
+
...(isMaxSelectionsReached
|
|
7174
|
+
? [
|
|
7175
|
+
m('li.search-select-max-info', {
|
|
7176
|
+
style: {
|
|
7177
|
+
fontStyle: 'italic',
|
|
7178
|
+
color: 'var(--mm-text-hint, #9e9e9e)',
|
|
7179
|
+
padding: '8px 16px',
|
|
7180
|
+
cursor: 'default',
|
|
7181
|
+
},
|
|
7182
|
+
}, texts.maxSelectionsReached.replace('{max}', maxSelectedOptions.toString())),
|
|
7183
|
+
]
|
|
7184
|
+
: []),
|
|
7095
7185
|
// Add new option item
|
|
7096
7186
|
...(showAddNew
|
|
7097
7187
|
? [
|
|
7098
7188
|
m('li', {
|
|
7099
7189
|
onclick: async () => {
|
|
7100
|
-
|
|
7101
|
-
toggleOption(option, attrs);
|
|
7190
|
+
await createAndSelectOption(attrs);
|
|
7102
7191
|
},
|
|
7103
|
-
class: state.focusedIndex ===
|
|
7192
|
+
class: state.focusedIndex === displayedOptions.length ? 'active' : '',
|
|
7104
7193
|
onmouseover: () => {
|
|
7105
|
-
state.focusedIndex =
|
|
7194
|
+
state.focusedIndex = displayedOptions.length;
|
|
7106
7195
|
},
|
|
7107
7196
|
}, [m('span', `${texts.addNewPrefix} "${state.searchTerm}"`)]),
|
|
7108
7197
|
]
|
|
7109
7198
|
: []),
|
|
7110
7199
|
// List of filtered options
|
|
7111
|
-
...
|
|
7200
|
+
...displayedOptions.map((option, index) => m(DropdownOption, {
|
|
7112
7201
|
// key: option.id,
|
|
7113
7202
|
option,
|
|
7114
7203
|
index,
|
|
@@ -7118,6 +7207,7 @@
|
|
|
7118
7207
|
onMouseOver: (idx) => {
|
|
7119
7208
|
state.focusedIndex = idx;
|
|
7120
7209
|
},
|
|
7210
|
+
showCheckbox: maxSelectedOptions !== 1,
|
|
7121
7211
|
})),
|
|
7122
7212
|
]),
|
|
7123
7213
|
]);
|
|
@@ -9817,6 +9907,7 @@
|
|
|
9817
9907
|
exports.range = range;
|
|
9818
9908
|
exports.releasePortalContainer = releasePortalContainer;
|
|
9819
9909
|
exports.renderToPortal = renderToPortal;
|
|
9910
|
+
exports.sortOptions = sortOptions;
|
|
9820
9911
|
exports.toast = toast;
|
|
9821
9912
|
exports.uniqueId = uniqueId;
|
|
9822
9913
|
exports.uuid4 = uuid4;
|