mithril-materialized 2.0.0-beta.8 → 2.0.0-rc.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/README.md +353 -10
- package/dist/advanced.css +6 -6
- package/dist/button.d.ts +56 -11
- package/dist/components.css +1674 -6
- package/dist/core.css +13 -13
- package/dist/datatable.d.ts +291 -0
- package/dist/datepicker.d.ts +12 -2
- package/dist/forms.css +344 -13
- package/dist/icon.d.ts +2 -2
- package/dist/image-list.d.ts +70 -0
- package/dist/index.css +1940 -20
- package/dist/index.d.ts +8 -0
- package/dist/index.esm.js +2700 -639
- package/dist/index.js +2710 -638
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +2710 -638
- package/dist/input-options.d.ts +18 -4
- package/dist/input.d.ts +0 -1
- package/dist/label.d.ts +2 -0
- package/dist/masonry.d.ts +17 -0
- package/dist/material-icon.d.ts +3 -0
- package/dist/pickers.css +45 -0
- package/dist/range-slider.d.ts +42 -0
- package/dist/timeline.d.ts +43 -0
- package/dist/treeview.d.ts +39 -0
- package/dist/types.d.ts +226 -0
- package/dist/utilities.css +16 -9
- package/package.json +12 -9
- package/sass/components/_cards.scss +10 -3
- package/sass/components/_datatable.scss +417 -0
- package/sass/components/_datepicker.scss +57 -0
- package/sass/components/_global.scss +6 -6
- package/sass/components/_image-list.scss +421 -0
- package/sass/components/_masonry.scss +241 -0
- package/sass/components/_timeline.scss +452 -0
- package/sass/components/_treeview.scss +353 -0
- package/sass/components/forms/_forms.scss +1 -1
- package/sass/components/forms/_range-enhanced.scss +406 -0
- package/sass/components/forms/_range.scss +5 -5
- package/sass/components/forms/_select.scss +1 -1
- package/sass/materialize.scss +6 -0
package/dist/index.js
CHANGED
|
@@ -168,9 +168,14 @@ const Mandatory = { view: ({ attrs }) => m('span.mandatory', attrs, '*') };
|
|
|
168
168
|
const Label = () => {
|
|
169
169
|
return {
|
|
170
170
|
view: (_a) => {
|
|
171
|
-
var _b = _a.attrs, { label, id, isMandatory, isActive, className } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className"]);
|
|
171
|
+
var _b = _a.attrs, { label, id, isMandatory, isActive, className, initialValue } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className", "initialValue"]);
|
|
172
172
|
return label
|
|
173
|
-
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id
|
|
173
|
+
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id, oncreate: ({ dom }) => {
|
|
174
|
+
if (!initialValue)
|
|
175
|
+
return;
|
|
176
|
+
const labelEl = dom;
|
|
177
|
+
labelEl.classList.add('active');
|
|
178
|
+
} }), [m.trust(label), isMandatory ? m(Mandatory) : undefined])
|
|
174
179
|
: undefined;
|
|
175
180
|
},
|
|
176
181
|
};
|
|
@@ -413,12 +418,18 @@ const ButtonFactory = (element, defaultClassNames, type = '') => {
|
|
|
413
418
|
return () => {
|
|
414
419
|
return {
|
|
415
420
|
view: ({ attrs }) => {
|
|
416
|
-
const { modalId, tooltip,
|
|
417
|
-
|
|
421
|
+
const { modalId, tooltip, tooltipPosition, tooltipPostion, // Keep for backwards compatibility
|
|
422
|
+
iconName, iconClass, label, className, attr, variant } = attrs, params = __rest(attrs, ["modalId", "tooltip", "tooltipPosition", "tooltipPostion", "iconName", "iconClass", "label", "className", "attr", "variant"]);
|
|
423
|
+
// Handle both new variant prop and legacy modalId/type
|
|
424
|
+
const buttonType = (variant === null || variant === void 0 ? void 0 : variant.type) || (modalId ? 'modal' : type || 'button');
|
|
425
|
+
const modalTarget = (variant === null || variant === void 0 ? void 0 : variant.type) === 'modal' ? variant.modalId : modalId;
|
|
426
|
+
const cn = [modalTarget ? 'modal-trigger' : '', tooltip ? 'tooltipped' : '', defaultClassNames, className]
|
|
418
427
|
.filter(Boolean)
|
|
419
428
|
.join(' ')
|
|
420
429
|
.trim();
|
|
421
|
-
|
|
430
|
+
// Use tooltipPosition if available, fallback to legacy tooltipPostion
|
|
431
|
+
const position = tooltipPosition || tooltipPostion || 'top';
|
|
432
|
+
return m(element, Object.assign(Object.assign(Object.assign({}, params), attr), { className: cn, href: modalTarget ? `#${modalTarget}` : undefined, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType === 'modal' ? 'button' : buttonType }),
|
|
422
433
|
// `${dca}${modalId ? `.modal-trigger[href=#${modalId}]` : ''}${
|
|
423
434
|
// tooltip ? `.tooltipped[data-position=${tooltipPostion || 'top'}][data-tooltip=${tooltip}]` : ''
|
|
424
435
|
// }${toAttributeString(attr)}`, {}
|
|
@@ -827,6 +838,18 @@ const iconPaths = {
|
|
|
827
838
|
'M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7A1 1 0 0 0 5.7 7.11L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z',
|
|
828
839
|
'M0 0h24v24H0z',
|
|
829
840
|
],
|
|
841
|
+
chevron: [
|
|
842
|
+
'M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z', // chevron down
|
|
843
|
+
'M0 0h24v24H0z', // background
|
|
844
|
+
],
|
|
845
|
+
expand: [
|
|
846
|
+
'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z', // plus
|
|
847
|
+
'M0 0h24v24H0z', // background
|
|
848
|
+
],
|
|
849
|
+
collapse: [
|
|
850
|
+
'M19 13H5v-2h14v2z', // minus
|
|
851
|
+
'M0 0h24v24H0z', // background
|
|
852
|
+
],
|
|
830
853
|
};
|
|
831
854
|
const MaterialIcon = () => {
|
|
832
855
|
return {
|
|
@@ -836,8 +859,8 @@ const MaterialIcon = () => {
|
|
|
836
859
|
const rotationMap = {
|
|
837
860
|
down: 0,
|
|
838
861
|
up: 180,
|
|
839
|
-
left:
|
|
840
|
-
right: 90,
|
|
862
|
+
left: 90,
|
|
863
|
+
right: -90,
|
|
841
864
|
};
|
|
842
865
|
const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
|
|
843
866
|
const transform = rotation ? `rotate(${rotation}deg)` : undefined;
|
|
@@ -1146,7 +1169,7 @@ const CodeBlock = () => ({
|
|
|
1146
1169
|
const lang = language || 'lang-TypeScript';
|
|
1147
1170
|
const label = lang.replace('lang-', '');
|
|
1148
1171
|
const cb = code instanceof Array ? code.join('\n') : code;
|
|
1149
|
-
const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim();
|
|
1172
|
+
const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim() || undefined;
|
|
1150
1173
|
return m(`pre.codeblock${newRow ? '.clear' : ''}`, attrs, [
|
|
1151
1174
|
m('div', m('label', label)),
|
|
1152
1175
|
m('code', Object.assign(Object.assign({}, params), { className: cn }), cb),
|
|
@@ -1458,6 +1481,42 @@ const DatePicker = () => {
|
|
|
1458
1481
|
});
|
|
1459
1482
|
return result;
|
|
1460
1483
|
};
|
|
1484
|
+
// New function to format any specific date (not just state.date)
|
|
1485
|
+
const formatDate = (date, format, options) => {
|
|
1486
|
+
if (!date || !isDate(date)) {
|
|
1487
|
+
return '';
|
|
1488
|
+
}
|
|
1489
|
+
// Split format into tokens - match longer patterns first
|
|
1490
|
+
const formatTokens = /(dddd|ddd|dd|d|mmmm|mmm|mm|m|yyyy|yy)/g;
|
|
1491
|
+
let result = format;
|
|
1492
|
+
// Create temporary formats for the specific date
|
|
1493
|
+
const dateFormats = {
|
|
1494
|
+
d: () => date.getDate(),
|
|
1495
|
+
dd: () => {
|
|
1496
|
+
const d = date.getDate();
|
|
1497
|
+
return (d < 10 ? '0' : '') + d;
|
|
1498
|
+
},
|
|
1499
|
+
ddd: () => options.i18n.weekdaysShort[date.getDay()],
|
|
1500
|
+
dddd: () => options.i18n.weekdays[date.getDay()],
|
|
1501
|
+
m: () => date.getMonth() + 1,
|
|
1502
|
+
mm: () => {
|
|
1503
|
+
const m = date.getMonth() + 1;
|
|
1504
|
+
return (m < 10 ? '0' : '') + m;
|
|
1505
|
+
},
|
|
1506
|
+
mmm: () => options.i18n.monthsShort[date.getMonth()],
|
|
1507
|
+
mmmm: () => options.i18n.months[date.getMonth()],
|
|
1508
|
+
yy: () => ('' + date.getFullYear()).slice(2),
|
|
1509
|
+
yyyy: () => date.getFullYear(),
|
|
1510
|
+
};
|
|
1511
|
+
// Replace all format tokens with actual values
|
|
1512
|
+
result = result.replace(formatTokens, (match) => {
|
|
1513
|
+
if (dateFormats[match]) {
|
|
1514
|
+
return String(dateFormats[match]());
|
|
1515
|
+
}
|
|
1516
|
+
return match;
|
|
1517
|
+
});
|
|
1518
|
+
return result;
|
|
1519
|
+
};
|
|
1461
1520
|
const setDate = (date, preventOnSelect = false, options) => {
|
|
1462
1521
|
if (!date) {
|
|
1463
1522
|
state.date = null;
|
|
@@ -1484,6 +1543,55 @@ const DatePicker = () => {
|
|
|
1484
1543
|
options.onSelect(state.date);
|
|
1485
1544
|
}
|
|
1486
1545
|
};
|
|
1546
|
+
const handleRangeSelection = (date, options) => {
|
|
1547
|
+
setToStartOfDay(date);
|
|
1548
|
+
// First click or reset - set start date
|
|
1549
|
+
if (!state.startDate || (state.startDate && state.endDate)) {
|
|
1550
|
+
state.startDate = new Date(date.getTime());
|
|
1551
|
+
state.endDate = null;
|
|
1552
|
+
state.selectionMode = 'end';
|
|
1553
|
+
state.isSelectingRange = true;
|
|
1554
|
+
}
|
|
1555
|
+
// Second click - set end date
|
|
1556
|
+
else if (state.startDate && !state.endDate) {
|
|
1557
|
+
// Ensure proper order (start <= end)
|
|
1558
|
+
if (date < state.startDate) {
|
|
1559
|
+
state.endDate = new Date(state.startDate.getTime());
|
|
1560
|
+
state.startDate = new Date(date.getTime());
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
state.endDate = new Date(date.getTime());
|
|
1564
|
+
}
|
|
1565
|
+
// Validate range constraints
|
|
1566
|
+
if (options.minDateRange || options.maxDateRange) {
|
|
1567
|
+
const daysDiff = Math.ceil((state.endDate.getTime() - state.startDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
1568
|
+
if (options.minDateRange && daysDiff < options.minDateRange) {
|
|
1569
|
+
// Range too short, reset
|
|
1570
|
+
state.startDate = new Date(date.getTime());
|
|
1571
|
+
state.endDate = null;
|
|
1572
|
+
state.selectionMode = 'end';
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
if (options.maxDateRange && daysDiff > options.maxDateRange) {
|
|
1576
|
+
// Range too long, reset
|
|
1577
|
+
state.startDate = new Date(date.getTime());
|
|
1578
|
+
state.endDate = null;
|
|
1579
|
+
state.selectionMode = 'end';
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
state.selectionMode = null;
|
|
1584
|
+
state.isSelectingRange = false;
|
|
1585
|
+
// Call onSelect with both dates
|
|
1586
|
+
if (options.onSelect) {
|
|
1587
|
+
options.onSelect(state.startDate, state.endDate);
|
|
1588
|
+
}
|
|
1589
|
+
// Auto-close if enabled
|
|
1590
|
+
if (options.autoClose) {
|
|
1591
|
+
state.isOpen = false;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1487
1595
|
const gotoDate = (date) => {
|
|
1488
1596
|
if (!isDate(date)) {
|
|
1489
1597
|
return;
|
|
@@ -1542,6 +1650,21 @@ const DatePicker = () => {
|
|
|
1542
1650
|
arr.push('is-selected');
|
|
1543
1651
|
ariaSelected = 'true';
|
|
1544
1652
|
}
|
|
1653
|
+
// Range selection states
|
|
1654
|
+
if (opts.isRangeStart) {
|
|
1655
|
+
arr.push('is-range-start');
|
|
1656
|
+
ariaSelected = 'true';
|
|
1657
|
+
}
|
|
1658
|
+
if (opts.isRangeEnd) {
|
|
1659
|
+
arr.push('is-range-end');
|
|
1660
|
+
ariaSelected = 'true';
|
|
1661
|
+
}
|
|
1662
|
+
if (opts.isInRange) {
|
|
1663
|
+
arr.push('is-in-range');
|
|
1664
|
+
}
|
|
1665
|
+
if (opts.isRangePreview) {
|
|
1666
|
+
arr.push('is-range-preview');
|
|
1667
|
+
}
|
|
1545
1668
|
if (opts.hasEvent) {
|
|
1546
1669
|
arr.push('has-event');
|
|
1547
1670
|
}
|
|
@@ -1562,9 +1685,14 @@ const DatePicker = () => {
|
|
|
1562
1685
|
const month = parseInt(target.getAttribute('data-month') || '0', 10);
|
|
1563
1686
|
const day = parseInt(target.getAttribute('data-day') || '0', 10);
|
|
1564
1687
|
const selectedDate = new Date(year, month, day);
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1688
|
+
if (options.dateRange) {
|
|
1689
|
+
handleRangeSelection(selectedDate, options);
|
|
1690
|
+
}
|
|
1691
|
+
else {
|
|
1692
|
+
setDate(selectedDate, false, options);
|
|
1693
|
+
if (options.autoClose) {
|
|
1694
|
+
state.isOpen = false;
|
|
1695
|
+
}
|
|
1568
1696
|
}
|
|
1569
1697
|
}
|
|
1570
1698
|
},
|
|
@@ -1624,16 +1752,37 @@ const DatePicker = () => {
|
|
|
1624
1752
|
(options.maxDate && day > options.maxDate) ||
|
|
1625
1753
|
(options.disableWeekends && isWeekend(day)) ||
|
|
1626
1754
|
(options.disableDayFn && options.disableDayFn(day));
|
|
1755
|
+
// Range selection states
|
|
1756
|
+
let isRangeStart = false;
|
|
1757
|
+
let isRangeEnd = false;
|
|
1758
|
+
let isInRange = false;
|
|
1759
|
+
let isRangePreview = false;
|
|
1760
|
+
if (options.dateRange) {
|
|
1761
|
+
if (state.startDate && compareDates(day, state.startDate)) {
|
|
1762
|
+
isRangeStart = true;
|
|
1763
|
+
}
|
|
1764
|
+
if (state.endDate && compareDates(day, state.endDate)) {
|
|
1765
|
+
isRangeEnd = true;
|
|
1766
|
+
}
|
|
1767
|
+
if (state.startDate && state.endDate && day > state.startDate && day < state.endDate) {
|
|
1768
|
+
isInRange = true;
|
|
1769
|
+
}
|
|
1770
|
+
// TODO: Add hover preview logic for range selection
|
|
1771
|
+
}
|
|
1627
1772
|
const dayConfig = {
|
|
1628
1773
|
day: dayNumber,
|
|
1629
1774
|
month: monthNumber,
|
|
1630
1775
|
year: yearNumber,
|
|
1631
1776
|
hasEvent: false,
|
|
1632
|
-
isSelected: isSelected,
|
|
1777
|
+
isSelected: !options.dateRange && isSelected, // Only use isSelected in single date mode
|
|
1633
1778
|
isToday: isToday,
|
|
1634
1779
|
isDisabled: isDisabled,
|
|
1635
1780
|
isEmpty: isEmpty,
|
|
1636
1781
|
showDaysInNextAndPreviousMonths: false,
|
|
1782
|
+
isRangeStart: isRangeStart,
|
|
1783
|
+
isRangeEnd: isRangeEnd,
|
|
1784
|
+
isInRange: isInRange,
|
|
1785
|
+
isRangePreview: isRangePreview,
|
|
1637
1786
|
};
|
|
1638
1787
|
// Add week number cell at the beginning of each row
|
|
1639
1788
|
if (r === 0 && options.showWeekNumbers) {
|
|
@@ -1680,14 +1829,58 @@ const DatePicker = () => {
|
|
|
1680
1829
|
return {
|
|
1681
1830
|
view: ({ attrs }) => {
|
|
1682
1831
|
const { options } = attrs;
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1832
|
+
if (options.dateRange) {
|
|
1833
|
+
// Range display
|
|
1834
|
+
const startDate = state.startDate;
|
|
1835
|
+
const endDate = state.endDate;
|
|
1836
|
+
if (startDate && endDate) {
|
|
1837
|
+
// Both dates selected
|
|
1838
|
+
const startDay = options.i18n.weekdaysShort[startDate.getDay()];
|
|
1839
|
+
const startMonth = options.i18n.monthsShort[startDate.getMonth()];
|
|
1840
|
+
const endDay = options.i18n.weekdaysShort[endDate.getDay()];
|
|
1841
|
+
const endMonth = options.i18n.monthsShort[endDate.getMonth()];
|
|
1842
|
+
return m('.datepicker-date-display.range-display', [
|
|
1843
|
+
m('span.year-text', startDate.getFullYear()),
|
|
1844
|
+
m('span.date-text', [
|
|
1845
|
+
m('span.start-date', `${startDay}, ${startMonth} ${startDate.getDate()}`),
|
|
1846
|
+
m('span.range-separator', ' - '),
|
|
1847
|
+
m('span.end-date', `${endDay}, ${endMonth} ${endDate.getDate()}`),
|
|
1848
|
+
]),
|
|
1849
|
+
]);
|
|
1850
|
+
}
|
|
1851
|
+
else if (startDate) {
|
|
1852
|
+
// Only start date selected
|
|
1853
|
+
const startDay = options.i18n.weekdaysShort[startDate.getDay()];
|
|
1854
|
+
const startMonth = options.i18n.monthsShort[startDate.getMonth()];
|
|
1855
|
+
return m('.datepicker-date-display.range-display', [
|
|
1856
|
+
m('span.year-text', startDate.getFullYear()),
|
|
1857
|
+
m('span.date-text', [
|
|
1858
|
+
m('span.start-date', `${startDay}, ${startMonth} ${startDate.getDate()}`),
|
|
1859
|
+
m('span.range-separator', ' - '),
|
|
1860
|
+
m('span.end-date.placeholder', 'Select end date'),
|
|
1861
|
+
]),
|
|
1862
|
+
]);
|
|
1863
|
+
}
|
|
1864
|
+
else {
|
|
1865
|
+
// No dates selected
|
|
1866
|
+
const currentDate = new Date();
|
|
1867
|
+
return m('.datepicker-date-display.range-display', [
|
|
1868
|
+
m('span.year-text', currentDate.getFullYear()),
|
|
1869
|
+
m('span.date-text.placeholder', 'Select date range'),
|
|
1870
|
+
]);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
else {
|
|
1874
|
+
// Single date display (original behavior)
|
|
1875
|
+
const displayDate = isDate(state.date) ? state.date : new Date();
|
|
1876
|
+
const day = options.i18n.weekdaysShort[displayDate.getDay()];
|
|
1877
|
+
const month = options.i18n.monthsShort[displayDate.getMonth()];
|
|
1878
|
+
const date = displayDate.getDate();
|
|
1879
|
+
return m('.datepicker-date-display', [
|
|
1880
|
+
m('span.year-text', displayDate.getFullYear()),
|
|
1881
|
+
m('span.date-text', `${day}, ${month} ${date}`),
|
|
1882
|
+
]);
|
|
1883
|
+
}
|
|
1691
1884
|
}
|
|
1692
1885
|
};
|
|
1693
1886
|
};
|
|
@@ -1836,6 +2029,10 @@ const DatePicker = () => {
|
|
|
1836
2029
|
id: uniqueId(),
|
|
1837
2030
|
isOpen: false,
|
|
1838
2031
|
date: null,
|
|
2032
|
+
startDate: null,
|
|
2033
|
+
endDate: null,
|
|
2034
|
+
selectionMode: null,
|
|
2035
|
+
isSelectingRange: false,
|
|
1839
2036
|
calendars: [{ month: 0, year: 0 }],
|
|
1840
2037
|
monthDropdownOpen: false,
|
|
1841
2038
|
yearDropdownOpen: false,
|
|
@@ -1860,17 +2057,35 @@ const DatePicker = () => {
|
|
|
1860
2057
|
yyyy: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0; },
|
|
1861
2058
|
},
|
|
1862
2059
|
};
|
|
1863
|
-
// Initialize date
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2060
|
+
// Initialize date or date range
|
|
2061
|
+
if (options.dateRange) {
|
|
2062
|
+
// Initialize range dates
|
|
2063
|
+
if (attrs.initialStartDate && isDate(attrs.initialStartDate)) {
|
|
2064
|
+
state.startDate = new Date(attrs.initialStartDate.getTime());
|
|
2065
|
+
setToStartOfDay(state.startDate);
|
|
2066
|
+
gotoDate(state.startDate);
|
|
2067
|
+
}
|
|
2068
|
+
if (attrs.initialEndDate && isDate(attrs.initialEndDate)) {
|
|
2069
|
+
state.endDate = new Date(attrs.initialEndDate.getTime());
|
|
2070
|
+
setToStartOfDay(state.endDate);
|
|
2071
|
+
}
|
|
2072
|
+
if (!state.startDate && !state.endDate) {
|
|
2073
|
+
gotoDate(new Date());
|
|
2074
|
+
}
|
|
1871
2075
|
}
|
|
1872
2076
|
else {
|
|
1873
|
-
|
|
2077
|
+
// Single date initialization (original behavior)
|
|
2078
|
+
let defaultDate = attrs.defaultDate;
|
|
2079
|
+
if (!defaultDate && attrs.initialValue) {
|
|
2080
|
+
defaultDate = new Date(attrs.initialValue);
|
|
2081
|
+
}
|
|
2082
|
+
if (isDate(defaultDate)) {
|
|
2083
|
+
// Always set the date if we have initialValue or defaultDate
|
|
2084
|
+
setDate(defaultDate, true, options);
|
|
2085
|
+
}
|
|
2086
|
+
else {
|
|
2087
|
+
gotoDate(new Date());
|
|
2088
|
+
}
|
|
1874
2089
|
}
|
|
1875
2090
|
// Add document click listener to close dropdowns
|
|
1876
2091
|
document.addEventListener('click', handleDocumentClick);
|
|
@@ -1886,19 +2101,61 @@ const DatePicker = () => {
|
|
|
1886
2101
|
const className = cn1 || cn2 || 'col s12';
|
|
1887
2102
|
// Calculate display value for the input
|
|
1888
2103
|
let displayValue = '';
|
|
1889
|
-
if (
|
|
1890
|
-
|
|
2104
|
+
if (options.dateRange) {
|
|
2105
|
+
// Handle date range display
|
|
2106
|
+
const formatToUse = attrs.displayFormat || options.format;
|
|
2107
|
+
if (state.startDate && state.endDate) {
|
|
2108
|
+
let startStr, endStr;
|
|
2109
|
+
if (attrs.displayFormat) {
|
|
2110
|
+
// Custom display format for date range
|
|
2111
|
+
startStr = attrs.displayFormat
|
|
2112
|
+
.replace(/yyyy/gi, state.startDate.getFullYear().toString())
|
|
2113
|
+
.replace(/mm/gi, (state.startDate.getMonth() + 1).toString().padStart(2, '0'))
|
|
2114
|
+
.replace(/dd/gi, state.startDate.getDate().toString().padStart(2, '0'));
|
|
2115
|
+
endStr = attrs.displayFormat
|
|
2116
|
+
.replace(/yyyy/gi, state.endDate.getFullYear().toString())
|
|
2117
|
+
.replace(/mm/gi, (state.endDate.getMonth() + 1).toString().padStart(2, '0'))
|
|
2118
|
+
.replace(/dd/gi, state.endDate.getDate().toString().padStart(2, '0'));
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
// Standard format
|
|
2122
|
+
startStr = formatDate(state.startDate, formatToUse, options);
|
|
2123
|
+
endStr = formatDate(state.endDate, formatToUse, options);
|
|
2124
|
+
}
|
|
2125
|
+
displayValue = `${startStr} - ${endStr}`;
|
|
2126
|
+
}
|
|
2127
|
+
else if (state.startDate) {
|
|
2128
|
+
let startStr;
|
|
2129
|
+
if (attrs.displayFormat) {
|
|
2130
|
+
// Custom display format for single date
|
|
2131
|
+
startStr = attrs.displayFormat
|
|
2132
|
+
.replace(/yyyy/gi, state.startDate.getFullYear().toString())
|
|
2133
|
+
.replace(/mm/gi, (state.startDate.getMonth() + 1).toString().padStart(2, '0'))
|
|
2134
|
+
.replace(/dd/gi, state.startDate.getDate().toString().padStart(2, '0'));
|
|
2135
|
+
}
|
|
2136
|
+
else {
|
|
2137
|
+
// Standard format
|
|
2138
|
+
startStr = formatDate(state.startDate, formatToUse, options);
|
|
2139
|
+
}
|
|
2140
|
+
displayValue = `${startStr} - `;
|
|
2141
|
+
}
|
|
1891
2142
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
// const formatRegex = /(yyyy|mm|dd)/gi;
|
|
1895
|
-
let customDisplayValue = attrs.displayFormat;
|
|
2143
|
+
else {
|
|
2144
|
+
// Single date display (original behavior)
|
|
1896
2145
|
if (state.date) {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2146
|
+
displayValue = toString(state.date, options.format);
|
|
2147
|
+
}
|
|
2148
|
+
// Custom date format handling
|
|
2149
|
+
if (attrs.displayFormat) {
|
|
2150
|
+
// const formatRegex = /(yyyy|mm|dd)/gi;
|
|
2151
|
+
let customDisplayValue = attrs.displayFormat;
|
|
2152
|
+
if (state.date) {
|
|
2153
|
+
customDisplayValue = customDisplayValue
|
|
2154
|
+
.replace(/yyyy/gi, state.date.getFullYear().toString())
|
|
2155
|
+
.replace(/mm/gi, (state.date.getMonth() + 1).toString().padStart(2, '0'))
|
|
2156
|
+
.replace(/dd/gi, state.date.getDate().toString().padStart(2, '0'));
|
|
2157
|
+
displayValue = customDisplayValue;
|
|
2158
|
+
}
|
|
1902
2159
|
}
|
|
1903
2160
|
}
|
|
1904
2161
|
return m('.input-field', {
|
|
@@ -2002,8 +2259,19 @@ const DatePicker = () => {
|
|
|
2002
2259
|
type: 'button',
|
|
2003
2260
|
onclick: () => {
|
|
2004
2261
|
state.isOpen = false;
|
|
2005
|
-
if (
|
|
2006
|
-
|
|
2262
|
+
if (options.dateRange) {
|
|
2263
|
+
// Range mode
|
|
2264
|
+
if (state.startDate && state.endDate && onchange) {
|
|
2265
|
+
const startStr = toString(state.startDate, 'yyyy-mm-dd');
|
|
2266
|
+
const endStr = toString(state.endDate, 'yyyy-mm-dd');
|
|
2267
|
+
onchange(`${startStr} - ${endStr}`);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
else {
|
|
2271
|
+
// Single date mode
|
|
2272
|
+
if (state.date && onchange) {
|
|
2273
|
+
onchange(toString(state.date, 'yyyy-mm-dd')); // Always return ISO format
|
|
2274
|
+
}
|
|
2007
2275
|
}
|
|
2008
2276
|
if (options.onClose)
|
|
2009
2277
|
options.onClose();
|
|
@@ -2032,246 +2300,458 @@ const DatePicker = () => {
|
|
|
2032
2300
|
};
|
|
2033
2301
|
};
|
|
2034
2302
|
|
|
2035
|
-
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2303
|
+
// Tooltip component for range sliders
|
|
2304
|
+
const RangeTooltip = {
|
|
2305
|
+
view: ({ attrs: { show, position, value } }) => {
|
|
2306
|
+
return show ? m(`.value-tooltip.${position}`, value.toFixed(0)) : null;
|
|
2307
|
+
},
|
|
2308
|
+
};
|
|
2309
|
+
const DoubleRangeTooltip = {
|
|
2310
|
+
view: ({ attrs: { show, orientation, value } }) => {
|
|
2311
|
+
return show ? m(`.value.${orientation}`, value.toFixed(0)) : null;
|
|
2312
|
+
},
|
|
2313
|
+
};
|
|
2314
|
+
// Utility functions
|
|
2315
|
+
const getPercentage = (value, min, max) => {
|
|
2316
|
+
return ((value - min) / (max - min)) * 100;
|
|
2317
|
+
};
|
|
2318
|
+
const positionToValue = (e, rect, min, max, step, vertical) => {
|
|
2319
|
+
let percentage;
|
|
2320
|
+
if (vertical) {
|
|
2321
|
+
percentage = ((rect.bottom - e.clientY) / rect.height) * 100;
|
|
2322
|
+
}
|
|
2323
|
+
else {
|
|
2324
|
+
percentage = ((e.clientX - rect.left) / rect.width) * 100;
|
|
2325
|
+
}
|
|
2326
|
+
percentage = Math.max(0, Math.min(100, percentage));
|
|
2327
|
+
const value = min + (percentage / 100) * (max - min);
|
|
2328
|
+
return Math.round(value / step) * step;
|
|
2329
|
+
};
|
|
2330
|
+
const handleKeyboardNavigation = (key, currentValue, min, max, step) => {
|
|
2331
|
+
const largeStep = step * 10;
|
|
2332
|
+
switch (key) {
|
|
2333
|
+
case 'ArrowLeft':
|
|
2334
|
+
case 'ArrowDown':
|
|
2335
|
+
return Math.max(min, currentValue - step);
|
|
2336
|
+
case 'ArrowRight':
|
|
2337
|
+
case 'ArrowUp':
|
|
2338
|
+
return Math.min(max, currentValue + step);
|
|
2339
|
+
case 'PageDown':
|
|
2340
|
+
return Math.max(min, currentValue - largeStep);
|
|
2341
|
+
case 'PageUp':
|
|
2342
|
+
return Math.min(max, currentValue + largeStep);
|
|
2343
|
+
case 'Home':
|
|
2344
|
+
return min;
|
|
2345
|
+
case 'End':
|
|
2346
|
+
return max;
|
|
2347
|
+
default:
|
|
2348
|
+
return null;
|
|
2349
|
+
}
|
|
2350
|
+
};
|
|
2351
|
+
const initRangeState = (state, attrs) => {
|
|
2352
|
+
const { min = 0, max = 100, initialValue, minValue, maxValue } = attrs;
|
|
2353
|
+
// Initialize single range value
|
|
2354
|
+
if (initialValue !== undefined) {
|
|
2355
|
+
const currentValue = initialValue;
|
|
2356
|
+
if (state.singleValue === undefined) {
|
|
2357
|
+
state.singleValue = currentValue;
|
|
2358
|
+
}
|
|
2359
|
+
if (state.lastInitialValue !== initialValue && !state.hasUserInteracted) {
|
|
2360
|
+
state.singleValue = initialValue;
|
|
2361
|
+
state.lastInitialValue = initialValue;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
else if (state.singleValue === undefined) {
|
|
2365
|
+
state.singleValue = min;
|
|
2366
|
+
}
|
|
2367
|
+
// Initialize range values
|
|
2368
|
+
const currentMinValue = minValue !== undefined ? minValue : min;
|
|
2369
|
+
const currentMaxValue = maxValue !== undefined ? maxValue : max;
|
|
2370
|
+
if (state.rangeMinValue === undefined || state.rangeMaxValue === undefined) {
|
|
2371
|
+
state.rangeMinValue = currentMinValue;
|
|
2372
|
+
state.rangeMaxValue = currentMaxValue;
|
|
2373
|
+
}
|
|
2374
|
+
if (!state.hasUserInteracted &&
|
|
2375
|
+
((minValue !== undefined && state.lastMinValue !== minValue) ||
|
|
2376
|
+
(maxValue !== undefined && state.lastMaxValue !== maxValue))) {
|
|
2377
|
+
state.rangeMinValue = currentMinValue;
|
|
2378
|
+
state.rangeMaxValue = currentMaxValue;
|
|
2379
|
+
state.lastMinValue = minValue;
|
|
2380
|
+
state.lastMaxValue = maxValue;
|
|
2381
|
+
}
|
|
2382
|
+
// Initialize active thumb if not set
|
|
2383
|
+
if (state.activeThumb === null) {
|
|
2384
|
+
state.activeThumb = 'min';
|
|
2385
|
+
}
|
|
2386
|
+
// Initialize dragging state
|
|
2387
|
+
if (state.isDragging === undefined) {
|
|
2388
|
+
state.isDragging = false;
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
const updateRangeValues = (minValue, maxValue, attrs, state, immediate) => {
|
|
2392
|
+
// Ensure min doesn't exceed max and vice versa
|
|
2393
|
+
if (minValue > maxValue)
|
|
2394
|
+
minValue = maxValue;
|
|
2395
|
+
if (maxValue < minValue)
|
|
2396
|
+
maxValue = minValue;
|
|
2397
|
+
state.rangeMinValue = minValue;
|
|
2398
|
+
state.rangeMaxValue = maxValue;
|
|
2399
|
+
state.hasUserInteracted = true;
|
|
2400
|
+
// Call oninput for immediate feedback or onchange for final changes
|
|
2401
|
+
if (immediate && attrs.oninput) {
|
|
2402
|
+
attrs.oninput(minValue, maxValue);
|
|
2403
|
+
}
|
|
2404
|
+
else if (!immediate && attrs.onchange) {
|
|
2405
|
+
attrs.onchange(minValue, maxValue);
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
// Single Range Slider Component
|
|
2409
|
+
const SingleRangeSlider = {
|
|
2410
|
+
oninit({ state }) {
|
|
2411
|
+
if (!state.componentInitialized) {
|
|
2412
|
+
state.componentInitialized = true;
|
|
2413
|
+
}
|
|
2414
|
+
},
|
|
2415
|
+
onremove({ state }) {
|
|
2416
|
+
if (state.cleanupMouseEvents) {
|
|
2417
|
+
state.cleanupMouseEvents();
|
|
2418
|
+
state.cleanupMouseEvents = null;
|
|
2419
|
+
}
|
|
2420
|
+
},
|
|
2421
|
+
view({ attrs, state, }) {
|
|
2422
|
+
const { cn, style, iconName, id, label, isMandatory, helperText, min = 0, max = 100, step = 1, vertical = false, showValue = false, valueDisplay, height = '200px', disabled = false, tooltipPos = 'top', oninput, onchange, } = attrs;
|
|
2423
|
+
// Apply fallback logic for valueDisplay if not explicitly set
|
|
2424
|
+
const finalValueDisplay = valueDisplay || (showValue ? 'always' : 'none');
|
|
2425
|
+
// Initialize state
|
|
2426
|
+
initRangeState(state, attrs);
|
|
2427
|
+
const percentage = getPercentage(state.singleValue, min, max);
|
|
2428
|
+
const containerStyle = vertical ? { height } : {};
|
|
2429
|
+
const orientation = vertical ? 'vertical' : 'horizontal';
|
|
2430
|
+
const progressStyle = vertical ? { height: `${percentage}%` } : { width: `${percentage}%` };
|
|
2431
|
+
const thumbStyle = vertical ? { bottom: `${percentage}%` } : { left: `${percentage}%` };
|
|
2432
|
+
// Determine tooltip position for vertical sliders
|
|
2433
|
+
const tooltipPosition = vertical
|
|
2434
|
+
? tooltipPos === 'top' || tooltipPos === 'bottom'
|
|
2435
|
+
? 'right'
|
|
2436
|
+
: tooltipPos
|
|
2437
|
+
: tooltipPos;
|
|
2438
|
+
const updateSingleValue = (newValue, immediate = false) => {
|
|
2439
|
+
state.singleValue = newValue;
|
|
2440
|
+
state.hasUserInteracted = true;
|
|
2441
|
+
if (immediate && oninput) {
|
|
2442
|
+
oninput(newValue);
|
|
2443
|
+
}
|
|
2444
|
+
else if (!immediate && onchange) {
|
|
2445
|
+
onchange(newValue);
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
const handleMouseDown = (e) => {
|
|
2449
|
+
if (disabled)
|
|
2450
|
+
return;
|
|
2451
|
+
e.preventDefault();
|
|
2452
|
+
state.isDragging = true;
|
|
2453
|
+
if (finalValueDisplay === 'auto') {
|
|
2454
|
+
m.redraw();
|
|
2455
|
+
}
|
|
2456
|
+
const thumbElement = e.currentTarget;
|
|
2457
|
+
const container = thumbElement.parentElement;
|
|
2458
|
+
if (!container)
|
|
2459
|
+
return;
|
|
2460
|
+
const handleMouseMove = (e) => {
|
|
2461
|
+
if (!state.isDragging || !container)
|
|
2462
|
+
return;
|
|
2463
|
+
const rect = container.getBoundingClientRect();
|
|
2464
|
+
const value = positionToValue(e, rect, min, max, step, vertical);
|
|
2465
|
+
updateSingleValue(value, true);
|
|
2466
|
+
m.redraw();
|
|
2467
|
+
};
|
|
2468
|
+
const handleMouseUp = () => {
|
|
2469
|
+
state.isDragging = false;
|
|
2470
|
+
if (state.cleanupMouseEvents) {
|
|
2471
|
+
state.cleanupMouseEvents();
|
|
2472
|
+
state.cleanupMouseEvents = null;
|
|
2053
2473
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2474
|
+
updateSingleValue(state.singleValue, false);
|
|
2475
|
+
if (finalValueDisplay === 'auto') {
|
|
2476
|
+
m.redraw();
|
|
2056
2477
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2478
|
+
};
|
|
2479
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
2480
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
2481
|
+
state.cleanupMouseEvents = () => {
|
|
2482
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
2483
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
2484
|
+
};
|
|
2485
|
+
};
|
|
2486
|
+
const fieldClass = vertical ? 'range-field vertical' : 'range-field';
|
|
2487
|
+
return m('.input-field', { className: cn, style }, [
|
|
2488
|
+
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2489
|
+
m('input[type=range]', {
|
|
2490
|
+
id,
|
|
2491
|
+
value: state.singleValue,
|
|
2492
|
+
min,
|
|
2493
|
+
max,
|
|
2494
|
+
step,
|
|
2495
|
+
style: { display: 'none' },
|
|
2496
|
+
disabled,
|
|
2497
|
+
tabindex: -1,
|
|
2498
|
+
}),
|
|
2499
|
+
m('div', { class: fieldClass, style: containerStyle }, [
|
|
2500
|
+
m(`.single-range-slider.${orientation}`, {
|
|
2501
|
+
tabindex: disabled ? -1 : 0,
|
|
2502
|
+
role: 'slider',
|
|
2503
|
+
'aria-valuemin': min,
|
|
2504
|
+
'aria-valuemax': max,
|
|
2505
|
+
'aria-valuenow': state.singleValue,
|
|
2506
|
+
'aria-label': label || 'Range slider',
|
|
2507
|
+
onclick: (e) => {
|
|
2508
|
+
if (disabled)
|
|
2509
|
+
return;
|
|
2510
|
+
const container = e.currentTarget;
|
|
2511
|
+
const rect = container.getBoundingClientRect();
|
|
2512
|
+
const value = positionToValue(e, rect, min, max, step, vertical);
|
|
2513
|
+
updateSingleValue(value, false);
|
|
2514
|
+
e.currentTarget.focus();
|
|
2515
|
+
},
|
|
2516
|
+
onkeydown: (e) => {
|
|
2517
|
+
if (disabled)
|
|
2518
|
+
return;
|
|
2519
|
+
const currentValue = state.singleValue;
|
|
2520
|
+
const newValue = handleKeyboardNavigation(e.key, currentValue, min, max, step);
|
|
2521
|
+
if (newValue !== null) {
|
|
2522
|
+
e.preventDefault();
|
|
2523
|
+
updateSingleValue(newValue, false);
|
|
2524
|
+
}
|
|
2525
|
+
},
|
|
2526
|
+
}, [
|
|
2527
|
+
m(`.track.${orientation}`),
|
|
2528
|
+
m(`.range-progress.${orientation}`, { style: progressStyle }),
|
|
2529
|
+
m(`.thumb.${orientation}`, { style: thumbStyle, onmousedown: handleMouseDown }, m(RangeTooltip, {
|
|
2530
|
+
value: state.singleValue,
|
|
2531
|
+
position: tooltipPosition,
|
|
2532
|
+
show: finalValueDisplay === 'always' || (finalValueDisplay === 'auto' && state.isDragging),
|
|
2533
|
+
})),
|
|
2534
|
+
]),
|
|
2535
|
+
]),
|
|
2536
|
+
label ? m(Label, { label, id, isMandatory, isActive: true }) : null,
|
|
2537
|
+
helperText ? m(HelperText, { helperText }) : null,
|
|
2538
|
+
]);
|
|
2539
|
+
},
|
|
2540
|
+
};
|
|
2541
|
+
// Double Range Slider Component
|
|
2542
|
+
const DoubleRangeSlider = {
|
|
2543
|
+
oninit({ state }) {
|
|
2544
|
+
if (!state.componentInitialized) {
|
|
2545
|
+
state.componentInitialized = true;
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2548
|
+
onremove({ state }) {
|
|
2549
|
+
if (state.cleanupMouseEvents) {
|
|
2550
|
+
state.cleanupMouseEvents();
|
|
2551
|
+
state.cleanupMouseEvents = null;
|
|
2552
|
+
}
|
|
2553
|
+
},
|
|
2554
|
+
view({ attrs, state, }) {
|
|
2555
|
+
const { cn, style, iconName, id, label, isMandatory, helperText, min = 0, max = 100, step = 1, vertical = false, showValue = false, valueDisplay, height = '200px', disabled = false, } = attrs;
|
|
2556
|
+
const finalValueDisplay = valueDisplay || (showValue ? 'always' : 'none');
|
|
2557
|
+
initRangeState(state, attrs);
|
|
2558
|
+
const minPercentage = getPercentage(state.rangeMinValue, min, max);
|
|
2559
|
+
const maxPercentage = getPercentage(state.rangeMaxValue, min, max);
|
|
2560
|
+
const containerStyle = vertical ? { height } : {};
|
|
2561
|
+
const orientation = vertical ? 'vertical' : 'horizontal';
|
|
2562
|
+
const rangeStyle = vertical
|
|
2563
|
+
? {
|
|
2564
|
+
bottom: `${minPercentage}%`,
|
|
2565
|
+
height: `${maxPercentage - minPercentage}%`,
|
|
2566
|
+
}
|
|
2567
|
+
: {
|
|
2568
|
+
left: `${minPercentage}%`,
|
|
2569
|
+
width: `${maxPercentage - minPercentage}%`,
|
|
2570
|
+
};
|
|
2571
|
+
const createThumbStyle = (percentage, isActive) => vertical
|
|
2572
|
+
? {
|
|
2573
|
+
bottom: `${percentage}%`,
|
|
2574
|
+
zIndex: isActive ? 10 : 5,
|
|
2575
|
+
}
|
|
2576
|
+
: {
|
|
2577
|
+
left: `${percentage}%`,
|
|
2578
|
+
zIndex: isActive ? 10 : 5,
|
|
2579
|
+
};
|
|
2580
|
+
const handleMouseDown = (thumb) => (e) => {
|
|
2581
|
+
if (disabled)
|
|
2582
|
+
return;
|
|
2583
|
+
e.preventDefault();
|
|
2584
|
+
state.isDragging = true;
|
|
2585
|
+
state.activeThumb = thumb;
|
|
2586
|
+
if (finalValueDisplay === 'auto') {
|
|
2587
|
+
m.redraw();
|
|
2588
|
+
}
|
|
2589
|
+
const thumbElement = e.currentTarget;
|
|
2590
|
+
const container = thumbElement.parentElement;
|
|
2591
|
+
if (!container)
|
|
2592
|
+
return;
|
|
2593
|
+
const handleMouseMove = (e) => {
|
|
2594
|
+
if (!state.isDragging || !container)
|
|
2595
|
+
return;
|
|
2596
|
+
const rect = container.getBoundingClientRect();
|
|
2597
|
+
const steppedValue = positionToValue(e, rect, min, max, step, vertical);
|
|
2598
|
+
if (thumb === 'min') {
|
|
2599
|
+
updateRangeValues(Math.min(steppedValue, state.rangeMaxValue), state.rangeMaxValue, attrs, state, true);
|
|
2062
2600
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
case ' ':
|
|
2066
|
-
e.preventDefault();
|
|
2067
|
-
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
2068
|
-
const selectedItem = availableItems[state.focusedIndex];
|
|
2069
|
-
const value = (selectedItem.id || selectedItem.label);
|
|
2070
|
-
state.initialValue = value;
|
|
2071
|
-
state.isOpen = false;
|
|
2072
|
-
state.focusedIndex = -1;
|
|
2073
|
-
if (onchange)
|
|
2074
|
-
onchange(value);
|
|
2601
|
+
else {
|
|
2602
|
+
updateRangeValues(state.rangeMinValue, Math.max(steppedValue, state.rangeMinValue), attrs, state, true);
|
|
2075
2603
|
}
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2604
|
+
m.redraw();
|
|
2605
|
+
};
|
|
2606
|
+
const handleMouseUp = () => {
|
|
2607
|
+
state.isDragging = false;
|
|
2608
|
+
state.activeThumb = null;
|
|
2609
|
+
if (state.cleanupMouseEvents) {
|
|
2610
|
+
state.cleanupMouseEvents();
|
|
2611
|
+
state.cleanupMouseEvents = null;
|
|
2079
2612
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
},
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2613
|
+
updateRangeValues(state.rangeMinValue, state.rangeMaxValue, attrs, state, false);
|
|
2614
|
+
if (finalValueDisplay === 'auto') {
|
|
2615
|
+
m.redraw();
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
2619
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
2620
|
+
state.cleanupMouseEvents = () => {
|
|
2621
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
2622
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
2623
|
+
};
|
|
2624
|
+
};
|
|
2625
|
+
const fieldClass = vertical ? 'range-field vertical' : 'range-field';
|
|
2626
|
+
return m('.input-field', { className: cn, style }, [
|
|
2627
|
+
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2628
|
+
m('input[type=range]', {
|
|
2629
|
+
id,
|
|
2630
|
+
value: state.rangeMinValue,
|
|
2631
|
+
min,
|
|
2632
|
+
max,
|
|
2633
|
+
step,
|
|
2634
|
+
style: { display: 'none' },
|
|
2635
|
+
disabled,
|
|
2636
|
+
tabindex: -1,
|
|
2637
|
+
}),
|
|
2638
|
+
m('input[type=range]', {
|
|
2639
|
+
id: `${id}_max`,
|
|
2640
|
+
value: state.rangeMaxValue,
|
|
2641
|
+
min,
|
|
2642
|
+
max,
|
|
2643
|
+
step,
|
|
2644
|
+
style: { display: 'none' },
|
|
2645
|
+
disabled,
|
|
2646
|
+
tabindex: -1,
|
|
2647
|
+
}),
|
|
2648
|
+
m(`div`, { className: fieldClass }, [
|
|
2649
|
+
m(`.double-range-slider.${orientation}`, {
|
|
2650
|
+
style: containerStyle,
|
|
2651
|
+
onclick: (e) => {
|
|
2652
|
+
if (disabled)
|
|
2653
|
+
return;
|
|
2654
|
+
const container = e.currentTarget;
|
|
2655
|
+
const rect = container.getBoundingClientRect();
|
|
2656
|
+
const steppedValue = positionToValue(e, rect, min, max, step, vertical);
|
|
2657
|
+
const distToMin = Math.abs(steppedValue - state.rangeMinValue);
|
|
2658
|
+
const distToMax = Math.abs(steppedValue - state.rangeMaxValue);
|
|
2659
|
+
if (distToMin <= distToMax) {
|
|
2660
|
+
updateRangeValues(Math.min(steppedValue, state.rangeMaxValue), state.rangeMaxValue, attrs, state, false);
|
|
2661
|
+
state.activeThumb = 'min';
|
|
2662
|
+
const minThumb = container.querySelector('.thumb.min-thumb');
|
|
2663
|
+
if (minThumb)
|
|
2664
|
+
minThumb.focus();
|
|
2665
|
+
}
|
|
2666
|
+
else {
|
|
2667
|
+
updateRangeValues(state.rangeMinValue, Math.max(steppedValue, state.rangeMinValue), attrs, state, false);
|
|
2668
|
+
state.activeThumb = 'max';
|
|
2669
|
+
const maxThumb = container.querySelector('.thumb.max-thumb');
|
|
2670
|
+
if (maxThumb)
|
|
2671
|
+
maxThumb.focus();
|
|
2672
|
+
}
|
|
2673
|
+
},
|
|
2116
2674
|
}, [
|
|
2117
|
-
m(
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2675
|
+
m(`.track.${orientation}`),
|
|
2676
|
+
m(`.range.${orientation}`, { style: rangeStyle }),
|
|
2677
|
+
// Min thumb
|
|
2678
|
+
m(`.thumb.${orientation}.min-thumb${state.activeThumb === 'min' ? '.active' : ''}`, {
|
|
2679
|
+
style: createThumbStyle(minPercentage, state.activeThumb === 'min'),
|
|
2680
|
+
tabindex: disabled ? -1 : 0,
|
|
2681
|
+
role: 'slider',
|
|
2682
|
+
'aria-valuemin': min,
|
|
2683
|
+
'aria-valuemax': state.rangeMaxValue,
|
|
2684
|
+
'aria-valuenow': state.rangeMinValue,
|
|
2685
|
+
'aria-label': `Minimum value: ${state.rangeMinValue}`,
|
|
2686
|
+
'aria-orientation': vertical ? 'vertical' : 'horizontal',
|
|
2687
|
+
onmousedown: handleMouseDown('min'),
|
|
2123
2688
|
onclick: (e) => {
|
|
2124
|
-
e.preventDefault();
|
|
2125
2689
|
e.stopPropagation();
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
2129
|
-
}
|
|
2690
|
+
state.activeThumb = 'min';
|
|
2691
|
+
e.currentTarget.focus();
|
|
2130
2692
|
},
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
state.
|
|
2143
|
-
},
|
|
2144
|
-
style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
|
|
2145
|
-
// Convert dropdown items to format expected by getDropdownStyles
|
|
2146
|
-
group: undefined }))), true),
|
|
2147
|
-
}, items.map((item, index) => {
|
|
2148
|
-
if (item.divider) {
|
|
2149
|
-
return m('li.divider', {
|
|
2150
|
-
key: `divider-${index}`,
|
|
2151
|
-
});
|
|
2693
|
+
onfocus: () => {
|
|
2694
|
+
state.activeThumb = 'min';
|
|
2695
|
+
},
|
|
2696
|
+
onkeydown: (e) => {
|
|
2697
|
+
if (disabled)
|
|
2698
|
+
return;
|
|
2699
|
+
const currentValue = state.rangeMinValue;
|
|
2700
|
+
const newValue = handleKeyboardNavigation(e.key, currentValue, min, max, step);
|
|
2701
|
+
if (newValue !== null) {
|
|
2702
|
+
e.preventDefault();
|
|
2703
|
+
const constrainedValue = Math.min(newValue, state.rangeMaxValue);
|
|
2704
|
+
updateRangeValues(constrainedValue, state.rangeMaxValue, attrs, state, false);
|
|
2152
2705
|
}
|
|
2153
|
-
const itemIndex = availableItems.indexOf(item);
|
|
2154
|
-
const isFocused = itemIndex === state.focusedIndex;
|
|
2155
|
-
return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
|
|
2156
|
-
item.disabled ? 'disabled' : '',
|
|
2157
|
-
isFocused ? 'focused' : '',
|
|
2158
|
-
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
2159
|
-
]
|
|
2160
|
-
.filter(Boolean)
|
|
2161
|
-
.join(' ') }, (item.disabled
|
|
2162
|
-
? {}
|
|
2163
|
-
: {
|
|
2164
|
-
onclick: (e) => {
|
|
2165
|
-
e.stopPropagation();
|
|
2166
|
-
const value = (item.id || item.label);
|
|
2167
|
-
state.initialValue = value;
|
|
2168
|
-
state.isOpen = false;
|
|
2169
|
-
state.focusedIndex = -1;
|
|
2170
|
-
if (onchange)
|
|
2171
|
-
onchange(value);
|
|
2172
|
-
},
|
|
2173
|
-
})), m('span', {
|
|
2174
|
-
style: {
|
|
2175
|
-
display: 'flex',
|
|
2176
|
-
alignItems: 'center',
|
|
2177
|
-
padding: '14px 16px',
|
|
2178
|
-
},
|
|
2179
|
-
}, [
|
|
2180
|
-
item.iconName
|
|
2181
|
-
? m('i.material-icons', {
|
|
2182
|
-
style: { marginRight: '32px' },
|
|
2183
|
-
}, item.iconName)
|
|
2184
|
-
: undefined,
|
|
2185
|
-
item.label,
|
|
2186
|
-
]));
|
|
2187
|
-
})),
|
|
2188
|
-
m(MaterialIcon, {
|
|
2189
|
-
name: 'caret',
|
|
2190
|
-
direction: 'down',
|
|
2191
|
-
class: 'caret',
|
|
2192
|
-
}),
|
|
2193
|
-
]),
|
|
2194
|
-
]);
|
|
2195
|
-
},
|
|
2196
|
-
};
|
|
2197
|
-
};
|
|
2198
|
-
|
|
2199
|
-
/**
|
|
2200
|
-
* Floating Action Button
|
|
2201
|
-
*/
|
|
2202
|
-
const FloatingActionButton = () => {
|
|
2203
|
-
const state = {
|
|
2204
|
-
isOpen: false,
|
|
2205
|
-
};
|
|
2206
|
-
const handleClickOutside = (e) => {
|
|
2207
|
-
const target = e.target;
|
|
2208
|
-
if (!target.closest('.fixed-action-btn')) {
|
|
2209
|
-
state.isOpen = false;
|
|
2210
|
-
}
|
|
2211
|
-
};
|
|
2212
|
-
return {
|
|
2213
|
-
oncreate: () => {
|
|
2214
|
-
document.addEventListener('click', handleClickOutside);
|
|
2215
|
-
},
|
|
2216
|
-
onremove: () => {
|
|
2217
|
-
document.removeEventListener('click', handleClickOutside);
|
|
2218
|
-
},
|
|
2219
|
-
view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
|
|
2220
|
-
? 'position: absolute; display: inline-block; left: 24px;'
|
|
2221
|
-
: position === 'right' || position === 'inline-right'
|
|
2222
|
-
? 'position: absolute; display: inline-block; right: 24px;'
|
|
2223
|
-
: undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
|
|
2224
|
-
const fabClasses = [
|
|
2225
|
-
'fixed-action-btn',
|
|
2226
|
-
direction ? `direction-${direction}` : '',
|
|
2227
|
-
state.isOpen ? 'active' : '',
|
|
2228
|
-
// hoverEnabled ? 'hover-enabled' : '',
|
|
2229
|
-
]
|
|
2230
|
-
.filter(Boolean)
|
|
2231
|
-
.join(' ');
|
|
2232
|
-
return m('div', {
|
|
2233
|
-
style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
|
|
2234
|
-
}, m(`.${fabClasses}`, {
|
|
2235
|
-
style,
|
|
2236
|
-
onclick: (e) => {
|
|
2237
|
-
e.stopPropagation();
|
|
2238
|
-
if (buttons && buttons.length > 0) {
|
|
2239
|
-
state.isOpen = !state.isOpen;
|
|
2240
|
-
}
|
|
2241
|
-
},
|
|
2242
|
-
onmouseover: hoverEnabled
|
|
2243
|
-
? () => {
|
|
2244
|
-
if (buttons && buttons.length > 0) {
|
|
2245
|
-
state.isOpen = true;
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
: undefined,
|
|
2249
|
-
onmouseleave: hoverEnabled
|
|
2250
|
-
? () => {
|
|
2251
|
-
state.isOpen = false;
|
|
2252
|
-
}
|
|
2253
|
-
: undefined,
|
|
2254
|
-
}, [
|
|
2255
|
-
m('a.btn-floating.btn-large', {
|
|
2256
|
-
className,
|
|
2257
|
-
}, m('i.material-icons', { className: iconClass }, iconName)),
|
|
2258
|
-
buttons &&
|
|
2259
|
-
buttons.length > 0 &&
|
|
2260
|
-
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
|
|
2261
|
-
style: {
|
|
2262
|
-
opacity: state.isOpen ? '1' : '0',
|
|
2263
|
-
transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
|
|
2264
|
-
transition: `all 0.3s ease ${index * 40}ms`,
|
|
2265
2706
|
},
|
|
2707
|
+
}, m(DoubleRangeTooltip, {
|
|
2708
|
+
value: state.rangeMinValue,
|
|
2709
|
+
orientation,
|
|
2710
|
+
show: finalValueDisplay === 'always' ||
|
|
2711
|
+
(finalValueDisplay === 'auto' && state.isDragging && state.activeThumb === 'min'),
|
|
2712
|
+
})),
|
|
2713
|
+
// Max thumb
|
|
2714
|
+
m(`.thumb.${orientation}.max-thumb${state.activeThumb === 'max' ? '.active' : ''}`, {
|
|
2715
|
+
style: createThumbStyle(maxPercentage, state.activeThumb === 'max'),
|
|
2716
|
+
tabindex: disabled ? -1 : 0,
|
|
2717
|
+
role: 'slider',
|
|
2718
|
+
'aria-valuemin': state.rangeMinValue,
|
|
2719
|
+
'aria-valuemax': max,
|
|
2720
|
+
'aria-valuenow': state.rangeMaxValue,
|
|
2721
|
+
'aria-label': `Maximum value: ${state.rangeMaxValue}`,
|
|
2722
|
+
'aria-orientation': vertical ? 'vertical' : 'horizontal',
|
|
2723
|
+
onmousedown: handleMouseDown('max'),
|
|
2266
2724
|
onclick: (e) => {
|
|
2267
2725
|
e.stopPropagation();
|
|
2268
|
-
|
|
2269
|
-
|
|
2726
|
+
state.activeThumb = 'max';
|
|
2727
|
+
e.currentTarget.focus();
|
|
2270
2728
|
},
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2729
|
+
onfocus: () => {
|
|
2730
|
+
state.activeThumb = 'max';
|
|
2731
|
+
},
|
|
2732
|
+
onkeydown: (e) => {
|
|
2733
|
+
if (disabled)
|
|
2734
|
+
return;
|
|
2735
|
+
const currentValue = state.rangeMaxValue;
|
|
2736
|
+
const newValue = handleKeyboardNavigation(e.key, currentValue, min, max, step);
|
|
2737
|
+
if (newValue !== null) {
|
|
2738
|
+
e.preventDefault();
|
|
2739
|
+
const constrainedValue = Math.max(newValue, state.rangeMinValue);
|
|
2740
|
+
updateRangeValues(state.rangeMinValue, constrainedValue, attrs, state, false);
|
|
2741
|
+
}
|
|
2742
|
+
},
|
|
2743
|
+
}, m(DoubleRangeTooltip, {
|
|
2744
|
+
value: state.rangeMaxValue,
|
|
2745
|
+
orientation,
|
|
2746
|
+
show: finalValueDisplay === 'always' ||
|
|
2747
|
+
(finalValueDisplay === 'auto' && state.isDragging && state.activeThumb === 'max'),
|
|
2748
|
+
})),
|
|
2749
|
+
]),
|
|
2750
|
+
]),
|
|
2751
|
+
label ? m(Label, { label, id, isMandatory, isActive: true }) : null,
|
|
2752
|
+
helperText ? m(HelperText, { helperText }) : null,
|
|
2753
|
+
]);
|
|
2754
|
+
},
|
|
2275
2755
|
};
|
|
2276
2756
|
|
|
2277
2757
|
/** Character counter component that tracks text length against maxLength */
|
|
@@ -2384,6 +2864,7 @@ const TextArea = () => {
|
|
|
2384
2864
|
id,
|
|
2385
2865
|
isMandatory,
|
|
2386
2866
|
isActive: ((_a = state.textarea) === null || _a === void 0 ? void 0 : _a.value) || placeholder || state.active,
|
|
2867
|
+
initialValue: initialValue !== undefined,
|
|
2387
2868
|
}),
|
|
2388
2869
|
m(HelperText, {
|
|
2389
2870
|
helperText,
|
|
@@ -2410,6 +2891,12 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2410
2891
|
isValid: true,
|
|
2411
2892
|
active: false,
|
|
2412
2893
|
inputElement: null,
|
|
2894
|
+
// Range-specific state
|
|
2895
|
+
rangeMinValue: undefined,
|
|
2896
|
+
rangeMaxValue: undefined,
|
|
2897
|
+
singleValue: undefined,
|
|
2898
|
+
isDragging: false,
|
|
2899
|
+
activeThumb: null,
|
|
2413
2900
|
};
|
|
2414
2901
|
// let labelManager: { updateLabelState: () => void; cleanup: () => void } | null = null;
|
|
2415
2902
|
// let lengthUpdateHandler: (() => void) | null = null;
|
|
@@ -2435,15 +2922,39 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2435
2922
|
state.hasInteracted = length > 0;
|
|
2436
2923
|
}
|
|
2437
2924
|
};
|
|
2925
|
+
// Range slider helper functions
|
|
2926
|
+
// Range slider rendering functions are now in separate module
|
|
2438
2927
|
return {
|
|
2439
2928
|
view: ({ attrs }) => {
|
|
2440
2929
|
var _a;
|
|
2441
2930
|
const { className = 'col s12', dataError, dataSuccess, helperText, iconName, id = state.id, initialValue, placeholder, isMandatory, label, maxLength, newRow, oninput, onchange, onkeydown, onkeypress, onkeyup, style, validate } = attrs, params = __rest(attrs, ["className", "dataError", "dataSuccess", "helperText", "iconName", "id", "initialValue", "placeholder", "isMandatory", "label", "maxLength", "newRow", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "style", "validate"]);
|
|
2442
2931
|
// const attributes = toAttrs(params);
|
|
2443
|
-
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim();
|
|
2932
|
+
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
2933
|
+
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
2934
|
+
? true
|
|
2935
|
+
: false;
|
|
2936
|
+
// Special rendering for minmax range sliders
|
|
2937
|
+
if (type === 'range' && (attrs.minmax || attrs.valueDisplay)) {
|
|
2938
|
+
return m(attrs.minmax ? DoubleRangeSlider : SingleRangeSlider, Object.assign(Object.assign({}, attrs), { state,
|
|
2939
|
+
cn,
|
|
2940
|
+
style,
|
|
2941
|
+
iconName,
|
|
2942
|
+
id,
|
|
2943
|
+
label,
|
|
2944
|
+
isMandatory,
|
|
2945
|
+
helperText }));
|
|
2946
|
+
}
|
|
2444
2947
|
return m('.input-field', { className: cn, style }, [
|
|
2445
2948
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2446
|
-
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2949
|
+
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2950
|
+
placeholder, class: type === 'range' && attrs.vertical ? 'range-slider vertical' : undefined, style: type === 'range' && attrs.vertical
|
|
2951
|
+
? {
|
|
2952
|
+
height: attrs.height || '200px',
|
|
2953
|
+
width: '6px',
|
|
2954
|
+
writingMode: 'vertical-lr',
|
|
2955
|
+
direction: 'rtl',
|
|
2956
|
+
}
|
|
2957
|
+
: undefined,
|
|
2447
2958
|
// attributes,
|
|
2448
2959
|
oncreate: ({ dom }) => {
|
|
2449
2960
|
const input = (state.inputElement = dom);
|
|
@@ -2459,7 +2970,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2459
2970
|
state.currentLength = input.value.length; // Initial count
|
|
2460
2971
|
}
|
|
2461
2972
|
// Range input functionality
|
|
2462
|
-
if (type === 'range') {
|
|
2973
|
+
if (type === 'range' && !attrs.minmax) {
|
|
2463
2974
|
const updateThumb = () => {
|
|
2464
2975
|
const value = input.value;
|
|
2465
2976
|
const min = input.min || '0';
|
|
@@ -2560,14 +3071,8 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2560
3071
|
label,
|
|
2561
3072
|
id,
|
|
2562
3073
|
isMandatory,
|
|
2563
|
-
isActive
|
|
2564
|
-
|
|
2565
|
-
placeholder ||
|
|
2566
|
-
type === 'number' ||
|
|
2567
|
-
type === 'color' ||
|
|
2568
|
-
type === 'range'
|
|
2569
|
-
? true
|
|
2570
|
-
: false,
|
|
3074
|
+
isActive,
|
|
3075
|
+
initialValue: initialValue !== undefined,
|
|
2571
3076
|
}),
|
|
2572
3077
|
m(HelperText, {
|
|
2573
3078
|
helperText,
|
|
@@ -2662,81 +3167,1008 @@ const FileInput = () => {
|
|
|
2662
3167
|
};
|
|
2663
3168
|
};
|
|
2664
3169
|
|
|
2665
|
-
/**
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
3170
|
+
/** Component to show a check box */
|
|
3171
|
+
const InputCheckbox = () => {
|
|
3172
|
+
return {
|
|
3173
|
+
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3174
|
+
const checkboxId = inputId || uniqueId();
|
|
3175
|
+
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3176
|
+
m('input[type=checkbox][tabindex=0]', {
|
|
3177
|
+
id: checkboxId,
|
|
3178
|
+
checked,
|
|
3179
|
+
disabled,
|
|
3180
|
+
onclick: onchange
|
|
3181
|
+
? (e) => {
|
|
3182
|
+
if (e.target && typeof e.target.checked !== 'undefined') {
|
|
3183
|
+
onchange(e.target.checked);
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
: undefined,
|
|
3187
|
+
}),
|
|
3188
|
+
label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
|
|
3189
|
+
]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
|
|
3190
|
+
},
|
|
2675
3191
|
};
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
3192
|
+
};
|
|
3193
|
+
/** A list of checkboxes */
|
|
3194
|
+
const Options = () => {
|
|
3195
|
+
const state = {};
|
|
3196
|
+
const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
|
|
3197
|
+
const selectAll = (options, callback) => {
|
|
3198
|
+
const allIds = options.map((option) => option.id);
|
|
3199
|
+
state.checkedIds = [...allIds];
|
|
3200
|
+
if (callback)
|
|
3201
|
+
callback(allIds);
|
|
3202
|
+
};
|
|
3203
|
+
const selectNone = (callback) => {
|
|
3204
|
+
state.checkedIds = [];
|
|
3205
|
+
if (callback)
|
|
3206
|
+
callback([]);
|
|
3207
|
+
};
|
|
3208
|
+
return {
|
|
3209
|
+
oninit: ({ attrs: { initialValue, checkedId, id } }) => {
|
|
3210
|
+
const iv = checkedId || initialValue;
|
|
3211
|
+
state.checkedId = checkedId;
|
|
3212
|
+
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3213
|
+
state.componentId = id || uniqueId();
|
|
3214
|
+
},
|
|
3215
|
+
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
|
|
3216
|
+
const onchange = callback
|
|
3217
|
+
? (propId, checked) => {
|
|
3218
|
+
const checkedIds = state.checkedIds.filter((i) => i !== propId);
|
|
3219
|
+
if (checked) {
|
|
3220
|
+
checkedIds.push(propId);
|
|
3221
|
+
}
|
|
3222
|
+
state.checkedIds = checkedIds;
|
|
3223
|
+
callback(checkedIds);
|
|
3224
|
+
}
|
|
3225
|
+
: undefined;
|
|
3226
|
+
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
3227
|
+
const optionsContent = layout === 'horizontal'
|
|
3228
|
+
? m('div.grid-container', options.map((option) => m(InputCheckbox, {
|
|
3229
|
+
disabled: disabled || option.disabled,
|
|
3230
|
+
label: option.label,
|
|
3231
|
+
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3232
|
+
className: option.className || checkboxClass,
|
|
3233
|
+
checked: isChecked(option.id),
|
|
3234
|
+
description: option.description,
|
|
3235
|
+
inputId: `${state.componentId}-${option.id}`,
|
|
3236
|
+
})))
|
|
3237
|
+
: options.map((option) => m(InputCheckbox, {
|
|
3238
|
+
disabled: disabled || option.disabled,
|
|
3239
|
+
label: option.label,
|
|
3240
|
+
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3241
|
+
className: option.className || checkboxClass,
|
|
3242
|
+
checked: isChecked(option.id),
|
|
3243
|
+
description: option.description,
|
|
3244
|
+
inputId: `${state.componentId}-${option.id}`,
|
|
3245
|
+
}));
|
|
3246
|
+
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3247
|
+
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3248
|
+
showSelectAll &&
|
|
3249
|
+
m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
|
|
3250
|
+
m('a', {
|
|
3251
|
+
href: '#',
|
|
3252
|
+
onclick: (e) => {
|
|
3253
|
+
e.preventDefault();
|
|
3254
|
+
selectAll(options, callback);
|
|
3255
|
+
},
|
|
3256
|
+
style: 'margin-right: 15px;',
|
|
3257
|
+
}, 'Select All'),
|
|
3258
|
+
m('a', {
|
|
3259
|
+
href: '#',
|
|
3260
|
+
onclick: (e) => {
|
|
3261
|
+
e.preventDefault();
|
|
3262
|
+
selectNone(callback);
|
|
3263
|
+
},
|
|
3264
|
+
}, 'Select None'),
|
|
3265
|
+
]),
|
|
3266
|
+
description && m(HelperText, { helperText: description }),
|
|
3267
|
+
m('form', { action: '#' }, optionsContent),
|
|
3268
|
+
]);
|
|
3269
|
+
},
|
|
3270
|
+
};
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
const GlobalSearch = () => {
|
|
3274
|
+
return {
|
|
3275
|
+
view: ({ attrs }) => {
|
|
3276
|
+
const { searchPlaceholder, onSearch, i18n } = attrs;
|
|
3277
|
+
return m('.datatable-global-search', m(TextInput, {
|
|
3278
|
+
className: 'datatable-search',
|
|
3279
|
+
label: (i18n === null || i18n === void 0 ? void 0 : i18n.search) || 'Search',
|
|
3280
|
+
placeholder: searchPlaceholder || (i18n === null || i18n === void 0 ? void 0 : i18n.searchPlaceholder) || 'Search table...',
|
|
3281
|
+
oninput: onSearch,
|
|
3282
|
+
}));
|
|
3283
|
+
},
|
|
3284
|
+
};
|
|
3285
|
+
};
|
|
3286
|
+
const TableHeader = () => {
|
|
3287
|
+
return {
|
|
3288
|
+
view: ({ attrs }) => {
|
|
3289
|
+
const { columns, selection, sort, allSelected, helpers } = attrs;
|
|
3290
|
+
return m('thead', m('tr', [
|
|
3291
|
+
// Selection column header
|
|
3292
|
+
...(selection && selection.mode !== 'none'
|
|
3293
|
+
? [
|
|
3294
|
+
m('th.selection-checkbox', [
|
|
3295
|
+
selection.mode === 'multiple' &&
|
|
3296
|
+
m(InputCheckbox, {
|
|
3297
|
+
checked: allSelected,
|
|
3298
|
+
onchange: helpers.handleSelectAll,
|
|
3299
|
+
className: '',
|
|
3300
|
+
}),
|
|
3301
|
+
]),
|
|
3302
|
+
]
|
|
3303
|
+
: []),
|
|
3304
|
+
// Regular columns
|
|
3305
|
+
...columns.map((column) => {
|
|
3306
|
+
const isSorted = (sort === null || sort === void 0 ? void 0 : sort.column) === column.key;
|
|
3307
|
+
const sortDirection = isSorted ? sort.direction : null;
|
|
3308
|
+
return m('th', {
|
|
3309
|
+
class: [
|
|
3310
|
+
column.headerClassName,
|
|
3311
|
+
column.sortable ? 'sortable' : '',
|
|
3312
|
+
isSorted ? `sorted-${sortDirection}` : '',
|
|
3313
|
+
]
|
|
3314
|
+
.filter(Boolean)
|
|
3315
|
+
.join(' '),
|
|
3316
|
+
style: column.width ? { width: column.width } : undefined,
|
|
3317
|
+
onclick: column.sortable ? () => helpers.handleSort(column.key) : undefined,
|
|
3318
|
+
}, [
|
|
3319
|
+
column.title,
|
|
3320
|
+
column.sortable &&
|
|
3321
|
+
m('.sort-indicators', [
|
|
3322
|
+
m('span.sort-icon.sort-asc', {
|
|
3323
|
+
className: isSorted && sortDirection === 'asc' ? 'active' : '',
|
|
3324
|
+
}, '▲'),
|
|
3325
|
+
m('span.sort-icon.sort-desc', {
|
|
3326
|
+
className: isSorted && sortDirection === 'desc' ? 'active' : '',
|
|
3327
|
+
}, '▼'),
|
|
3328
|
+
]),
|
|
3329
|
+
]);
|
|
3330
|
+
}),
|
|
3331
|
+
]));
|
|
3332
|
+
},
|
|
3333
|
+
};
|
|
3334
|
+
};
|
|
3335
|
+
const TableRow = () => {
|
|
3336
|
+
return {
|
|
3337
|
+
view: ({ attrs }) => {
|
|
3338
|
+
const { row, index, columns, selection, onRowClick, onRowDoubleClick, getRowClassName, helpers, data } = attrs;
|
|
3339
|
+
// Calculate the original data index for the row key
|
|
3340
|
+
const originalIndex = data.findIndex((originalRow) => originalRow === row);
|
|
3341
|
+
const rowKey = (selection === null || selection === void 0 ? void 0 : selection.getRowKey(row, originalIndex)) || String(originalIndex);
|
|
3342
|
+
const isSelected = (selection === null || selection === void 0 ? void 0 : selection.selectedKeys.includes(rowKey)) || false;
|
|
3343
|
+
return m('tr', {
|
|
3344
|
+
class: [getRowClassName ? getRowClassName(row, index) : '', isSelected ? 'selected' : '']
|
|
3345
|
+
.filter(Boolean)
|
|
3346
|
+
.join(' ') || undefined,
|
|
3347
|
+
onclick: onRowClick ? (e) => onRowClick(row, index, e) : undefined,
|
|
3348
|
+
ondblclick: onRowDoubleClick ? (e) => onRowDoubleClick(row, index, e) : undefined,
|
|
3349
|
+
}, [
|
|
3350
|
+
// Selection column
|
|
3351
|
+
selection &&
|
|
3352
|
+
selection.mode !== 'none' &&
|
|
3353
|
+
m('td.selection-checkbox', [
|
|
3354
|
+
m(InputCheckbox, {
|
|
3355
|
+
checked: isSelected,
|
|
3356
|
+
onchange: (checked) => helpers.handleSelectionChange(rowKey, checked),
|
|
3357
|
+
className: '',
|
|
3358
|
+
}),
|
|
3359
|
+
]),
|
|
3360
|
+
columns.map((column) => {
|
|
3361
|
+
const value = helpers.getCellValue(row, column);
|
|
3362
|
+
let cellContent;
|
|
3363
|
+
if (column.cellRenderer) {
|
|
3364
|
+
cellContent = m(column.cellRenderer, {
|
|
3365
|
+
value,
|
|
3366
|
+
row,
|
|
3367
|
+
index,
|
|
3368
|
+
column,
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
else if (column.render) {
|
|
3372
|
+
// Backward compatibility with deprecated render function
|
|
3373
|
+
cellContent = column.render(value, row, index);
|
|
3374
|
+
}
|
|
3375
|
+
else {
|
|
3376
|
+
cellContent = String(value || '');
|
|
3377
|
+
}
|
|
3378
|
+
return m('td', {
|
|
3379
|
+
class: [column.className, column.align ? `align-${column.align}` : ''].filter(Boolean).join(' ') ||
|
|
3380
|
+
undefined,
|
|
3381
|
+
}, cellContent);
|
|
3382
|
+
}),
|
|
3383
|
+
]);
|
|
3384
|
+
},
|
|
3385
|
+
};
|
|
3386
|
+
};
|
|
3387
|
+
/**
|
|
3388
|
+
* Standalone Pagination Controls component
|
|
3389
|
+
*
|
|
3390
|
+
* Provides navigation controls for paginated data with customizable text labels.
|
|
3391
|
+
* Includes first page, previous page, next page, last page buttons and page info display.
|
|
3392
|
+
* Can be used independently of DataTable for any paginated content.
|
|
3393
|
+
*
|
|
3394
|
+
* @example
|
|
3395
|
+
* ```typescript
|
|
3396
|
+
* m(PaginationControls, {
|
|
3397
|
+
* pagination: { page: 0, pageSize: 10, total: 100 },
|
|
3398
|
+
* onPaginationChange: (newPagination) => console.log('Page changed:', newPagination),
|
|
3399
|
+
* i18n: { showing: 'Showing', to: 'to', of: 'of', entries: 'entries', page: 'Page' }
|
|
3400
|
+
* })
|
|
3401
|
+
* ```
|
|
3402
|
+
*/
|
|
3403
|
+
const PaginationControls = () => {
|
|
3404
|
+
return {
|
|
3405
|
+
view: ({ attrs }) => {
|
|
3406
|
+
const { pagination, onPaginationChange, i18n } = attrs;
|
|
3407
|
+
if (!pagination)
|
|
3408
|
+
return null;
|
|
3409
|
+
const { page, pageSize, total } = pagination;
|
|
3410
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
3411
|
+
const startItem = page * pageSize + 1;
|
|
3412
|
+
const endItem = Math.min((page + 1) * pageSize, total);
|
|
3413
|
+
const showingText = (i18n === null || i18n === void 0 ? void 0 : i18n.showing) || 'Showing';
|
|
3414
|
+
const toText = (i18n === null || i18n === void 0 ? void 0 : i18n.to) || 'to';
|
|
3415
|
+
const ofText = (i18n === null || i18n === void 0 ? void 0 : i18n.of) || 'of';
|
|
3416
|
+
const entriesText = (i18n === null || i18n === void 0 ? void 0 : i18n.entries) || 'entries';
|
|
3417
|
+
const pageText = (i18n === null || i18n === void 0 ? void 0 : i18n.page) || 'Page';
|
|
3418
|
+
return m('.datatable-pagination', [
|
|
3419
|
+
m('.pagination-info', `${showingText} ${startItem} ${toText} ${endItem} ${ofText} ${total} ${entriesText}`),
|
|
3420
|
+
m('.pagination-controls', [
|
|
3421
|
+
m('button.btn-flat', {
|
|
3422
|
+
disabled: page === 0,
|
|
3423
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: 0 })),
|
|
3424
|
+
}, '⏮'),
|
|
3425
|
+
m('button.btn-flat', {
|
|
3426
|
+
disabled: page === 0,
|
|
3427
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: page - 1 })),
|
|
3428
|
+
}, '◀'),
|
|
3429
|
+
m('span.page-info', `${pageText} ${page + 1} ${ofText} ${totalPages}`),
|
|
3430
|
+
m('button.btn-flat', {
|
|
3431
|
+
disabled: page >= totalPages - 1,
|
|
3432
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: page + 1 })),
|
|
3433
|
+
}, '▶'),
|
|
3434
|
+
m('button.btn-flat', {
|
|
3435
|
+
disabled: page >= totalPages - 1,
|
|
3436
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: totalPages - 1 })),
|
|
3437
|
+
}, '⏭'),
|
|
3438
|
+
]),
|
|
3439
|
+
]);
|
|
3440
|
+
},
|
|
3441
|
+
};
|
|
3442
|
+
};
|
|
3443
|
+
const TableContent = () => {
|
|
3444
|
+
return {
|
|
3445
|
+
view: ({ attrs: contentAttrs }) => {
|
|
3446
|
+
const { processedData, tableClasses, columns, selection, internalSort, allSelected, someSelected, helpers, onRowClick, onRowDoubleClick, getRowClassName, data, } = contentAttrs;
|
|
3447
|
+
return m('table', {
|
|
3448
|
+
class: tableClasses,
|
|
3449
|
+
}, m(TableHeader(), {
|
|
3450
|
+
columns,
|
|
3451
|
+
selection,
|
|
3452
|
+
sort: internalSort,
|
|
3453
|
+
allSelected,
|
|
3454
|
+
someSelected,
|
|
3455
|
+
helpers,
|
|
3456
|
+
}), m('tbody', processedData.map((row, index) => m(TableRow(), {
|
|
3457
|
+
key: (selection === null || selection === void 0 ? void 0 : selection.getRowKey(row, data.findIndex((originalRow) => originalRow === row))) || index,
|
|
3458
|
+
row,
|
|
3459
|
+
index,
|
|
3460
|
+
columns,
|
|
3461
|
+
selection,
|
|
3462
|
+
onRowClick,
|
|
3463
|
+
onRowDoubleClick,
|
|
3464
|
+
getRowClassName,
|
|
3465
|
+
helpers,
|
|
3466
|
+
data,
|
|
3467
|
+
}))));
|
|
3468
|
+
},
|
|
3469
|
+
};
|
|
3470
|
+
};
|
|
3471
|
+
/**
|
|
3472
|
+
* A comprehensive data table component with sorting, filtering, pagination, and selection capabilities.
|
|
3473
|
+
*
|
|
3474
|
+
* @template T The type of data objects displayed in each row
|
|
3475
|
+
*
|
|
3476
|
+
* @description
|
|
3477
|
+
* The DataTable component provides a feature-rich interface for displaying and interacting with tabular data.
|
|
3478
|
+
* It supports both controlled and uncontrolled modes for all interactive features.
|
|
3479
|
+
*
|
|
3480
|
+
* **Key Features:**
|
|
3481
|
+
* - Sorting: Click column headers to sort data ascending/descending
|
|
3482
|
+
* - Filtering: Global search across filterable columns
|
|
3483
|
+
* - Pagination: Navigate through large datasets with customizable page sizes
|
|
3484
|
+
* - Selection: Single or multiple row selection with callbacks
|
|
3485
|
+
* - Custom rendering: Use cellRenderer for complex cell content
|
|
3486
|
+
* - Responsive: Adapts to different screen sizes
|
|
3487
|
+
* - Internationalization: Customize all UI text
|
|
3488
|
+
* - Accessibility: Proper ARIA attributes and keyboard navigation
|
|
3489
|
+
*
|
|
3490
|
+
* @example Basic usage
|
|
3491
|
+
* ```typescript
|
|
3492
|
+
* interface User { id: number; name: string; email: string; }
|
|
3493
|
+
* const users: User[] = [...];
|
|
3494
|
+
* const columns: DataTableColumn<User>[] = [
|
|
3495
|
+
* { key: 'name', title: 'Name', field: 'name', sortable: true, filterable: true },
|
|
3496
|
+
* { key: 'email', title: 'Email', field: 'email', sortable: true, filterable: true }
|
|
3497
|
+
* ];
|
|
3498
|
+
*
|
|
3499
|
+
* return m(DataTable<User>, { data: users, columns });
|
|
3500
|
+
* ```
|
|
3501
|
+
*
|
|
3502
|
+
* @example Advanced usage with all features
|
|
3503
|
+
* ```typescript
|
|
3504
|
+
* return m(DataTable<User>, {
|
|
3505
|
+
* data: users,
|
|
3506
|
+
* columns,
|
|
3507
|
+
* title: 'User Management',
|
|
3508
|
+
* striped: true,
|
|
3509
|
+
* hoverable: true,
|
|
3510
|
+
* height: 400,
|
|
3511
|
+
*
|
|
3512
|
+
* // Pagination
|
|
3513
|
+
* pagination: { page: 0, pageSize: 10, total: users.length },
|
|
3514
|
+
* onPaginationChange: (pagination) => console.log('Page changed:', pagination),
|
|
3515
|
+
*
|
|
3516
|
+
* // Selection
|
|
3517
|
+
* selection: {
|
|
3518
|
+
* mode: 'multiple',
|
|
3519
|
+
* selectedKeys: [],
|
|
3520
|
+
* getRowKey: (user) => String(user.id),
|
|
3521
|
+
* onSelectionChange: (keys, selectedUsers) => console.log('Selection:', selectedUsers)
|
|
3522
|
+
* },
|
|
3523
|
+
*
|
|
3524
|
+
* // Search
|
|
3525
|
+
* enableGlobalSearch: true,
|
|
3526
|
+
* searchPlaceholder: 'Search users...',
|
|
3527
|
+
*
|
|
3528
|
+
* // Events
|
|
3529
|
+
* onRowClick: (user, index, event) => console.log('Clicked:', user),
|
|
3530
|
+
* onRowDoubleClick: (user) => editUser(user),
|
|
3531
|
+
*
|
|
3532
|
+
* // Styling
|
|
3533
|
+
* getRowClassName: (user) => user.active ? '' : 'inactive-row'
|
|
3534
|
+
* });
|
|
3535
|
+
* ```
|
|
3536
|
+
*
|
|
3537
|
+
* @returns A Mithril component that renders the data table
|
|
3538
|
+
*/
|
|
3539
|
+
const DataTable = () => {
|
|
3540
|
+
const state = {
|
|
3541
|
+
internalSort: undefined,
|
|
3542
|
+
internalFilter: undefined,
|
|
3543
|
+
internalPagination: undefined,
|
|
3544
|
+
processedData: [],
|
|
3545
|
+
tableId: '',
|
|
3546
|
+
// Performance optimization caches
|
|
3547
|
+
lastProcessedHash: ''};
|
|
3548
|
+
// Helper functions
|
|
3549
|
+
const quickDataHash = (data) => {
|
|
3550
|
+
if (data.length === 0)
|
|
3551
|
+
return '0';
|
|
3552
|
+
if (data.length === 1)
|
|
3553
|
+
return '1';
|
|
3554
|
+
// Sample first, middle, and last items for quick hash
|
|
3555
|
+
const first = JSON.stringify(data[0]);
|
|
3556
|
+
const middle = data.length > 2 ? JSON.stringify(data[Math.floor(data.length / 2)]) : '';
|
|
3557
|
+
const last = JSON.stringify(data[data.length - 1]);
|
|
3558
|
+
return `${data.length}-${first.length}-${middle.length}-${last.length}`;
|
|
3559
|
+
};
|
|
3560
|
+
const getDataHash = (attrs) => {
|
|
3561
|
+
const { data, sort, filter, pagination } = attrs;
|
|
3562
|
+
const { internalSort, internalFilter, internalPagination } = state;
|
|
3563
|
+
const hashInputs = {
|
|
3564
|
+
dataLength: data.length,
|
|
3565
|
+
dataHash: quickDataHash(data),
|
|
3566
|
+
sort: sort || internalSort,
|
|
3567
|
+
filter: filter || internalFilter,
|
|
3568
|
+
pagination: pagination || internalPagination,
|
|
3569
|
+
};
|
|
3570
|
+
return JSON.stringify(hashInputs);
|
|
3571
|
+
};
|
|
3572
|
+
const getCellValue = (row, column) => {
|
|
3573
|
+
if (column.field) {
|
|
3574
|
+
return row[column.field];
|
|
3575
|
+
}
|
|
3576
|
+
return row;
|
|
3577
|
+
};
|
|
3578
|
+
const applyFiltering = (data, filter, columns) => {
|
|
3579
|
+
var _a;
|
|
3580
|
+
if (!filter.searchTerm && !filter.columnFilters)
|
|
3581
|
+
return data;
|
|
3582
|
+
const filterableColumns = columns.filter((col) => col.filterable);
|
|
3583
|
+
if (filterableColumns.length === 0 && !filter.searchTerm)
|
|
3584
|
+
return data;
|
|
3585
|
+
const searchTerm = (_a = filter.searchTerm) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
3586
|
+
const hasColumnFilters = filter.columnFilters &&
|
|
3587
|
+
Object.keys(filter.columnFilters).some((key) => {
|
|
3588
|
+
const value = filter.columnFilters[key];
|
|
3589
|
+
return value !== null && value !== undefined && value !== '';
|
|
3590
|
+
});
|
|
3591
|
+
return data.filter((row) => {
|
|
3592
|
+
// Global search
|
|
3593
|
+
if (searchTerm) {
|
|
3594
|
+
const matchesGlobal = filterableColumns.some((column) => {
|
|
3595
|
+
const value = getCellValue(row, column);
|
|
3596
|
+
if (value == null)
|
|
3597
|
+
return false;
|
|
3598
|
+
return String(value).toLowerCase().includes(searchTerm);
|
|
3599
|
+
});
|
|
3600
|
+
if (!matchesGlobal)
|
|
3601
|
+
return false;
|
|
3602
|
+
}
|
|
3603
|
+
// Column-specific filters
|
|
3604
|
+
if (hasColumnFilters) {
|
|
3605
|
+
const matchesColumnFilters = Object.entries(filter.columnFilters).every(([columnKey, filterValue]) => {
|
|
3606
|
+
if (filterValue === null || filterValue === undefined || filterValue === '')
|
|
3607
|
+
return true;
|
|
3608
|
+
const column = columns.find((col) => col.key === columnKey);
|
|
3609
|
+
if (!column)
|
|
3610
|
+
return true;
|
|
3611
|
+
const value = getCellValue(row, column);
|
|
3612
|
+
if (value == null)
|
|
3613
|
+
return false;
|
|
3614
|
+
return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
|
|
3615
|
+
});
|
|
3616
|
+
if (!matchesColumnFilters)
|
|
3617
|
+
return false;
|
|
3618
|
+
}
|
|
3619
|
+
return true;
|
|
3620
|
+
});
|
|
3621
|
+
};
|
|
3622
|
+
const applySorting = (data, sort, columns) => {
|
|
3623
|
+
const column = columns.find((col) => col.key === sort.column);
|
|
3624
|
+
if (!column || !column.sortable)
|
|
3625
|
+
return data;
|
|
3626
|
+
const multiplier = sort.direction === 'asc' ? 1 : -1;
|
|
3627
|
+
return [...data].sort((a, b) => {
|
|
3628
|
+
const aValue = getCellValue(a, column);
|
|
3629
|
+
const bValue = getCellValue(b, column);
|
|
3630
|
+
// Handle null/undefined values
|
|
3631
|
+
if (aValue == null && bValue == null)
|
|
3632
|
+
return 0;
|
|
3633
|
+
if (aValue == null)
|
|
3634
|
+
return -1 * multiplier;
|
|
3635
|
+
if (bValue == null)
|
|
3636
|
+
return 1 * multiplier;
|
|
3637
|
+
// Type-specific comparisons
|
|
3638
|
+
const aType = typeof aValue;
|
|
3639
|
+
const bType = typeof bValue;
|
|
3640
|
+
if (aType === bType) {
|
|
3641
|
+
if (aType === 'number') {
|
|
3642
|
+
return (aValue - bValue) * multiplier;
|
|
3643
|
+
}
|
|
3644
|
+
if (aType === 'boolean') {
|
|
3645
|
+
return (aValue === bValue ? 0 : aValue ? 1 : -1) * multiplier;
|
|
3646
|
+
}
|
|
3647
|
+
if (aValue instanceof Date && bValue instanceof Date) {
|
|
3648
|
+
return (aValue.getTime() - bValue.getTime()) * multiplier;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
// Fallback to string comparison
|
|
3652
|
+
return String(aValue).localeCompare(String(bValue)) * multiplier;
|
|
3653
|
+
});
|
|
3654
|
+
};
|
|
3655
|
+
const processData = (attrs) => {
|
|
3656
|
+
const { data } = attrs;
|
|
3657
|
+
const { internalSort, internalFilter, internalPagination } = state;
|
|
3658
|
+
let processedData = [...data];
|
|
3659
|
+
// Apply filtering
|
|
3660
|
+
if (internalFilter) {
|
|
3661
|
+
processedData = applyFiltering(processedData, internalFilter, attrs.columns);
|
|
3662
|
+
}
|
|
3663
|
+
// Apply sorting
|
|
3664
|
+
if (internalSort) {
|
|
3665
|
+
processedData = applySorting(processedData, internalSort, attrs.columns);
|
|
3666
|
+
}
|
|
3667
|
+
// Update total count for pagination
|
|
3668
|
+
if (internalPagination) {
|
|
3669
|
+
state.internalPagination = Object.assign(Object.assign({}, internalPagination), { total: processedData.length });
|
|
3670
|
+
}
|
|
3671
|
+
// Apply pagination
|
|
3672
|
+
if (internalPagination) {
|
|
3673
|
+
const { page, pageSize } = internalPagination;
|
|
3674
|
+
const start = page * pageSize;
|
|
3675
|
+
const end = start + pageSize;
|
|
3676
|
+
processedData = processedData.slice(start, end);
|
|
3677
|
+
}
|
|
3678
|
+
state.processedData = processedData;
|
|
3679
|
+
};
|
|
3680
|
+
// Create stable helper functions that don't get recreated on every render
|
|
3681
|
+
const createHelpers = (attrs) => ({
|
|
3682
|
+
getCellValue,
|
|
3683
|
+
handleSort: (columnKey) => {
|
|
3684
|
+
var _a;
|
|
3685
|
+
const column = attrs.columns.find((col) => col.key === columnKey);
|
|
3686
|
+
if (!column || !column.sortable)
|
|
3687
|
+
return;
|
|
3688
|
+
const currentSort = state.internalSort;
|
|
3689
|
+
let newSort;
|
|
3690
|
+
if ((currentSort === null || currentSort === void 0 ? void 0 : currentSort.column) === columnKey) {
|
|
3691
|
+
// Toggle direction
|
|
3692
|
+
if (currentSort.direction === 'asc') {
|
|
3693
|
+
newSort = { column: columnKey, direction: 'desc' };
|
|
3694
|
+
}
|
|
3695
|
+
else {
|
|
3696
|
+
newSort = { column: columnKey, direction: 'asc' };
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
else {
|
|
3700
|
+
// New column sort
|
|
3701
|
+
newSort = { column: columnKey, direction: 'asc' };
|
|
3702
|
+
}
|
|
3703
|
+
state.internalSort = newSort;
|
|
3704
|
+
(_a = attrs.onSortChange) === null || _a === void 0 ? void 0 : _a.call(attrs, newSort);
|
|
3705
|
+
},
|
|
3706
|
+
handleGlobalSearch: (searchTerm) => {
|
|
3707
|
+
var _a;
|
|
3708
|
+
const newFilter = Object.assign(Object.assign({}, state.internalFilter), { searchTerm });
|
|
3709
|
+
state.internalFilter = newFilter;
|
|
3710
|
+
// Reset pagination to first page when filtering
|
|
3711
|
+
if (state.internalPagination) {
|
|
3712
|
+
state.internalPagination = Object.assign(Object.assign({}, state.internalPagination), { page: 0 });
|
|
3713
|
+
}
|
|
3714
|
+
(_a = attrs.onFilterChange) === null || _a === void 0 ? void 0 : _a.call(attrs, newFilter);
|
|
3715
|
+
},
|
|
3716
|
+
handleSelectionChange: (rowKey, selected) => {
|
|
3717
|
+
var _a, _b;
|
|
3718
|
+
if (!attrs.selection)
|
|
3719
|
+
return;
|
|
3720
|
+
let newSelectedKeys;
|
|
3721
|
+
if (attrs.selection.mode === 'single') {
|
|
3722
|
+
newSelectedKeys = selected ? [rowKey] : [];
|
|
3723
|
+
}
|
|
3724
|
+
else if (attrs.selection.mode === 'multiple') {
|
|
3725
|
+
if (selected) {
|
|
3726
|
+
newSelectedKeys = [...attrs.selection.selectedKeys, rowKey];
|
|
3727
|
+
}
|
|
3728
|
+
else {
|
|
3729
|
+
newSelectedKeys = attrs.selection.selectedKeys.filter((key) => key !== rowKey);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
else {
|
|
3733
|
+
return; // No selection mode
|
|
3734
|
+
}
|
|
3735
|
+
// Get selected rows
|
|
3736
|
+
const selectedRows = attrs.data.filter((row, index) => {
|
|
3737
|
+
const key = attrs.selection.getRowKey(row, index);
|
|
3738
|
+
return newSelectedKeys.includes(key);
|
|
3739
|
+
});
|
|
3740
|
+
(_b = (_a = attrs.selection).onSelectionChange) === null || _b === void 0 ? void 0 : _b.call(_a, newSelectedKeys, selectedRows);
|
|
3741
|
+
},
|
|
3742
|
+
handleSelectAll: (selected) => {
|
|
3743
|
+
var _a, _b;
|
|
3744
|
+
if (!attrs.selection || attrs.selection.mode !== 'multiple')
|
|
3745
|
+
return;
|
|
3746
|
+
let newSelectedKeys;
|
|
3747
|
+
if (selected) {
|
|
3748
|
+
// Select all visible rows
|
|
3749
|
+
newSelectedKeys = state.processedData.map((row) => {
|
|
3750
|
+
const originalIndex = attrs.data.findIndex((originalRow) => originalRow === row);
|
|
3751
|
+
return attrs.selection.getRowKey(row, originalIndex);
|
|
3752
|
+
});
|
|
3753
|
+
}
|
|
3754
|
+
else {
|
|
3755
|
+
newSelectedKeys = [];
|
|
3756
|
+
}
|
|
3757
|
+
const selectedRows = attrs.data.filter((row, index) => {
|
|
3758
|
+
const key = attrs.selection.getRowKey(row, index);
|
|
3759
|
+
return newSelectedKeys.includes(key);
|
|
3760
|
+
});
|
|
3761
|
+
(_b = (_a = attrs.selection).onSelectionChange) === null || _b === void 0 ? void 0 : _b.call(_a, newSelectedKeys, selectedRows);
|
|
3762
|
+
},
|
|
3763
|
+
});
|
|
3764
|
+
return {
|
|
3765
|
+
oninit(vnodeInit) {
|
|
3766
|
+
const { sort, filter, pagination } = vnodeInit.attrs;
|
|
3767
|
+
state.tableId = uniqueId();
|
|
3768
|
+
state.internalSort = sort || undefined;
|
|
3769
|
+
state.internalFilter = filter || { searchTerm: '', columnFilters: {} };
|
|
3770
|
+
state.internalPagination = pagination || undefined;
|
|
3771
|
+
processData(vnodeInit.attrs);
|
|
3772
|
+
},
|
|
3773
|
+
onbeforeupdate(vnodeUpdate) {
|
|
3774
|
+
// Only reprocess data if inputs have changed
|
|
3775
|
+
const currentHash = getDataHash(vnodeUpdate.attrs);
|
|
3776
|
+
if (currentHash !== state.lastProcessedHash) {
|
|
3777
|
+
processData(vnodeUpdate.attrs);
|
|
3778
|
+
state.lastProcessedHash = currentHash;
|
|
3779
|
+
}
|
|
3780
|
+
},
|
|
3781
|
+
view(vnodeView) {
|
|
3782
|
+
const attrs = vnodeView.attrs;
|
|
3783
|
+
const { loading, emptyMessage, striped, hoverable, responsive, centered, className, id, title, height, enableGlobalSearch, searchPlaceholder, selection, columns, onRowClick, onRowDoubleClick, getRowClassName, data, onPaginationChange, i18n, } = attrs;
|
|
3784
|
+
const { processedData, tableId, internalSort, internalPagination } = state;
|
|
3785
|
+
if (loading) {
|
|
3786
|
+
return m('.datatable-loading', [
|
|
3787
|
+
m('.preloader-wrapper.small.active', m('.spinner-layer.spinner-blue-only', m('.circle-clipper.left', m('.circle')))),
|
|
3788
|
+
m('p', (i18n === null || i18n === void 0 ? void 0 : i18n.loading) || 'Loading...'),
|
|
3789
|
+
]);
|
|
3790
|
+
}
|
|
3791
|
+
// Create stable helpers object using the factory function
|
|
3792
|
+
const helpers = createHelpers(attrs);
|
|
3793
|
+
// Calculate selection state for "select all" checkbox
|
|
3794
|
+
let allSelected = false;
|
|
3795
|
+
let someSelected = false;
|
|
3796
|
+
if (selection && selection.mode === 'multiple') {
|
|
3797
|
+
const visibleRowKeys = processedData.map((row) => {
|
|
3798
|
+
const originalIndex = data.findIndex((originalRow) => originalRow === row);
|
|
3799
|
+
return selection.getRowKey(row, originalIndex);
|
|
3800
|
+
});
|
|
3801
|
+
const selectedVisibleKeys = visibleRowKeys.filter((key) => selection.selectedKeys.includes(key));
|
|
3802
|
+
allSelected = visibleRowKeys.length > 0 && selectedVisibleKeys.length === visibleRowKeys.length;
|
|
3803
|
+
someSelected = selectedVisibleKeys.length > 0 && selectedVisibleKeys.length < visibleRowKeys.length;
|
|
3804
|
+
}
|
|
3805
|
+
const tableClasses = [
|
|
3806
|
+
'datatable',
|
|
3807
|
+
striped ? 'striped' : '',
|
|
3808
|
+
hoverable ? 'highlight' : '',
|
|
3809
|
+
responsive ? 'responsive-table' : '',
|
|
3810
|
+
centered ? 'centered' : '',
|
|
3811
|
+
className || '',
|
|
3812
|
+
]
|
|
3813
|
+
.filter(Boolean)
|
|
3814
|
+
.join(' ');
|
|
3815
|
+
return m('.datatable-container', {
|
|
3816
|
+
id: id || tableId,
|
|
3817
|
+
}, title && m('h5.datatable-title', title), enableGlobalSearch &&
|
|
3818
|
+
m(GlobalSearch, {
|
|
3819
|
+
searchPlaceholder,
|
|
3820
|
+
onSearch: helpers.handleGlobalSearch,
|
|
3821
|
+
i18n,
|
|
3822
|
+
}), m('.datatable-wrapper', {
|
|
3823
|
+
style: {
|
|
3824
|
+
maxHeight: height ? `${height}px` : undefined,
|
|
3825
|
+
overflowY: height ? 'auto' : undefined,
|
|
3826
|
+
},
|
|
3827
|
+
}, processedData.length === 0
|
|
3828
|
+
? m('.datatable-empty', emptyMessage || (i18n === null || i18n === void 0 ? void 0 : i18n.noDataAvailable) || 'No data available')
|
|
3829
|
+
: m(TableContent(), {
|
|
3830
|
+
processedData,
|
|
3831
|
+
height,
|
|
3832
|
+
tableClasses,
|
|
3833
|
+
columns,
|
|
3834
|
+
selection,
|
|
3835
|
+
internalSort,
|
|
3836
|
+
allSelected,
|
|
3837
|
+
someSelected,
|
|
3838
|
+
helpers,
|
|
3839
|
+
onRowClick,
|
|
3840
|
+
onRowDoubleClick,
|
|
3841
|
+
getRowClassName,
|
|
3842
|
+
data,
|
|
3843
|
+
})), m(PaginationControls, {
|
|
3844
|
+
pagination: internalPagination,
|
|
3845
|
+
onPaginationChange: (pagination) => {
|
|
3846
|
+
state.internalPagination = pagination;
|
|
3847
|
+
onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(pagination);
|
|
3848
|
+
},
|
|
3849
|
+
i18n,
|
|
3850
|
+
}));
|
|
3851
|
+
},
|
|
3852
|
+
};
|
|
3853
|
+
};
|
|
3854
|
+
|
|
3855
|
+
/** Pure TypeScript Dropdown component - no Materialize dependencies */
|
|
3856
|
+
const Dropdown = () => {
|
|
3857
|
+
const state = {
|
|
3858
|
+
isOpen: false,
|
|
3859
|
+
initialValue: undefined,
|
|
3860
|
+
id: '',
|
|
3861
|
+
focusedIndex: -1,
|
|
3862
|
+
inputRef: null,
|
|
3863
|
+
dropdownRef: null,
|
|
3864
|
+
};
|
|
3865
|
+
const handleKeyDown = (e, items, onchange) => {
|
|
3866
|
+
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
3867
|
+
switch (e.key) {
|
|
3868
|
+
case 'ArrowDown':
|
|
3869
|
+
e.preventDefault();
|
|
3870
|
+
if (!state.isOpen) {
|
|
3871
|
+
state.isOpen = true;
|
|
3872
|
+
state.focusedIndex = 0;
|
|
3873
|
+
}
|
|
3874
|
+
else {
|
|
3875
|
+
state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
|
|
3876
|
+
}
|
|
3877
|
+
break;
|
|
3878
|
+
case 'ArrowUp':
|
|
3879
|
+
e.preventDefault();
|
|
3880
|
+
if (state.isOpen) {
|
|
3881
|
+
state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
|
|
3882
|
+
}
|
|
3883
|
+
break;
|
|
3884
|
+
case 'Enter':
|
|
3885
|
+
case ' ':
|
|
3886
|
+
e.preventDefault();
|
|
3887
|
+
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
3888
|
+
const selectedItem = availableItems[state.focusedIndex];
|
|
3889
|
+
const value = (selectedItem.id || selectedItem.label);
|
|
3890
|
+
state.initialValue = value;
|
|
3891
|
+
state.isOpen = false;
|
|
3892
|
+
state.focusedIndex = -1;
|
|
3893
|
+
if (onchange)
|
|
3894
|
+
onchange(value);
|
|
3895
|
+
}
|
|
3896
|
+
else if (!state.isOpen) {
|
|
3897
|
+
state.isOpen = true;
|
|
3898
|
+
state.focusedIndex = 0;
|
|
3899
|
+
}
|
|
3900
|
+
break;
|
|
3901
|
+
case 'Escape':
|
|
3902
|
+
e.preventDefault();
|
|
3903
|
+
state.isOpen = false;
|
|
3904
|
+
state.focusedIndex = -1;
|
|
3905
|
+
break;
|
|
3906
|
+
}
|
|
3907
|
+
};
|
|
3908
|
+
return {
|
|
3909
|
+
oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
|
|
3910
|
+
state.id = id;
|
|
3911
|
+
state.initialValue = initialValue || checkedId;
|
|
3912
|
+
// Mithril will handle click events through the component structure
|
|
3913
|
+
},
|
|
3914
|
+
view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
|
|
3915
|
+
const { initialValue } = state;
|
|
3916
|
+
const selectedItem = initialValue
|
|
3917
|
+
? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
|
|
3918
|
+
: undefined;
|
|
3919
|
+
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3920
|
+
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
3921
|
+
return m('.dropdown-wrapper.input-field', { className, key, style }, [
|
|
3922
|
+
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3923
|
+
m(HelperText, { helperText }),
|
|
3924
|
+
m('.select-wrapper', {
|
|
3925
|
+
onclick: disabled
|
|
3926
|
+
? undefined
|
|
3927
|
+
: () => {
|
|
3928
|
+
state.isOpen = !state.isOpen;
|
|
3929
|
+
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
3930
|
+
},
|
|
3931
|
+
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
|
|
3932
|
+
tabindex: disabled ? -1 : 0,
|
|
3933
|
+
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3934
|
+
'aria-haspopup': 'listbox',
|
|
3935
|
+
role: 'combobox',
|
|
3936
|
+
}, [
|
|
3937
|
+
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
3938
|
+
id: state.id,
|
|
3939
|
+
value: title,
|
|
3940
|
+
oncreate: ({ dom }) => {
|
|
3941
|
+
state.inputRef = dom;
|
|
3942
|
+
},
|
|
3943
|
+
onclick: (e) => {
|
|
3944
|
+
e.preventDefault();
|
|
3945
|
+
e.stopPropagation();
|
|
3946
|
+
if (!disabled) {
|
|
3947
|
+
state.isOpen = !state.isOpen;
|
|
3948
|
+
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
3949
|
+
}
|
|
3950
|
+
},
|
|
3951
|
+
}),
|
|
3952
|
+
// Dropdown Menu using Select component's positioning logic
|
|
3953
|
+
state.isOpen &&
|
|
3954
|
+
m('ul.dropdown-content.select-dropdown', {
|
|
3955
|
+
tabindex: 0,
|
|
3956
|
+
role: 'listbox',
|
|
3957
|
+
'aria-labelledby': state.id,
|
|
3958
|
+
oncreate: ({ dom }) => {
|
|
3959
|
+
state.dropdownRef = dom;
|
|
3960
|
+
},
|
|
3961
|
+
onremove: () => {
|
|
3962
|
+
state.dropdownRef = null;
|
|
3963
|
+
},
|
|
3964
|
+
style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
|
|
3965
|
+
// Convert dropdown items to format expected by getDropdownStyles
|
|
3966
|
+
group: undefined }))), true),
|
|
3967
|
+
}, items.map((item, index) => {
|
|
3968
|
+
if (item.divider) {
|
|
3969
|
+
return m('li.divider', {
|
|
3970
|
+
key: `divider-${index}`,
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
const itemIndex = availableItems.indexOf(item);
|
|
3974
|
+
const isFocused = itemIndex === state.focusedIndex;
|
|
3975
|
+
return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
|
|
3976
|
+
item.disabled ? 'disabled' : '',
|
|
3977
|
+
isFocused ? 'focused' : '',
|
|
3978
|
+
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
3979
|
+
]
|
|
3980
|
+
.filter(Boolean)
|
|
3981
|
+
.join(' ') }, (item.disabled
|
|
3982
|
+
? {}
|
|
3983
|
+
: {
|
|
3984
|
+
onclick: (e) => {
|
|
3985
|
+
e.stopPropagation();
|
|
3986
|
+
const value = (item.id || item.label);
|
|
3987
|
+
state.initialValue = value;
|
|
3988
|
+
state.isOpen = false;
|
|
3989
|
+
state.focusedIndex = -1;
|
|
3990
|
+
if (onchange)
|
|
3991
|
+
onchange(value);
|
|
3992
|
+
},
|
|
3993
|
+
})), m('span', {
|
|
3994
|
+
style: {
|
|
3995
|
+
display: 'flex',
|
|
3996
|
+
alignItems: 'center',
|
|
3997
|
+
padding: '14px 16px',
|
|
3998
|
+
},
|
|
3999
|
+
}, [
|
|
4000
|
+
item.iconName
|
|
4001
|
+
? m('i.material-icons', {
|
|
4002
|
+
style: { marginRight: '32px' },
|
|
4003
|
+
}, item.iconName)
|
|
4004
|
+
: undefined,
|
|
4005
|
+
item.label,
|
|
4006
|
+
]));
|
|
4007
|
+
})),
|
|
4008
|
+
m(MaterialIcon, {
|
|
4009
|
+
name: 'caret',
|
|
4010
|
+
direction: 'down',
|
|
4011
|
+
class: 'caret',
|
|
4012
|
+
}),
|
|
4013
|
+
]),
|
|
4014
|
+
]);
|
|
4015
|
+
},
|
|
4016
|
+
};
|
|
4017
|
+
};
|
|
4018
|
+
|
|
4019
|
+
/**
|
|
4020
|
+
* Floating Action Button
|
|
4021
|
+
*/
|
|
4022
|
+
const FloatingActionButton = () => {
|
|
4023
|
+
const state = {
|
|
4024
|
+
isOpen: false,
|
|
4025
|
+
};
|
|
4026
|
+
const handleClickOutside = (e) => {
|
|
4027
|
+
const target = e.target;
|
|
4028
|
+
if (!target.closest('.fixed-action-btn')) {
|
|
4029
|
+
state.isOpen = false;
|
|
4030
|
+
}
|
|
4031
|
+
};
|
|
4032
|
+
return {
|
|
4033
|
+
oncreate: () => {
|
|
4034
|
+
document.addEventListener('click', handleClickOutside);
|
|
4035
|
+
},
|
|
4036
|
+
onremove: () => {
|
|
4037
|
+
document.removeEventListener('click', handleClickOutside);
|
|
4038
|
+
},
|
|
4039
|
+
view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
|
|
4040
|
+
? 'position: absolute; display: inline-block; left: 24px;'
|
|
4041
|
+
: position === 'right' || position === 'inline-right'
|
|
4042
|
+
? 'position: absolute; display: inline-block; right: 24px;'
|
|
4043
|
+
: undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
|
|
4044
|
+
const fabClasses = [
|
|
4045
|
+
'fixed-action-btn',
|
|
4046
|
+
direction ? `direction-${direction}` : '',
|
|
4047
|
+
state.isOpen ? 'active' : '',
|
|
4048
|
+
// hoverEnabled ? 'hover-enabled' : '',
|
|
4049
|
+
]
|
|
4050
|
+
.filter(Boolean)
|
|
4051
|
+
.join(' ');
|
|
4052
|
+
return m('div', {
|
|
4053
|
+
style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
|
|
4054
|
+
}, m(`.${fabClasses}`, {
|
|
4055
|
+
style,
|
|
4056
|
+
onclick: (e) => {
|
|
4057
|
+
e.stopPropagation();
|
|
4058
|
+
if (buttons && buttons.length > 0) {
|
|
4059
|
+
state.isOpen = !state.isOpen;
|
|
4060
|
+
}
|
|
4061
|
+
},
|
|
4062
|
+
onmouseover: hoverEnabled
|
|
4063
|
+
? () => {
|
|
4064
|
+
if (buttons && buttons.length > 0) {
|
|
4065
|
+
state.isOpen = true;
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
: undefined,
|
|
4069
|
+
onmouseleave: hoverEnabled
|
|
4070
|
+
? () => {
|
|
4071
|
+
state.isOpen = false;
|
|
4072
|
+
}
|
|
4073
|
+
: undefined,
|
|
4074
|
+
}, [
|
|
4075
|
+
m('a.btn-floating.btn-large', {
|
|
4076
|
+
className,
|
|
4077
|
+
}, m('i.material-icons', { className: iconClass }, iconName)),
|
|
4078
|
+
buttons &&
|
|
4079
|
+
buttons.length > 0 &&
|
|
4080
|
+
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
|
|
4081
|
+
style: {
|
|
4082
|
+
opacity: state.isOpen ? '1' : '0',
|
|
4083
|
+
transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
|
|
4084
|
+
transition: `all 0.3s ease ${index * 40}ms`,
|
|
4085
|
+
},
|
|
4086
|
+
onclick: (e) => {
|
|
4087
|
+
e.stopPropagation();
|
|
4088
|
+
if (button.onClick)
|
|
4089
|
+
button.onClick(e);
|
|
4090
|
+
},
|
|
4091
|
+
}, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
|
|
4092
|
+
]));
|
|
4093
|
+
},
|
|
4094
|
+
};
|
|
4095
|
+
};
|
|
4096
|
+
|
|
4097
|
+
/**
|
|
4098
|
+
* Pure TypeScript MaterialBox - creates an image lightbox that fills the screen when clicked
|
|
4099
|
+
* No MaterializeCSS dependencies
|
|
4100
|
+
*/
|
|
4101
|
+
const MaterialBox = () => {
|
|
4102
|
+
const state = {
|
|
4103
|
+
isOpen: false,
|
|
4104
|
+
originalImage: null,
|
|
4105
|
+
overlay: null,
|
|
4106
|
+
overlayImage: null,
|
|
4107
|
+
};
|
|
4108
|
+
const openBox = (img, attrs) => {
|
|
4109
|
+
if (state.isOpen)
|
|
4110
|
+
return;
|
|
4111
|
+
state.isOpen = true;
|
|
4112
|
+
state.originalImage = img;
|
|
4113
|
+
if (attrs.onOpenStart)
|
|
4114
|
+
attrs.onOpenStart();
|
|
4115
|
+
// Create overlay
|
|
4116
|
+
const overlay = document.createElement('div');
|
|
4117
|
+
overlay.className = 'materialbox-overlay';
|
|
4118
|
+
overlay.style.cssText = `
|
|
4119
|
+
position: fixed;
|
|
4120
|
+
top: 0;
|
|
4121
|
+
left: 0;
|
|
4122
|
+
right: 0;
|
|
4123
|
+
bottom: 0;
|
|
4124
|
+
background-color: rgba(0, 0, 0, 0.85);
|
|
4125
|
+
z-index: 1000;
|
|
4126
|
+
opacity: 0;
|
|
4127
|
+
transition: opacity ${attrs.inDuration || 275}ms ease;
|
|
4128
|
+
cursor: zoom-out;
|
|
4129
|
+
`;
|
|
4130
|
+
// Create enlarged image
|
|
4131
|
+
const enlargedImg = document.createElement('img');
|
|
4132
|
+
enlargedImg.src = img.src;
|
|
4133
|
+
enlargedImg.alt = img.alt || '';
|
|
4134
|
+
enlargedImg.className = 'materialbox-image';
|
|
4135
|
+
// Get original image dimensions and position
|
|
4136
|
+
const imgRect = img.getBoundingClientRect();
|
|
4137
|
+
const windowWidth = window.innerWidth;
|
|
4138
|
+
const windowHeight = window.innerHeight;
|
|
4139
|
+
// Calculate final size maintaining aspect ratio
|
|
4140
|
+
const aspectRatio = img.naturalWidth / img.naturalHeight;
|
|
4141
|
+
const maxWidth = windowWidth * 0.9;
|
|
4142
|
+
const maxHeight = windowHeight * 0.9;
|
|
4143
|
+
let finalWidth = maxWidth;
|
|
4144
|
+
let finalHeight = maxWidth / aspectRatio;
|
|
4145
|
+
if (finalHeight > maxHeight) {
|
|
4146
|
+
finalHeight = maxHeight;
|
|
4147
|
+
finalWidth = maxHeight * aspectRatio;
|
|
4148
|
+
}
|
|
4149
|
+
// Set initial position and size (same as original image)
|
|
4150
|
+
enlargedImg.style.cssText = `
|
|
4151
|
+
position: fixed;
|
|
4152
|
+
top: ${imgRect.top}px;
|
|
4153
|
+
left: ${imgRect.left}px;
|
|
4154
|
+
width: ${imgRect.width}px;
|
|
4155
|
+
height: ${imgRect.height}px;
|
|
4156
|
+
transition: all ${attrs.inDuration || 275}ms ease;
|
|
4157
|
+
cursor: zoom-out;
|
|
4158
|
+
max-width: none;
|
|
4159
|
+
z-index: 1001;
|
|
4160
|
+
`;
|
|
4161
|
+
// Add caption if provided
|
|
4162
|
+
let caption = null;
|
|
4163
|
+
if (attrs.caption) {
|
|
4164
|
+
caption = document.createElement('div');
|
|
4165
|
+
caption.className = 'materialbox-caption';
|
|
4166
|
+
caption.textContent = attrs.caption;
|
|
4167
|
+
caption.style.cssText = `
|
|
4168
|
+
position: fixed;
|
|
4169
|
+
bottom: 20px;
|
|
4170
|
+
left: 50%;
|
|
4171
|
+
transform: translateX(-50%);
|
|
2740
4172
|
color: white;
|
|
2741
4173
|
font-size: 16px;
|
|
2742
4174
|
text-align: center;
|
|
@@ -2836,7 +4268,7 @@ const MaterialBox = () => {
|
|
|
2836
4268
|
view: ({ attrs }) => {
|
|
2837
4269
|
const { src, alt, width, height, caption, className, style } = attrs, otherAttrs = __rest(attrs, ["src", "alt", "width", "height", "caption", "className", "style"]);
|
|
2838
4270
|
return m('img.materialboxed', Object.assign(Object.assign({}, otherAttrs), { src, alt: alt || '', width,
|
|
2839
|
-
height, className: ['materialboxed', className].filter(Boolean).join(' '), style: Object.assign({ cursor: 'zoom-in', transition: 'opacity 200ms ease' }, style), onclick: (e) => {
|
|
4271
|
+
height, className: ['materialboxed', className].filter(Boolean).join(' ') || undefined, style: Object.assign({ cursor: 'zoom-in', transition: 'opacity 200ms ease' }, style), onclick: (e) => {
|
|
2840
4272
|
e.preventDefault();
|
|
2841
4273
|
openBox(e.target, attrs);
|
|
2842
4274
|
} }));
|
|
@@ -2918,7 +4350,7 @@ const ModalPanel = () => {
|
|
|
2918
4350
|
.filter(Boolean)
|
|
2919
4351
|
.join(' ')
|
|
2920
4352
|
.trim();
|
|
2921
|
-
const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim();
|
|
4353
|
+
const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined;
|
|
2922
4354
|
return m('div', { className: 'modal-container' }, [
|
|
2923
4355
|
// Modal overlay
|
|
2924
4356
|
m('div', {
|
|
@@ -2943,21 +4375,25 @@ const ModalPanel = () => {
|
|
|
2943
4375
|
role: 'dialog',
|
|
2944
4376
|
'aria-labelledby': `${id}-title`,
|
|
2945
4377
|
'aria-describedby': description ? `${id}-desc` : undefined,
|
|
2946
|
-
style: {
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
4378
|
+
style: Object.assign(Object.assign({ display: state.isOpen ? 'flex' : 'none', position: 'fixed' }, (bottomSheet
|
|
4379
|
+
? {
|
|
4380
|
+
// Bottom sheet positioning
|
|
4381
|
+
top: 'auto',
|
|
4382
|
+
bottom: '0',
|
|
4383
|
+
left: '0',
|
|
4384
|
+
right: '0',
|
|
4385
|
+
transform: 'none',
|
|
4386
|
+
maxWidth: '100%',
|
|
4387
|
+
borderRadius: '8px 8px 0 0',
|
|
4388
|
+
}
|
|
4389
|
+
: {
|
|
4390
|
+
// Regular modal positioning
|
|
4391
|
+
top: '50%',
|
|
4392
|
+
left: '50%',
|
|
4393
|
+
transform: 'translate(-50%, -50%)',
|
|
4394
|
+
maxWidth: '75%',
|
|
4395
|
+
borderRadius: '4px',
|
|
4396
|
+
})), { backgroundColor: 'var(--mm-modal-background, #fff)', maxHeight: '85%', overflow: 'auto', zIndex: '1003', padding: '0', flexDirection: 'column', boxShadow: '0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.20)' }),
|
|
2961
4397
|
onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
2962
4398
|
}, [
|
|
2963
4399
|
// Close button
|
|
@@ -2972,135 +4408,37 @@ const ModalPanel = () => {
|
|
|
2972
4408
|
minWidth: 'auto',
|
|
2973
4409
|
lineHeight: 1,
|
|
2974
4410
|
},
|
|
2975
|
-
onclick: () => closeModal(attrs),
|
|
2976
|
-
'aria-label': 'Close modal',
|
|
2977
|
-
}, '×'),
|
|
2978
|
-
// Modal content
|
|
2979
|
-
m('.modal-content', {
|
|
2980
|
-
style: {
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
style: {
|
|
2991
|
-
padding: '4px 6px',
|
|
2992
|
-
borderTop: '1px solid rgba(160,160,160,0.2)',
|
|
2993
|
-
textAlign: 'right',
|
|
2994
|
-
},
|
|
2995
|
-
}, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
|
|
2996
|
-
if (buttonProps.onclick)
|
|
2997
|
-
buttonProps.onclick(e);
|
|
2998
|
-
closeModal(attrs);
|
|
2999
|
-
} })))),
|
|
3000
|
-
]),
|
|
3001
|
-
]);
|
|
3002
|
-
},
|
|
3003
|
-
};
|
|
3004
|
-
};
|
|
3005
|
-
|
|
3006
|
-
/** Component to show a check box */
|
|
3007
|
-
const InputCheckbox = () => {
|
|
3008
|
-
return {
|
|
3009
|
-
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3010
|
-
const checkboxId = inputId || uniqueId();
|
|
3011
|
-
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3012
|
-
m('input[type=checkbox][tabindex=0]', {
|
|
3013
|
-
id: checkboxId,
|
|
3014
|
-
checked,
|
|
3015
|
-
disabled,
|
|
3016
|
-
onclick: onchange
|
|
3017
|
-
? (e) => {
|
|
3018
|
-
if (e.target && typeof e.target.checked !== 'undefined') {
|
|
3019
|
-
onchange(e.target.checked);
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
|
-
: undefined,
|
|
3023
|
-
}),
|
|
3024
|
-
label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
|
|
3025
|
-
]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
|
|
3026
|
-
},
|
|
3027
|
-
};
|
|
3028
|
-
};
|
|
3029
|
-
/** A list of checkboxes */
|
|
3030
|
-
const Options = () => {
|
|
3031
|
-
const state = {};
|
|
3032
|
-
const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
|
|
3033
|
-
const selectAll = (options, callback) => {
|
|
3034
|
-
const allIds = options.map((option) => option.id);
|
|
3035
|
-
state.checkedIds = [...allIds];
|
|
3036
|
-
if (callback)
|
|
3037
|
-
callback(allIds);
|
|
3038
|
-
};
|
|
3039
|
-
const selectNone = (callback) => {
|
|
3040
|
-
state.checkedIds = [];
|
|
3041
|
-
if (callback)
|
|
3042
|
-
callback([]);
|
|
3043
|
-
};
|
|
3044
|
-
return {
|
|
3045
|
-
oninit: ({ attrs: { initialValue, checkedId, id } }) => {
|
|
3046
|
-
const iv = checkedId || initialValue;
|
|
3047
|
-
state.checkedId = checkedId;
|
|
3048
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3049
|
-
state.componentId = id || uniqueId();
|
|
3050
|
-
},
|
|
3051
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
|
|
3052
|
-
const onchange = callback
|
|
3053
|
-
? (propId, checked) => {
|
|
3054
|
-
const checkedIds = state.checkedIds.filter((i) => i !== propId);
|
|
3055
|
-
if (checked) {
|
|
3056
|
-
checkedIds.push(propId);
|
|
3057
|
-
}
|
|
3058
|
-
state.checkedIds = checkedIds;
|
|
3059
|
-
callback(checkedIds);
|
|
3060
|
-
}
|
|
3061
|
-
: undefined;
|
|
3062
|
-
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
3063
|
-
const optionsContent = layout === 'horizontal'
|
|
3064
|
-
? m('div.grid-container', options.map((option) => m(InputCheckbox, {
|
|
3065
|
-
disabled: disabled || option.disabled,
|
|
3066
|
-
label: option.label,
|
|
3067
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3068
|
-
className: option.className || checkboxClass,
|
|
3069
|
-
checked: isChecked(option.id),
|
|
3070
|
-
description: option.description,
|
|
3071
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3072
|
-
})))
|
|
3073
|
-
: options.map((option) => m(InputCheckbox, {
|
|
3074
|
-
disabled: disabled || option.disabled,
|
|
3075
|
-
label: option.label,
|
|
3076
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3077
|
-
className: option.className || checkboxClass,
|
|
3078
|
-
checked: isChecked(option.id),
|
|
3079
|
-
description: option.description,
|
|
3080
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3081
|
-
}));
|
|
3082
|
-
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3083
|
-
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3084
|
-
showSelectAll &&
|
|
3085
|
-
m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
|
|
3086
|
-
m('a', {
|
|
3087
|
-
href: '#',
|
|
3088
|
-
onclick: (e) => {
|
|
3089
|
-
e.preventDefault();
|
|
3090
|
-
selectAll(options, callback);
|
|
3091
|
-
},
|
|
3092
|
-
style: 'margin-right: 15px;',
|
|
3093
|
-
}, 'Select All'),
|
|
3094
|
-
m('a', {
|
|
3095
|
-
href: '#',
|
|
3096
|
-
onclick: (e) => {
|
|
3097
|
-
e.preventDefault();
|
|
3098
|
-
selectNone(callback);
|
|
3099
|
-
},
|
|
3100
|
-
}, 'Select None'),
|
|
4411
|
+
onclick: () => closeModal(attrs),
|
|
4412
|
+
'aria-label': 'Close modal',
|
|
4413
|
+
}, '×'),
|
|
4414
|
+
// Modal content
|
|
4415
|
+
m('.modal-content', {
|
|
4416
|
+
style: {
|
|
4417
|
+
padding: '24px',
|
|
4418
|
+
paddingTop: showCloseButton ? '48px' : '24px',
|
|
4419
|
+
minHeight: 'auto',
|
|
4420
|
+
flex: '1 1 auto',
|
|
4421
|
+
},
|
|
4422
|
+
}, [
|
|
4423
|
+
m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
|
|
4424
|
+
description &&
|
|
4425
|
+
m('div', Object.assign({ id: `${id}-desc` }, (richContent && typeof description === 'string' ? { innerHTML: description } : {})), richContent && typeof description === 'string' ? undefined : description),
|
|
3101
4426
|
]),
|
|
3102
|
-
|
|
3103
|
-
|
|
4427
|
+
// Modal footer with buttons
|
|
4428
|
+
buttons &&
|
|
4429
|
+
buttons.length > 0 &&
|
|
4430
|
+
m('.modal-footer', {
|
|
4431
|
+
style: {
|
|
4432
|
+
padding: '4px 6px',
|
|
4433
|
+
borderTop: '1px solid var(--mm-border-color, rgba(160,160,160,0.2))',
|
|
4434
|
+
textAlign: 'right',
|
|
4435
|
+
},
|
|
4436
|
+
}, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
|
|
4437
|
+
if (buttonProps.onclick)
|
|
4438
|
+
buttonProps.onclick(e);
|
|
4439
|
+
closeModal(attrs);
|
|
4440
|
+
} })))),
|
|
4441
|
+
]),
|
|
3104
4442
|
]);
|
|
3105
4443
|
},
|
|
3106
4444
|
};
|
|
@@ -3370,12 +4708,15 @@ const TimePicker = () => {
|
|
|
3370
4708
|
};
|
|
3371
4709
|
const updateTimeFromInput = (inputValue) => {
|
|
3372
4710
|
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
4711
|
+
let amPmWasProvided = false;
|
|
3373
4712
|
if (options.twelveHour && value.length > 1) {
|
|
3374
4713
|
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
3375
4714
|
state.amOrPm = 'AM';
|
|
4715
|
+
amPmWasProvided = true;
|
|
3376
4716
|
}
|
|
3377
4717
|
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
3378
4718
|
state.amOrPm = 'PM';
|
|
4719
|
+
amPmWasProvided = true;
|
|
3379
4720
|
}
|
|
3380
4721
|
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
3381
4722
|
}
|
|
@@ -3384,21 +4725,33 @@ const TimePicker = () => {
|
|
|
3384
4725
|
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
3385
4726
|
if (options.twelveHour) {
|
|
3386
4727
|
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
4728
|
+
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
3387
4729
|
}
|
|
3388
4730
|
}
|
|
3389
4731
|
let hours = +value[0] || 0;
|
|
3390
4732
|
let minutes = +value[1] || 0;
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
4733
|
+
if (options.twelveHour) {
|
|
4734
|
+
if (!amPmWasProvided) {
|
|
4735
|
+
// No AM/PM was provided, assume this is 24-hour format input - convert it
|
|
4736
|
+
if (hours >= 12) {
|
|
4737
|
+
state.amOrPm = 'PM';
|
|
4738
|
+
if (hours > 12) {
|
|
4739
|
+
hours = hours - 12;
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
else {
|
|
4743
|
+
state.amOrPm = 'AM';
|
|
4744
|
+
if (hours === 0) {
|
|
4745
|
+
hours = 12;
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
3396
4748
|
}
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
4749
|
+
else {
|
|
4750
|
+
// AM/PM was provided, hours are already in 12-hour format
|
|
4751
|
+
// Just handle midnight/noon edge cases
|
|
4752
|
+
if (hours === 0 && state.amOrPm === 'AM') {
|
|
4753
|
+
hours = 12;
|
|
4754
|
+
}
|
|
3402
4755
|
}
|
|
3403
4756
|
}
|
|
3404
4757
|
state.hours = hours;
|
|
@@ -4078,7 +5431,7 @@ const RadioButtons = () => {
|
|
|
4078
5431
|
callback(propId);
|
|
4079
5432
|
}
|
|
4080
5433
|
};
|
|
4081
|
-
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
5434
|
+
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
4082
5435
|
const optionsContent = layout === 'horizontal'
|
|
4083
5436
|
? m('div.grid-container', options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
|
|
4084
5437
|
groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` }))))
|
|
@@ -4351,7 +5704,7 @@ const Switch = () => {
|
|
|
4351
5704
|
view: ({ attrs }) => {
|
|
4352
5705
|
const id = attrs.id || state.id;
|
|
4353
5706
|
const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
|
|
4354
|
-
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
5707
|
+
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
4355
5708
|
return m('div', {
|
|
4356
5709
|
className: cn,
|
|
4357
5710
|
onclick: (e) => {
|
|
@@ -4502,7 +5855,7 @@ const Tabs = () => {
|
|
|
4502
5855
|
},
|
|
4503
5856
|
view: ({ attrs }) => {
|
|
4504
5857
|
const { tabWidth, tabs, className, style, swipeable = false } = attrs;
|
|
4505
|
-
const cn = [tabWidth === 'fill' ? 'tabs-fixed-width' : '', className].filter(Boolean).join(' ').trim();
|
|
5858
|
+
const cn = [tabWidth === 'fill' ? 'tabs-fixed-width' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
4506
5859
|
const anchoredTabs = tabs.map(toAnchored());
|
|
4507
5860
|
const activeTab = setActiveTabId(anchoredTabs, attrs.selectedTabId);
|
|
4508
5861
|
updateIndicator();
|
|
@@ -5488,8 +6841,8 @@ const FileUpload = () => {
|
|
|
5488
6841
|
}
|
|
5489
6842
|
// Check file type
|
|
5490
6843
|
if (attrs.accept) {
|
|
5491
|
-
const acceptedTypes = attrs.accept.split(',').map(type => type.trim());
|
|
5492
|
-
const isAccepted = acceptedTypes.some(acceptedType => {
|
|
6844
|
+
const acceptedTypes = attrs.accept.split(',').map((type) => type.trim());
|
|
6845
|
+
const isAccepted = acceptedTypes.some((acceptedType) => {
|
|
5493
6846
|
if (acceptedType.startsWith('.')) {
|
|
5494
6847
|
// Extension check
|
|
5495
6848
|
return file.name.toLowerCase().endsWith(acceptedType.toLowerCase());
|
|
@@ -5549,11 +6902,11 @@ const FileUpload = () => {
|
|
|
5549
6902
|
}
|
|
5550
6903
|
// Notify parent component
|
|
5551
6904
|
if (attrs.onFilesSelected) {
|
|
5552
|
-
attrs.onFilesSelected(state.files.filter(f => !f.uploadError));
|
|
6905
|
+
attrs.onFilesSelected(state.files.filter((f) => !f.uploadError));
|
|
5553
6906
|
}
|
|
5554
6907
|
};
|
|
5555
6908
|
const removeFile = (fileToRemove, attrs) => {
|
|
5556
|
-
state.files = state.files.filter(file => file !== fileToRemove);
|
|
6909
|
+
state.files = state.files.filter((file) => file !== fileToRemove);
|
|
5557
6910
|
if (attrs.onFileRemoved) {
|
|
5558
6911
|
attrs.onFileRemoved(fileToRemove);
|
|
5559
6912
|
}
|
|
@@ -5572,11 +6925,11 @@ const FileUpload = () => {
|
|
|
5572
6925
|
id: uniqueId(),
|
|
5573
6926
|
files: [],
|
|
5574
6927
|
isDragOver: false,
|
|
5575
|
-
isUploading: false
|
|
6928
|
+
isUploading: false,
|
|
5576
6929
|
};
|
|
5577
6930
|
},
|
|
5578
6931
|
view: ({ attrs }) => {
|
|
5579
|
-
const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error } = attrs;
|
|
6932
|
+
const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error, } = attrs;
|
|
5580
6933
|
return m('.file-upload-container', { class: className }, [
|
|
5581
6934
|
// Upload area
|
|
5582
6935
|
m('.file-upload-area', {
|
|
@@ -5584,8 +6937,10 @@ const FileUpload = () => {
|
|
|
5584
6937
|
state.isDragOver ? 'drag-over' : '',
|
|
5585
6938
|
disabled ? 'disabled' : '',
|
|
5586
6939
|
error ? 'error' : '',
|
|
5587
|
-
state.files.length > 0 ? 'has-files' : ''
|
|
5588
|
-
]
|
|
6940
|
+
state.files.length > 0 ? 'has-files' : '',
|
|
6941
|
+
]
|
|
6942
|
+
.filter(Boolean)
|
|
6943
|
+
.join(' ') || undefined,
|
|
5589
6944
|
ondragover: (e) => {
|
|
5590
6945
|
if (disabled)
|
|
5591
6946
|
return;
|
|
@@ -5616,7 +6971,7 @@ const FileUpload = () => {
|
|
|
5616
6971
|
return;
|
|
5617
6972
|
const input = document.getElementById(state.id);
|
|
5618
6973
|
input === null || input === void 0 ? void 0 : input.click();
|
|
5619
|
-
}
|
|
6974
|
+
},
|
|
5620
6975
|
}, [
|
|
5621
6976
|
m('input[type="file"]', {
|
|
5622
6977
|
id: state.id,
|
|
@@ -5629,57 +6984,55 @@ const FileUpload = () => {
|
|
|
5629
6984
|
if (target.files) {
|
|
5630
6985
|
handleFiles(target.files, attrs);
|
|
5631
6986
|
}
|
|
5632
|
-
}
|
|
6987
|
+
},
|
|
5633
6988
|
}),
|
|
5634
6989
|
m('.file-upload-content', [
|
|
5635
6990
|
m('i.material-icons.file-upload-icon', 'cloud_upload'),
|
|
5636
6991
|
m('p.file-upload-label', label),
|
|
5637
6992
|
helperText && m('p.file-upload-helper', helperText),
|
|
5638
|
-
accept && m('p.file-upload-types', `Accepted: ${accept}`)
|
|
5639
|
-
])
|
|
6993
|
+
accept && m('p.file-upload-types', `Accepted: ${accept}`),
|
|
6994
|
+
]),
|
|
5640
6995
|
]),
|
|
5641
6996
|
// Error message
|
|
5642
6997
|
error && m('.file-upload-error', error),
|
|
5643
6998
|
// File list
|
|
5644
|
-
state.files.length > 0 &&
|
|
5645
|
-
m('
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
m('img', { src: file.preview, alt: file.name })
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
6999
|
+
state.files.length > 0 &&
|
|
7000
|
+
m('.file-upload-list', [
|
|
7001
|
+
m('h6', 'Selected Files:'),
|
|
7002
|
+
state.files.map((file) => m('.file-upload-item', { key: file.name + file.size }, [
|
|
7003
|
+
// Preview thumbnail
|
|
7004
|
+
showPreview && file.preview && m('.file-preview', [m('img', { src: file.preview, alt: file.name })]),
|
|
7005
|
+
// File info
|
|
7006
|
+
m('.file-info', [
|
|
7007
|
+
m('.file-name', file.name),
|
|
7008
|
+
m('.file-details', [
|
|
7009
|
+
m('span.file-size', formatFileSize(file.size)),
|
|
7010
|
+
file.type && m('span.file-type', file.type),
|
|
7011
|
+
]),
|
|
7012
|
+
// Progress bar (if uploading)
|
|
7013
|
+
file.uploadProgress !== undefined &&
|
|
7014
|
+
m('.file-progress', [
|
|
7015
|
+
m('.progress', [
|
|
7016
|
+
m('.determinate', {
|
|
7017
|
+
style: { width: `${file.uploadProgress}%` },
|
|
7018
|
+
}),
|
|
7019
|
+
]),
|
|
7020
|
+
]),
|
|
7021
|
+
// Error message
|
|
7022
|
+
file.uploadError && m('.file-error', file.uploadError),
|
|
5665
7023
|
]),
|
|
5666
|
-
//
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
}, [
|
|
5677
|
-
m('i.material-icons', 'close')
|
|
5678
|
-
])
|
|
5679
|
-
]))
|
|
5680
|
-
])
|
|
7024
|
+
// Remove button
|
|
7025
|
+
m('button.btn-flat.file-remove', {
|
|
7026
|
+
onclick: (e) => {
|
|
7027
|
+
e.stopPropagation();
|
|
7028
|
+
removeFile(file, attrs);
|
|
7029
|
+
},
|
|
7030
|
+
title: 'Remove file',
|
|
7031
|
+
}, [m('i.material-icons', 'close')]),
|
|
7032
|
+
])),
|
|
7033
|
+
]),
|
|
5681
7034
|
]);
|
|
5682
|
-
}
|
|
7035
|
+
},
|
|
5683
7036
|
};
|
|
5684
7037
|
};
|
|
5685
7038
|
|
|
@@ -5709,7 +7062,7 @@ const Sidenav = () => {
|
|
|
5709
7062
|
state = {
|
|
5710
7063
|
id: attrs.id || uniqueId(),
|
|
5711
7064
|
isOpen: attrs.isOpen || false,
|
|
5712
|
-
isAnimating: false
|
|
7065
|
+
isAnimating: false,
|
|
5713
7066
|
};
|
|
5714
7067
|
// Set up keyboard listener
|
|
5715
7068
|
if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
|
|
@@ -5738,34 +7091,33 @@ const Sidenav = () => {
|
|
|
5738
7091
|
}
|
|
5739
7092
|
},
|
|
5740
7093
|
view: ({ attrs, children }) => {
|
|
5741
|
-
const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false } = attrs;
|
|
7094
|
+
const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, } = attrs;
|
|
5742
7095
|
const isOpen = state.isOpen;
|
|
5743
7096
|
return [
|
|
5744
7097
|
// Backdrop (using existing materialize class)
|
|
5745
|
-
showBackdrop &&
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
7098
|
+
showBackdrop &&
|
|
7099
|
+
mode === 'overlay' &&
|
|
7100
|
+
m('.sidenav-overlay', {
|
|
7101
|
+
style: {
|
|
7102
|
+
display: isOpen ? 'block' : 'none',
|
|
7103
|
+
opacity: isOpen ? '1' : '0',
|
|
7104
|
+
},
|
|
7105
|
+
onclick: () => handleBackdropClick(attrs),
|
|
7106
|
+
}),
|
|
5752
7107
|
// Sidenav (using existing materialize structure)
|
|
5753
7108
|
m('ul.sidenav', {
|
|
5754
7109
|
id: state.id,
|
|
5755
|
-
class: [
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
className
|
|
5759
|
-
].filter(Boolean).join(' '),
|
|
7110
|
+
class: [position === 'right' ? 'right-aligned' : '', fixed ? 'sidenav-fixed' : '', className]
|
|
7111
|
+
.filter(Boolean)
|
|
7112
|
+
.join(' ') || undefined,
|
|
5760
7113
|
style: {
|
|
5761
7114
|
width: `${width}px`,
|
|
5762
|
-
transform: isOpen ? 'translateX(0)' :
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
}, children)
|
|
7115
|
+
transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
|
|
7116
|
+
'transition-duration': `${animationDuration}ms`,
|
|
7117
|
+
},
|
|
7118
|
+
}, children),
|
|
5767
7119
|
];
|
|
5768
|
-
}
|
|
7120
|
+
},
|
|
5769
7121
|
};
|
|
5770
7122
|
};
|
|
5771
7123
|
/**
|
|
@@ -5775,37 +7127,30 @@ const Sidenav = () => {
|
|
|
5775
7127
|
const SidenavItem = () => {
|
|
5776
7128
|
return {
|
|
5777
7129
|
view: ({ attrs, children }) => {
|
|
5778
|
-
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false } = attrs;
|
|
7130
|
+
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, } = attrs;
|
|
5779
7131
|
if (divider) {
|
|
5780
7132
|
return m('li.divider');
|
|
5781
7133
|
}
|
|
5782
7134
|
if (subheader) {
|
|
5783
7135
|
return m('li.subheader', text || children);
|
|
5784
7136
|
}
|
|
5785
|
-
const itemClasses = [
|
|
5786
|
-
|
|
5787
|
-
disabled ? 'disabled' : '',
|
|
5788
|
-
className
|
|
5789
|
-
].filter(Boolean).join(' ');
|
|
5790
|
-
const content = [
|
|
5791
|
-
icon && m('i.material-icons', icon),
|
|
5792
|
-
text || children
|
|
5793
|
-
];
|
|
7137
|
+
const itemClasses = [active ? 'active' : '', disabled ? 'disabled' : '', className].filter(Boolean).join(' ') || undefined;
|
|
7138
|
+
const content = [icon && m('i.material-icons', icon), text || children];
|
|
5794
7139
|
if (href && !disabled) {
|
|
5795
7140
|
return m('li', { class: itemClasses }, [
|
|
5796
7141
|
m('a', {
|
|
5797
7142
|
href,
|
|
5798
|
-
onclick: disabled ? undefined : onclick
|
|
5799
|
-
}, content)
|
|
7143
|
+
onclick: disabled ? undefined : onclick,
|
|
7144
|
+
}, content),
|
|
5800
7145
|
]);
|
|
5801
7146
|
}
|
|
5802
7147
|
return m('li', { class: itemClasses }, [
|
|
5803
7148
|
m('a', {
|
|
5804
7149
|
onclick: disabled ? undefined : onclick,
|
|
5805
|
-
href: '#!'
|
|
5806
|
-
}, content)
|
|
7150
|
+
href: '#!',
|
|
7151
|
+
}, content),
|
|
5807
7152
|
]);
|
|
5808
|
-
}
|
|
7153
|
+
},
|
|
5809
7154
|
};
|
|
5810
7155
|
};
|
|
5811
7156
|
/**
|
|
@@ -5856,7 +7201,7 @@ class SidenavManager {
|
|
|
5856
7201
|
const Breadcrumb = () => {
|
|
5857
7202
|
return {
|
|
5858
7203
|
view: ({ attrs }) => {
|
|
5859
|
-
const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false } = attrs;
|
|
7204
|
+
const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false, } = attrs;
|
|
5860
7205
|
if (items.length === 0) {
|
|
5861
7206
|
return null;
|
|
5862
7207
|
}
|
|
@@ -5865,52 +7210,46 @@ const Breadcrumb = () => {
|
|
|
5865
7210
|
if (maxItems && items.length > maxItems) {
|
|
5866
7211
|
const firstItem = items[0];
|
|
5867
7212
|
const lastItems = items.slice(-(maxItems - 2));
|
|
5868
|
-
displayItems = [
|
|
5869
|
-
firstItem,
|
|
5870
|
-
{ text: '...', disabled: true, className: 'breadcrumb-ellipsis' },
|
|
5871
|
-
...lastItems
|
|
5872
|
-
];
|
|
7213
|
+
displayItems = [firstItem, { text: '...', disabled: true, className: 'breadcrumb-ellipsis' }, ...lastItems];
|
|
5873
7214
|
}
|
|
5874
7215
|
return m('nav.breadcrumb', { class: className }, [
|
|
5875
|
-
m('ol.breadcrumb-list', displayItems
|
|
7216
|
+
m('ol.breadcrumb-list', displayItems
|
|
7217
|
+
.map((item, index) => {
|
|
5876
7218
|
const isLast = index === displayItems.length - 1;
|
|
5877
7219
|
const isFirst = index === 0;
|
|
5878
7220
|
return [
|
|
5879
7221
|
// Breadcrumb item
|
|
5880
7222
|
m('li.breadcrumb-item', {
|
|
5881
|
-
class: [
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
item.className || ''
|
|
5885
|
-
].filter(Boolean).join(' ')
|
|
7223
|
+
class: [item.active || isLast ? 'active' : '', item.disabled ? 'disabled' : '', item.className || '']
|
|
7224
|
+
.filter(Boolean)
|
|
7225
|
+
.join(' ') || undefined,
|
|
5886
7226
|
}, [
|
|
5887
|
-
item.href && !item.disabled && !isLast
|
|
5888
|
-
// Link item
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
// Text item (active or disabled)
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
7227
|
+
item.href && !item.disabled && !isLast
|
|
7228
|
+
? // Link item
|
|
7229
|
+
m('a.breadcrumb-link', {
|
|
7230
|
+
href: item.href,
|
|
7231
|
+
onclick: item.onclick,
|
|
7232
|
+
}, [
|
|
7233
|
+
showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
7234
|
+
showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
7235
|
+
m('span.breadcrumb-text', item.text),
|
|
7236
|
+
])
|
|
7237
|
+
: // Text item (active or disabled)
|
|
7238
|
+
m('span.breadcrumb-text', {
|
|
7239
|
+
onclick: item.disabled ? undefined : item.onclick,
|
|
7240
|
+
}, [
|
|
7241
|
+
showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
7242
|
+
showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
7243
|
+
item.text,
|
|
7244
|
+
]),
|
|
5905
7245
|
]),
|
|
5906
7246
|
// Separator (except for last item)
|
|
5907
|
-
!isLast && m('li.breadcrumb-separator', [
|
|
5908
|
-
m('i.material-icons', separator)
|
|
5909
|
-
])
|
|
7247
|
+
!isLast && m('li.breadcrumb-separator', [m('i.material-icons', separator)]),
|
|
5910
7248
|
];
|
|
5911
|
-
})
|
|
7249
|
+
})
|
|
7250
|
+
.reduce((acc, val) => acc.concat(val), [])),
|
|
5912
7251
|
]);
|
|
5913
|
-
}
|
|
7252
|
+
},
|
|
5914
7253
|
};
|
|
5915
7254
|
};
|
|
5916
7255
|
/**
|
|
@@ -5923,7 +7262,7 @@ const createBreadcrumb = (path, basePath = '/') => {
|
|
|
5923
7262
|
items.push({
|
|
5924
7263
|
text: 'Home',
|
|
5925
7264
|
href: basePath,
|
|
5926
|
-
icon: 'home'
|
|
7265
|
+
icon: 'home',
|
|
5927
7266
|
});
|
|
5928
7267
|
// Add path segments
|
|
5929
7268
|
let currentPath = basePath;
|
|
@@ -5933,7 +7272,7 @@ const createBreadcrumb = (path, basePath = '/') => {
|
|
|
5933
7272
|
items.push({
|
|
5934
7273
|
text: segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' '),
|
|
5935
7274
|
href: isLast ? undefined : currentPath,
|
|
5936
|
-
active: isLast
|
|
7275
|
+
active: isLast,
|
|
5937
7276
|
});
|
|
5938
7277
|
});
|
|
5939
7278
|
return items;
|
|
@@ -5952,19 +7291,18 @@ class BreadcrumbManager {
|
|
|
5952
7291
|
items.push({
|
|
5953
7292
|
text: 'Home',
|
|
5954
7293
|
href: '/',
|
|
5955
|
-
icon: 'home'
|
|
7294
|
+
icon: 'home',
|
|
5956
7295
|
});
|
|
5957
7296
|
let currentPath = '';
|
|
5958
7297
|
segments.forEach((segment, index) => {
|
|
5959
7298
|
currentPath += '/' + segment;
|
|
5960
7299
|
const isLast = index === segments.length - 1;
|
|
5961
7300
|
// Use custom text from config or format segment
|
|
5962
|
-
const text = routeConfig[currentPath] ||
|
|
5963
|
-
segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
|
|
7301
|
+
const text = routeConfig[currentPath] || segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
|
|
5964
7302
|
items.push({
|
|
5965
7303
|
text,
|
|
5966
7304
|
href: isLast ? undefined : currentPath,
|
|
5967
|
-
active: isLast
|
|
7305
|
+
active: isLast,
|
|
5968
7306
|
});
|
|
5969
7307
|
});
|
|
5970
7308
|
return items;
|
|
@@ -5976,7 +7314,7 @@ class BreadcrumbManager {
|
|
|
5976
7314
|
return hierarchy.map((item, index) => ({
|
|
5977
7315
|
text: item[textKey],
|
|
5978
7316
|
href: index === hierarchy.length - 1 ? undefined : item[pathKey],
|
|
5979
|
-
active: index === hierarchy.length - 1
|
|
7317
|
+
active: index === hierarchy.length - 1,
|
|
5980
7318
|
}));
|
|
5981
7319
|
}
|
|
5982
7320
|
}
|
|
@@ -6110,7 +7448,7 @@ const Wizard = () => {
|
|
|
6110
7448
|
hasError ? 'error' : '',
|
|
6111
7449
|
step.disabled ? 'disabled' : '',
|
|
6112
7450
|
step.optional ? 'optional' : ''
|
|
6113
|
-
].filter(Boolean).join(' '),
|
|
7451
|
+
].filter(Boolean).join(' ') || undefined,
|
|
6114
7452
|
onclick: allowHeaderNavigation && !step.disabled ?
|
|
6115
7453
|
() => goToStep(index, attrs) : undefined
|
|
6116
7454
|
}, [
|
|
@@ -6184,6 +7522,729 @@ const Stepper = () => {
|
|
|
6184
7522
|
};
|
|
6185
7523
|
};
|
|
6186
7524
|
|
|
7525
|
+
// Utility function to check if a node is the last in its branch
|
|
7526
|
+
const isNodeLastInBranch = (nodePath, rootNodes) => {
|
|
7527
|
+
// Navigate to the node's position and check if it's the last child at every level
|
|
7528
|
+
let currentNodes = rootNodes;
|
|
7529
|
+
for (let i = 0; i < nodePath.length; i++) {
|
|
7530
|
+
const index = nodePath[i];
|
|
7531
|
+
const isLastAtThisLevel = index === currentNodes.length - 1;
|
|
7532
|
+
// If this is not the last child at this level, then this node is not last in branch
|
|
7533
|
+
if (!isLastAtThisLevel) {
|
|
7534
|
+
return false;
|
|
7535
|
+
}
|
|
7536
|
+
// Move to the next level if it exists
|
|
7537
|
+
if (i < nodePath.length - 1) {
|
|
7538
|
+
const currentNode = currentNodes[index];
|
|
7539
|
+
if (currentNode.children) {
|
|
7540
|
+
currentNodes = currentNode.children;
|
|
7541
|
+
}
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
return true;
|
|
7545
|
+
};
|
|
7546
|
+
const TreeNodeComponent = () => {
|
|
7547
|
+
return {
|
|
7548
|
+
view: ({ attrs }) => {
|
|
7549
|
+
const { node, level, isSelected, isExpanded, isFocused, showConnectors, iconType, selectionMode, onToggleExpand, onToggleSelect, onFocus, } = attrs;
|
|
7550
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
7551
|
+
const indentLevel = level * 24; // 24px per level
|
|
7552
|
+
return m('li.tree-node', {
|
|
7553
|
+
class: [
|
|
7554
|
+
isSelected && 'selected',
|
|
7555
|
+
isFocused && 'focused',
|
|
7556
|
+
node.disabled && 'disabled',
|
|
7557
|
+
hasChildren && 'has-children',
|
|
7558
|
+
attrs.isLastInBranch && 'tree-last-in-branch',
|
|
7559
|
+
]
|
|
7560
|
+
.filter(Boolean)
|
|
7561
|
+
.join(' ') || undefined,
|
|
7562
|
+
'data-node-id': node.id,
|
|
7563
|
+
'data-level': level,
|
|
7564
|
+
}, [
|
|
7565
|
+
// Node content
|
|
7566
|
+
m('.tree-node-content', {
|
|
7567
|
+
style: {
|
|
7568
|
+
paddingLeft: `${indentLevel}px`,
|
|
7569
|
+
},
|
|
7570
|
+
onclick: node.disabled
|
|
7571
|
+
? undefined
|
|
7572
|
+
: () => {
|
|
7573
|
+
if (selectionMode !== 'none') {
|
|
7574
|
+
onToggleSelect(node.id);
|
|
7575
|
+
}
|
|
7576
|
+
onFocus(node.id);
|
|
7577
|
+
},
|
|
7578
|
+
onkeydown: (e) => {
|
|
7579
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
7580
|
+
e.preventDefault();
|
|
7581
|
+
if (!node.disabled && selectionMode !== 'none') {
|
|
7582
|
+
onToggleSelect(node.id);
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
},
|
|
7586
|
+
tabindex: node.disabled ? -1 : 0,
|
|
7587
|
+
role: selectionMode === 'multiple' ? 'option' : 'treeitem',
|
|
7588
|
+
'aria-selected': selectionMode !== 'none' ? isSelected.toString() : undefined,
|
|
7589
|
+
'aria-expanded': hasChildren ? isExpanded.toString() : undefined,
|
|
7590
|
+
'aria-disabled': node.disabled ? 'true' : undefined,
|
|
7591
|
+
}, [
|
|
7592
|
+
// Connector lines
|
|
7593
|
+
showConnectors &&
|
|
7594
|
+
level > 0 &&
|
|
7595
|
+
m('.tree-connectors', Array.from({ length: level }, (_, i) => m('.tree-connector', {
|
|
7596
|
+
key: i,
|
|
7597
|
+
style: { left: `${i * 24 + 12}px` },
|
|
7598
|
+
}))),
|
|
7599
|
+
// Expand/collapse icon or spacer
|
|
7600
|
+
hasChildren
|
|
7601
|
+
? m('.tree-expand-icon', {
|
|
7602
|
+
onclick: (e) => {
|
|
7603
|
+
e.stopPropagation();
|
|
7604
|
+
if (!node.disabled) {
|
|
7605
|
+
onToggleExpand(node.id);
|
|
7606
|
+
}
|
|
7607
|
+
},
|
|
7608
|
+
class: iconType,
|
|
7609
|
+
}, [
|
|
7610
|
+
iconType === 'plus-minus'
|
|
7611
|
+
? m('span.tree-plus-minus', isExpanded ? '−' : '+')
|
|
7612
|
+
: iconType === 'triangle'
|
|
7613
|
+
? m('span.tree-triangle', { class: isExpanded ? 'expanded' : undefined }, '▶')
|
|
7614
|
+
: iconType === 'chevron'
|
|
7615
|
+
? m(MaterialIcon, {
|
|
7616
|
+
name: 'chevron',
|
|
7617
|
+
direction: isExpanded ? 'down' : 'right',
|
|
7618
|
+
class: 'tree-chevron-icon',
|
|
7619
|
+
})
|
|
7620
|
+
: m(MaterialIcon, {
|
|
7621
|
+
name: 'caret',
|
|
7622
|
+
direction: isExpanded ? 'down' : 'right',
|
|
7623
|
+
class: 'tree-caret-icon',
|
|
7624
|
+
}),
|
|
7625
|
+
])
|
|
7626
|
+
: m('.tree-expand-spacer'), // Spacer for alignment
|
|
7627
|
+
// Selection indicator for multiple selection
|
|
7628
|
+
selectionMode === 'multiple' &&
|
|
7629
|
+
m('.tree-selection-indicator', [
|
|
7630
|
+
m('input[type=checkbox]', {
|
|
7631
|
+
checked: isSelected,
|
|
7632
|
+
disabled: node.disabled,
|
|
7633
|
+
onchange: () => {
|
|
7634
|
+
if (!node.disabled) {
|
|
7635
|
+
onToggleSelect(node.id);
|
|
7636
|
+
}
|
|
7637
|
+
},
|
|
7638
|
+
onclick: (e) => e.stopPropagation(),
|
|
7639
|
+
}),
|
|
7640
|
+
]),
|
|
7641
|
+
// Node icon (optional)
|
|
7642
|
+
node.icon && m('i.tree-node-icon.material-icons', node.icon),
|
|
7643
|
+
// Node label
|
|
7644
|
+
m('span.tree-node-label', node.label),
|
|
7645
|
+
]),
|
|
7646
|
+
// Children (recursive)
|
|
7647
|
+
hasChildren &&
|
|
7648
|
+
isExpanded &&
|
|
7649
|
+
m('ul.tree-children', {
|
|
7650
|
+
role: 'group',
|
|
7651
|
+
'aria-expanded': 'true',
|
|
7652
|
+
}, node.children.map((child, childIndex) => {
|
|
7653
|
+
var _a, _b, _c, _d, _e, _f;
|
|
7654
|
+
// Calculate state for each child using treeState
|
|
7655
|
+
const childIsSelected = (_b = (_a = attrs.treeState) === null || _a === void 0 ? void 0 : _a.selectedIds.has(child.id)) !== null && _b !== void 0 ? _b : false;
|
|
7656
|
+
const childIsExpanded = (_d = (_c = attrs.treeState) === null || _c === void 0 ? void 0 : _c.expandedIds.has(child.id)) !== null && _d !== void 0 ? _d : false;
|
|
7657
|
+
const childIsFocused = ((_e = attrs.treeState) === null || _e === void 0 ? void 0 : _e.focusedNodeId) === child.id;
|
|
7658
|
+
// Calculate if this child is last in branch
|
|
7659
|
+
const childPath = [...(attrs.currentPath || []), childIndex];
|
|
7660
|
+
const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data) ?
|
|
7661
|
+
isNodeLastInBranch(childPath, attrs.treeAttrs.data) : false;
|
|
7662
|
+
return m(TreeNodeComponent, {
|
|
7663
|
+
key: child.id,
|
|
7664
|
+
node: child,
|
|
7665
|
+
level: level + 1,
|
|
7666
|
+
isSelected: childIsSelected,
|
|
7667
|
+
isExpanded: childIsExpanded,
|
|
7668
|
+
isFocused: childIsFocused,
|
|
7669
|
+
showConnectors,
|
|
7670
|
+
iconType,
|
|
7671
|
+
selectionMode,
|
|
7672
|
+
onToggleExpand,
|
|
7673
|
+
onToggleSelect,
|
|
7674
|
+
onFocus,
|
|
7675
|
+
isLastInBranch: childIsLastInBranch,
|
|
7676
|
+
currentPath: childPath,
|
|
7677
|
+
treeState: attrs.treeState,
|
|
7678
|
+
treeAttrs: attrs.treeAttrs,
|
|
7679
|
+
});
|
|
7680
|
+
})),
|
|
7681
|
+
]);
|
|
7682
|
+
},
|
|
7683
|
+
};
|
|
7684
|
+
};
|
|
7685
|
+
const TreeView = () => {
|
|
7686
|
+
const state = {
|
|
7687
|
+
selectedIds: new Set(),
|
|
7688
|
+
expandedIds: new Set(),
|
|
7689
|
+
focusedNodeId: null,
|
|
7690
|
+
treeMap: new Map(),
|
|
7691
|
+
};
|
|
7692
|
+
const buildTreeMap = (nodes, map) => {
|
|
7693
|
+
nodes.forEach((node) => {
|
|
7694
|
+
map.set(node.id, node);
|
|
7695
|
+
if (node.children) {
|
|
7696
|
+
buildTreeMap(node.children, map);
|
|
7697
|
+
}
|
|
7698
|
+
});
|
|
7699
|
+
};
|
|
7700
|
+
const initializeExpandedNodes = (nodes) => {
|
|
7701
|
+
nodes.forEach((node) => {
|
|
7702
|
+
if (node.expanded) {
|
|
7703
|
+
state.expandedIds.add(node.id);
|
|
7704
|
+
}
|
|
7705
|
+
if (node.children) {
|
|
7706
|
+
initializeExpandedNodes(node.children);
|
|
7707
|
+
}
|
|
7708
|
+
});
|
|
7709
|
+
};
|
|
7710
|
+
const handleToggleExpand = (nodeId, attrs) => {
|
|
7711
|
+
var _a;
|
|
7712
|
+
const isExpanded = state.expandedIds.has(nodeId);
|
|
7713
|
+
if (isExpanded) {
|
|
7714
|
+
state.expandedIds.delete(nodeId);
|
|
7715
|
+
}
|
|
7716
|
+
else {
|
|
7717
|
+
state.expandedIds.add(nodeId);
|
|
7718
|
+
}
|
|
7719
|
+
(_a = attrs.onexpand) === null || _a === void 0 ? void 0 : _a.call(attrs, { nodeId, expanded: !isExpanded });
|
|
7720
|
+
};
|
|
7721
|
+
const handleToggleSelect = (nodeId, attrs) => {
|
|
7722
|
+
var _a;
|
|
7723
|
+
const { selectionMode = 'single' } = attrs;
|
|
7724
|
+
if (selectionMode === 'single') {
|
|
7725
|
+
state.selectedIds.clear();
|
|
7726
|
+
state.selectedIds.add(nodeId);
|
|
7727
|
+
}
|
|
7728
|
+
else if (selectionMode === 'multiple') {
|
|
7729
|
+
if (state.selectedIds.has(nodeId)) {
|
|
7730
|
+
state.selectedIds.delete(nodeId);
|
|
7731
|
+
}
|
|
7732
|
+
else {
|
|
7733
|
+
state.selectedIds.add(nodeId);
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
(_a = attrs.onselection) === null || _a === void 0 ? void 0 : _a.call(attrs, Array.from(state.selectedIds));
|
|
7737
|
+
};
|
|
7738
|
+
const handleFocus = (nodeId) => {
|
|
7739
|
+
state.focusedNodeId = nodeId;
|
|
7740
|
+
};
|
|
7741
|
+
const renderNodes = (nodes, attrs, level = 0, parentPath = []) => {
|
|
7742
|
+
return nodes.map((node, index) => {
|
|
7743
|
+
var _a, _b, _c;
|
|
7744
|
+
const isSelected = state.selectedIds.has(node.id);
|
|
7745
|
+
const isExpanded = state.expandedIds.has(node.id);
|
|
7746
|
+
const isFocused = state.focusedNodeId === node.id;
|
|
7747
|
+
const currentPath = [...parentPath, index];
|
|
7748
|
+
const isLastInBranch = isNodeLastInBranch(currentPath, attrs.data);
|
|
7749
|
+
return m(TreeNodeComponent, {
|
|
7750
|
+
key: node.id,
|
|
7751
|
+
node,
|
|
7752
|
+
level,
|
|
7753
|
+
isSelected,
|
|
7754
|
+
isExpanded,
|
|
7755
|
+
isFocused,
|
|
7756
|
+
showConnectors: (_a = attrs.showConnectors) !== null && _a !== void 0 ? _a : true,
|
|
7757
|
+
iconType: (_b = attrs.iconType) !== null && _b !== void 0 ? _b : 'caret',
|
|
7758
|
+
selectionMode: (_c = attrs.selectionMode) !== null && _c !== void 0 ? _c : 'single',
|
|
7759
|
+
onToggleExpand: (nodeId) => handleToggleExpand(nodeId, attrs),
|
|
7760
|
+
onToggleSelect: (nodeId) => handleToggleSelect(nodeId, attrs),
|
|
7761
|
+
onFocus: handleFocus,
|
|
7762
|
+
isLastInBranch,
|
|
7763
|
+
currentPath,
|
|
7764
|
+
// Pass state and attrs for recursive rendering
|
|
7765
|
+
treeState: state,
|
|
7766
|
+
treeAttrs: attrs,
|
|
7767
|
+
});
|
|
7768
|
+
});
|
|
7769
|
+
};
|
|
7770
|
+
return {
|
|
7771
|
+
oninit: ({ attrs }) => {
|
|
7772
|
+
// Build internal tree map for efficient lookups
|
|
7773
|
+
buildTreeMap(attrs.data, state.treeMap);
|
|
7774
|
+
// Initialize expanded nodes from data
|
|
7775
|
+
initializeExpandedNodes(attrs.data);
|
|
7776
|
+
// Initialize selected nodes from props
|
|
7777
|
+
if (attrs.selectedIds) {
|
|
7778
|
+
state.selectedIds = new Set(attrs.selectedIds);
|
|
7779
|
+
}
|
|
7780
|
+
},
|
|
7781
|
+
onupdate: ({ attrs }) => {
|
|
7782
|
+
// Sync selectedIds prop with internal state
|
|
7783
|
+
if (attrs.selectedIds) {
|
|
7784
|
+
const newSelection = new Set(attrs.selectedIds);
|
|
7785
|
+
if (newSelection.size !== state.selectedIds.size ||
|
|
7786
|
+
!Array.from(newSelection).every((id) => state.selectedIds.has(id))) {
|
|
7787
|
+
state.selectedIds = newSelection;
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7790
|
+
},
|
|
7791
|
+
view: ({ attrs }) => {
|
|
7792
|
+
const { data, className, style, id, selectionMode = 'single', showConnectors = true } = attrs;
|
|
7793
|
+
return m('div.tree-view', {
|
|
7794
|
+
class: [
|
|
7795
|
+
className,
|
|
7796
|
+
showConnectors && 'show-connectors'
|
|
7797
|
+
].filter(Boolean).join(' ') || undefined,
|
|
7798
|
+
style,
|
|
7799
|
+
id,
|
|
7800
|
+
role: selectionMode === 'multiple' ? 'listbox' : 'tree',
|
|
7801
|
+
'aria-multiselectable': selectionMode === 'multiple' ? 'true' : 'false',
|
|
7802
|
+
}, [
|
|
7803
|
+
m('ul.tree-root', {
|
|
7804
|
+
role: 'group',
|
|
7805
|
+
}, renderNodes(data, attrs)),
|
|
7806
|
+
]);
|
|
7807
|
+
},
|
|
7808
|
+
};
|
|
7809
|
+
};
|
|
7810
|
+
|
|
7811
|
+
/**
|
|
7812
|
+
* Timeline Component
|
|
7813
|
+
* Displays a sequence of events in chronological order with connecting lines
|
|
7814
|
+
* Supports both vertical and horizontal orientations with Material Design styling
|
|
7815
|
+
*/
|
|
7816
|
+
const Timeline = () => {
|
|
7817
|
+
const formatTimestamp = (timestamp) => {
|
|
7818
|
+
if (!timestamp)
|
|
7819
|
+
return '';
|
|
7820
|
+
if (typeof timestamp === 'string')
|
|
7821
|
+
return timestamp;
|
|
7822
|
+
return timestamp.toLocaleDateString();
|
|
7823
|
+
};
|
|
7824
|
+
const getColorClass = (color) => {
|
|
7825
|
+
switch (color) {
|
|
7826
|
+
case 'primary':
|
|
7827
|
+
return 'timeline-primary';
|
|
7828
|
+
case 'secondary':
|
|
7829
|
+
return 'timeline-secondary';
|
|
7830
|
+
case 'success':
|
|
7831
|
+
return 'timeline-success';
|
|
7832
|
+
case 'warning':
|
|
7833
|
+
return 'timeline-warning';
|
|
7834
|
+
case 'error':
|
|
7835
|
+
return 'timeline-error';
|
|
7836
|
+
case 'info':
|
|
7837
|
+
return 'timeline-info';
|
|
7838
|
+
default:
|
|
7839
|
+
return 'timeline-default';
|
|
7840
|
+
}
|
|
7841
|
+
};
|
|
7842
|
+
return {
|
|
7843
|
+
view: ({ attrs }) => {
|
|
7844
|
+
const { items = [], orientation = 'vertical', position = 'right', showConnector = true, className = '', showTimestamps = true, compact = false, } = attrs;
|
|
7845
|
+
const timelineClasses = [
|
|
7846
|
+
'timeline',
|
|
7847
|
+
`timeline-${orientation}`,
|
|
7848
|
+
`timeline-${position}`,
|
|
7849
|
+
showConnector ? 'timeline-connector' : '',
|
|
7850
|
+
compact ? 'timeline-compact' : '',
|
|
7851
|
+
className,
|
|
7852
|
+
]
|
|
7853
|
+
.filter(Boolean)
|
|
7854
|
+
.join(' ') || undefined;
|
|
7855
|
+
return m('div', { className: timelineClasses }, [
|
|
7856
|
+
items.map((item, index) => {
|
|
7857
|
+
const isAlternate = position === 'alternate';
|
|
7858
|
+
const itemPosition = isAlternate ? (index % 2 === 0 ? 'right' : 'left') : position;
|
|
7859
|
+
const itemClasses = [
|
|
7860
|
+
'timeline-item',
|
|
7861
|
+
`timeline-item-${itemPosition}`,
|
|
7862
|
+
getColorClass(item.color),
|
|
7863
|
+
item.disabled ? 'timeline-item-disabled' : '',
|
|
7864
|
+
item.className || '',
|
|
7865
|
+
]
|
|
7866
|
+
.filter(Boolean)
|
|
7867
|
+
.join(' ') || undefined;
|
|
7868
|
+
const handleItemClick = (e) => {
|
|
7869
|
+
if (item.disabled)
|
|
7870
|
+
return;
|
|
7871
|
+
if (item.onClick) {
|
|
7872
|
+
e.preventDefault();
|
|
7873
|
+
item.onClick(item, e);
|
|
7874
|
+
}
|
|
7875
|
+
};
|
|
7876
|
+
const isVertical = orientation === 'vertical';
|
|
7877
|
+
return m('div', {
|
|
7878
|
+
key: item.id || index,
|
|
7879
|
+
className: itemClasses,
|
|
7880
|
+
onclick: item.onClick ? handleItemClick : undefined,
|
|
7881
|
+
role: item.onClick ? 'button' : undefined,
|
|
7882
|
+
tabindex: item.onClick && !item.disabled ? 0 : undefined,
|
|
7883
|
+
'aria-disabled': item.disabled ? 'true' : undefined,
|
|
7884
|
+
}, [
|
|
7885
|
+
// Timestamp (on opposite side of content from bullet)
|
|
7886
|
+
isVertical &&
|
|
7887
|
+
showTimestamps &&
|
|
7888
|
+
item.timestamp &&
|
|
7889
|
+
m(`.timeline-timestamp-separate${!item.icon ? '.timeline-dot-small' : ''}`, formatTimestamp(item.timestamp)),
|
|
7890
|
+
// Timeline separator containing dot and connector
|
|
7891
|
+
m('.timeline-separator', [
|
|
7892
|
+
// Timeline dot/icon
|
|
7893
|
+
m(`.timeline-dot${!item.icon ? '.timeline-dot-small' : ''}`, [
|
|
7894
|
+
item.icon ? m('i.material-icons.timeline-icon', item.icon) : m('.timeline-marker'),
|
|
7895
|
+
]),
|
|
7896
|
+
// Timeline connector (only show if not the last item and connectors are enabled)
|
|
7897
|
+
showConnector && index < items.length - 1 && m('.timeline-connector'),
|
|
7898
|
+
]),
|
|
7899
|
+
// Content container
|
|
7900
|
+
m('.timeline-content', [
|
|
7901
|
+
// Timestamp for horizontal layout or when not shown separately
|
|
7902
|
+
!isVertical &&
|
|
7903
|
+
showTimestamps &&
|
|
7904
|
+
item.timestamp &&
|
|
7905
|
+
m('.timeline-timestamp', formatTimestamp(item.timestamp)),
|
|
7906
|
+
// Main content
|
|
7907
|
+
item.content
|
|
7908
|
+
? item.content
|
|
7909
|
+
: m('.timeline-text', [
|
|
7910
|
+
item.label && m('.timeline-label', item.label),
|
|
7911
|
+
item.description && m('.timeline-description', item.description),
|
|
7912
|
+
]),
|
|
7913
|
+
]),
|
|
7914
|
+
]);
|
|
7915
|
+
}),
|
|
7916
|
+
]);
|
|
7917
|
+
},
|
|
7918
|
+
};
|
|
7919
|
+
};
|
|
7920
|
+
|
|
7921
|
+
const Masonry = () => {
|
|
7922
|
+
let containerRef = null;
|
|
7923
|
+
const itemHeights = []; // measured heights
|
|
7924
|
+
let resizeObserver = null;
|
|
7925
|
+
const defaultBreakpoints = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5 };
|
|
7926
|
+
const getColumnsCount = (columns = 3) => {
|
|
7927
|
+
if (typeof columns === 'number')
|
|
7928
|
+
return columns;
|
|
7929
|
+
const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), columns);
|
|
7930
|
+
const width = window.innerWidth;
|
|
7931
|
+
if (width >= 1200)
|
|
7932
|
+
return breakpoints.xl || 5;
|
|
7933
|
+
if (width >= 992)
|
|
7934
|
+
return breakpoints.lg || 4;
|
|
7935
|
+
if (width >= 768)
|
|
7936
|
+
return breakpoints.md || 3;
|
|
7937
|
+
if (width >= 576)
|
|
7938
|
+
return breakpoints.sm || 2;
|
|
7939
|
+
return breakpoints.xs || 1;
|
|
7940
|
+
};
|
|
7941
|
+
const setupResizeObserver = () => {
|
|
7942
|
+
if (resizeObserver)
|
|
7943
|
+
return;
|
|
7944
|
+
if (typeof ResizeObserver === 'undefined')
|
|
7945
|
+
return;
|
|
7946
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
7947
|
+
for (const entry of entries) {
|
|
7948
|
+
const el = entry.target;
|
|
7949
|
+
const index = Number(el.dataset.index);
|
|
7950
|
+
if (!isNaN(index)) {
|
|
7951
|
+
const h = el.offsetHeight;
|
|
7952
|
+
if (itemHeights[index] !== h) {
|
|
7953
|
+
itemHeights[index] = h;
|
|
7954
|
+
m.redraw();
|
|
7955
|
+
}
|
|
7956
|
+
}
|
|
7957
|
+
}
|
|
7958
|
+
});
|
|
7959
|
+
};
|
|
7960
|
+
const cleanup = () => {
|
|
7961
|
+
if (resizeObserver) {
|
|
7962
|
+
resizeObserver.disconnect();
|
|
7963
|
+
resizeObserver = null;
|
|
7964
|
+
}
|
|
7965
|
+
};
|
|
7966
|
+
return {
|
|
7967
|
+
onremove: cleanup,
|
|
7968
|
+
view: ({ attrs, children }) => {
|
|
7969
|
+
const { columns = 3, spacing = 16, className = '', onItemClick, cssOnly = false, animationDelay } = attrs;
|
|
7970
|
+
const gap = typeof spacing === 'number' ? spacing : parseInt(spacing, 10) || 16;
|
|
7971
|
+
const columnsCount = typeof columns === 'number' ? columns : getColumnsCount(columns);
|
|
7972
|
+
const containerClasses = [
|
|
7973
|
+
'masonry',
|
|
7974
|
+
cssOnly ? 'masonry-css' : 'masonry-js',
|
|
7975
|
+
animationDelay ? 'masonry-animated' : '',
|
|
7976
|
+
className,
|
|
7977
|
+
]
|
|
7978
|
+
.filter(Boolean)
|
|
7979
|
+
.join(' ');
|
|
7980
|
+
const containerStyle = { position: 'relative' };
|
|
7981
|
+
// --- CSS-only fallback ---
|
|
7982
|
+
if (cssOnly) {
|
|
7983
|
+
containerStyle.display = 'grid';
|
|
7984
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
7985
|
+
containerStyle.gap = `${gap}px`;
|
|
7986
|
+
return m('div', { className: containerClasses, style: containerStyle }, children);
|
|
7987
|
+
}
|
|
7988
|
+
// --- JS Masonry ---
|
|
7989
|
+
const containerWidth = (containerRef === null || containerRef === void 0 ? void 0 : containerRef.offsetWidth) || 800;
|
|
7990
|
+
const totalGapWidth = gap * (columnsCount - 1);
|
|
7991
|
+
const columnWidth = (containerWidth - totalGapWidth) / columnsCount;
|
|
7992
|
+
const columnHeights = new Array(columnsCount).fill(0);
|
|
7993
|
+
const positionedChildren = [];
|
|
7994
|
+
(Array.isArray(children) ? children : [children]).forEach((child, index) => {
|
|
7995
|
+
var _a;
|
|
7996
|
+
const shortestColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
|
|
7997
|
+
const x = shortestColumnIndex * (columnWidth + gap);
|
|
7998
|
+
const y = columnHeights[shortestColumnIndex];
|
|
7999
|
+
const itemHeight = (_a = itemHeights[index]) !== null && _a !== void 0 ? _a : 200; // fallback until measured
|
|
8000
|
+
columnHeights[shortestColumnIndex] += itemHeight + gap;
|
|
8001
|
+
const itemStyle = {
|
|
8002
|
+
position: 'absolute',
|
|
8003
|
+
left: `${x}px`,
|
|
8004
|
+
top: `${y}px`,
|
|
8005
|
+
width: `${columnWidth}px`,
|
|
8006
|
+
transition: 'all 0.3s ease',
|
|
8007
|
+
animationDelay: animationDelay ? `${index * animationDelay}ms` : undefined,
|
|
8008
|
+
};
|
|
8009
|
+
positionedChildren.push(m('div', {
|
|
8010
|
+
key: `masonry-item-${index}`,
|
|
8011
|
+
className: 'masonry-item',
|
|
8012
|
+
style: itemStyle,
|
|
8013
|
+
'data-index': index,
|
|
8014
|
+
onclick: onItemClick ? (e) => onItemClick(index, e) : undefined,
|
|
8015
|
+
oncreate: ({ dom }) => {
|
|
8016
|
+
const el = dom;
|
|
8017
|
+
setupResizeObserver();
|
|
8018
|
+
resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(el);
|
|
8019
|
+
const h = el.offsetHeight;
|
|
8020
|
+
if (itemHeights[index] !== h) {
|
|
8021
|
+
itemHeights[index] = h;
|
|
8022
|
+
m.redraw();
|
|
8023
|
+
}
|
|
8024
|
+
},
|
|
8025
|
+
onremove: ({ dom }) => {
|
|
8026
|
+
resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.unobserve(dom);
|
|
8027
|
+
},
|
|
8028
|
+
}, child));
|
|
8029
|
+
});
|
|
8030
|
+
containerStyle.height = `${Math.max(...columnHeights) - gap}px`;
|
|
8031
|
+
return m('div', {
|
|
8032
|
+
className: containerClasses,
|
|
8033
|
+
style: containerStyle,
|
|
8034
|
+
oncreate: ({ dom }) => {
|
|
8035
|
+
containerRef = dom;
|
|
8036
|
+
},
|
|
8037
|
+
}, positionedChildren);
|
|
8038
|
+
},
|
|
8039
|
+
};
|
|
8040
|
+
};
|
|
8041
|
+
|
|
8042
|
+
/**
|
|
8043
|
+
* ImageList Component
|
|
8044
|
+
* Displays a collection of images in various grid layouts
|
|
8045
|
+
* Supports standard grid, quilted (varied sizes), masonry, and woven patterns
|
|
8046
|
+
*/
|
|
8047
|
+
const ImageList = () => {
|
|
8048
|
+
const defaultBreakpoints = {
|
|
8049
|
+
xs: 1,
|
|
8050
|
+
sm: 2,
|
|
8051
|
+
md: 3,
|
|
8052
|
+
lg: 4,
|
|
8053
|
+
xl: 5
|
|
8054
|
+
};
|
|
8055
|
+
const getColumnsCount = (cols = 3) => {
|
|
8056
|
+
if (typeof cols === 'number')
|
|
8057
|
+
return cols;
|
|
8058
|
+
const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), cols);
|
|
8059
|
+
const width = typeof window !== 'undefined' ? window.innerWidth : 1024;
|
|
8060
|
+
if (width >= 1200)
|
|
8061
|
+
return breakpoints.xl || 5;
|
|
8062
|
+
if (width >= 992)
|
|
8063
|
+
return breakpoints.lg || 4;
|
|
8064
|
+
if (width >= 768)
|
|
8065
|
+
return breakpoints.md || 3;
|
|
8066
|
+
if (width >= 576)
|
|
8067
|
+
return breakpoints.sm || 2;
|
|
8068
|
+
return breakpoints.xs || 1;
|
|
8069
|
+
};
|
|
8070
|
+
const handleImageLoad = (e) => {
|
|
8071
|
+
const img = e.target;
|
|
8072
|
+
img.classList.add('loaded');
|
|
8073
|
+
};
|
|
8074
|
+
const handleImageError = (e) => {
|
|
8075
|
+
const img = e.target;
|
|
8076
|
+
img.classList.add('error');
|
|
8077
|
+
// Could set a placeholder image here
|
|
8078
|
+
img.alt = 'Failed to load image';
|
|
8079
|
+
};
|
|
8080
|
+
const renderImage = (item, index, options) => {
|
|
8081
|
+
const { src, alt = '', title, subtitle, onclick, actionButton, className = '', loading = options.loading || 'lazy', aspectRatio, cols = 1, rows = 1, featured = false } = item;
|
|
8082
|
+
const itemClasses = [
|
|
8083
|
+
'image-list-item',
|
|
8084
|
+
featured ? 'image-list-item-featured' : '',
|
|
8085
|
+
onclick ? 'image-list-item-clickable' : '',
|
|
8086
|
+
className
|
|
8087
|
+
].filter(Boolean).join(' ');
|
|
8088
|
+
const itemStyle = {};
|
|
8089
|
+
// Quilted layout with fixed alternating pattern
|
|
8090
|
+
if (options.variant === 'quilted') {
|
|
8091
|
+
if (featured || index % 7 === 0) {
|
|
8092
|
+
itemStyle.gridColumnEnd = 'span 2';
|
|
8093
|
+
itemStyle.gridRowEnd = 'span 2';
|
|
8094
|
+
}
|
|
8095
|
+
else if (index % 3 === 0) {
|
|
8096
|
+
itemStyle.gridColumnEnd = 'span 2';
|
|
8097
|
+
itemStyle.gridRowEnd = 'span 1';
|
|
8098
|
+
}
|
|
8099
|
+
else {
|
|
8100
|
+
itemStyle.gridColumnEnd = 'span 1';
|
|
8101
|
+
itemStyle.gridRowEnd = 'span 1';
|
|
8102
|
+
}
|
|
8103
|
+
}
|
|
8104
|
+
// Woven layout with varied sizes based on item properties
|
|
8105
|
+
if (options.variant === 'woven') {
|
|
8106
|
+
itemStyle.gridColumnEnd = `span ${cols}`;
|
|
8107
|
+
itemStyle.gridRowEnd = `span ${rows}`;
|
|
8108
|
+
}
|
|
8109
|
+
// Masonry layout - prevent break inside items
|
|
8110
|
+
if (options.variant === 'masonry') {
|
|
8111
|
+
itemStyle.breakInside = 'avoid';
|
|
8112
|
+
itemStyle.marginBottom = typeof options.gap === 'number' ? `${options.gap}px` : options.gap || '4px';
|
|
8113
|
+
itemStyle.display = 'inline-block';
|
|
8114
|
+
itemStyle.width = '100%';
|
|
8115
|
+
}
|
|
8116
|
+
// Custom aspect ratio
|
|
8117
|
+
if (aspectRatio && options.variant !== 'masonry') {
|
|
8118
|
+
itemStyle.aspectRatio = aspectRatio.toString();
|
|
8119
|
+
}
|
|
8120
|
+
const handleItemClick = (e) => {
|
|
8121
|
+
if (onclick) {
|
|
8122
|
+
e.preventDefault();
|
|
8123
|
+
onclick(item, e);
|
|
8124
|
+
}
|
|
8125
|
+
};
|
|
8126
|
+
const handleActionClick = (e) => {
|
|
8127
|
+
e.stopPropagation();
|
|
8128
|
+
if (actionButton === null || actionButton === void 0 ? void 0 : actionButton.onclick) {
|
|
8129
|
+
actionButton.onclick(item, e);
|
|
8130
|
+
}
|
|
8131
|
+
};
|
|
8132
|
+
return m(`.${itemClasses}`, {
|
|
8133
|
+
key: `image-${index}`,
|
|
8134
|
+
style: itemStyle,
|
|
8135
|
+
onclick: onclick ? handleItemClick : undefined,
|
|
8136
|
+
role: onclick ? 'button' : undefined,
|
|
8137
|
+
tabindex: onclick ? 0 : undefined,
|
|
8138
|
+
}, [
|
|
8139
|
+
m('.image-list-item-img', [
|
|
8140
|
+
m('img', {
|
|
8141
|
+
src,
|
|
8142
|
+
alt,
|
|
8143
|
+
loading,
|
|
8144
|
+
onload: handleImageLoad,
|
|
8145
|
+
onerror: handleImageError,
|
|
8146
|
+
draggable: false,
|
|
8147
|
+
}),
|
|
8148
|
+
// Loading placeholder
|
|
8149
|
+
m('.image-list-item-placeholder'),
|
|
8150
|
+
]),
|
|
8151
|
+
// Title overlay
|
|
8152
|
+
(options.showTitles && (title || subtitle)) &&
|
|
8153
|
+
m('.image-list-item-bar', [
|
|
8154
|
+
m('.image-list-item-title-wrap', [
|
|
8155
|
+
title && m('.image-list-item-title', title),
|
|
8156
|
+
subtitle && m('.image-list-item-subtitle', subtitle)
|
|
8157
|
+
])
|
|
8158
|
+
]),
|
|
8159
|
+
// Action button
|
|
8160
|
+
(options.showActions && actionButton) &&
|
|
8161
|
+
m('button.image-list-item-action', {
|
|
8162
|
+
class: `image-list-action-${actionButton.position || 'top-right'}`,
|
|
8163
|
+
onclick: handleActionClick,
|
|
8164
|
+
'aria-label': actionButton.ariaLabel || `Action for ${title || alt}`,
|
|
8165
|
+
}, [
|
|
8166
|
+
m('i.material-icons', actionButton.icon)
|
|
8167
|
+
])
|
|
8168
|
+
]);
|
|
8169
|
+
};
|
|
8170
|
+
return {
|
|
8171
|
+
view: ({ attrs }) => {
|
|
8172
|
+
const { items = [], variant = 'standard', cols = 3, gap = 4, rowHeight = 'auto', className = '', showTitles = false, showActions = false } = attrs;
|
|
8173
|
+
const columnsCount = getColumnsCount(cols);
|
|
8174
|
+
const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
|
|
8175
|
+
const containerClasses = [
|
|
8176
|
+
'image-list',
|
|
8177
|
+
`image-list-${variant}`,
|
|
8178
|
+
showTitles ? 'image-list-with-titles' : '',
|
|
8179
|
+
showActions ? 'image-list-with-actions' : '',
|
|
8180
|
+
className
|
|
8181
|
+
].filter(Boolean).join(' ');
|
|
8182
|
+
const containerStyle = {
|
|
8183
|
+
gap: gapValue
|
|
8184
|
+
};
|
|
8185
|
+
// Set up grid based on variant
|
|
8186
|
+
switch (variant) {
|
|
8187
|
+
case 'standard':
|
|
8188
|
+
containerStyle.display = 'grid';
|
|
8189
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
8190
|
+
if (rowHeight !== 'auto') {
|
|
8191
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight;
|
|
8192
|
+
}
|
|
8193
|
+
break;
|
|
8194
|
+
case 'quilted':
|
|
8195
|
+
// Fixed pattern like woven
|
|
8196
|
+
containerStyle.display = 'grid';
|
|
8197
|
+
containerStyle.gridTemplateColumns = `repeat(${Math.max(columnsCount * 2, 4)}, 1fr)`;
|
|
8198
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '150px';
|
|
8199
|
+
containerStyle.gridAutoFlow = 'dense';
|
|
8200
|
+
break;
|
|
8201
|
+
case 'masonry':
|
|
8202
|
+
// Use CSS columns for masonry effect
|
|
8203
|
+
containerStyle.display = 'block';
|
|
8204
|
+
containerStyle.columnCount = columnsCount;
|
|
8205
|
+
containerStyle.columnGap = gapValue;
|
|
8206
|
+
containerStyle.columnFill = 'balance';
|
|
8207
|
+
containerStyle.orphans = 1;
|
|
8208
|
+
containerStyle.widows = 1;
|
|
8209
|
+
break;
|
|
8210
|
+
case 'woven':
|
|
8211
|
+
// Varied sizes based on item cols/rows
|
|
8212
|
+
containerStyle.display = 'grid';
|
|
8213
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
8214
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '200px';
|
|
8215
|
+
containerStyle.gridAutoFlow = 'dense';
|
|
8216
|
+
break;
|
|
8217
|
+
}
|
|
8218
|
+
return m(`.${containerClasses}`, {
|
|
8219
|
+
style: containerStyle
|
|
8220
|
+
}, [
|
|
8221
|
+
items.map((item, index) => renderImage(item, index, attrs))
|
|
8222
|
+
]);
|
|
8223
|
+
}
|
|
8224
|
+
};
|
|
8225
|
+
};
|
|
8226
|
+
|
|
8227
|
+
/**
|
|
8228
|
+
* @fileoverview Core TypeScript utility types for mithril-materialized library
|
|
8229
|
+
* These types improve type safety and developer experience across all components
|
|
8230
|
+
*/
|
|
8231
|
+
/**
|
|
8232
|
+
* Type guard to check if validation result indicates success
|
|
8233
|
+
* @param result - The validation result to check
|
|
8234
|
+
* @returns True if validation passed
|
|
8235
|
+
*/
|
|
8236
|
+
const isValidationSuccess = (result) => result === true || result === '';
|
|
8237
|
+
/**
|
|
8238
|
+
* Type guard to check if validation result indicates an error
|
|
8239
|
+
* @param result - The validation result to check
|
|
8240
|
+
* @returns True if validation failed
|
|
8241
|
+
*/
|
|
8242
|
+
const isValidationError = (result) => !isValidationSuccess(result);
|
|
8243
|
+
// ============================================================================
|
|
8244
|
+
// EXPORTS
|
|
8245
|
+
// ============================================================================
|
|
8246
|
+
// All types are already exported via individual export declarations above
|
|
8247
|
+
|
|
6187
8248
|
exports.AnchorItem = AnchorItem;
|
|
6188
8249
|
exports.Autocomplete = Autocomplete;
|
|
6189
8250
|
exports.Breadcrumb = Breadcrumb;
|
|
@@ -6198,7 +8259,9 @@ exports.Collapsible = Collapsible;
|
|
|
6198
8259
|
exports.CollapsibleItem = CollapsibleItem;
|
|
6199
8260
|
exports.Collection = Collection;
|
|
6200
8261
|
exports.ColorInput = ColorInput;
|
|
8262
|
+
exports.DataTable = DataTable;
|
|
6201
8263
|
exports.DatePicker = DatePicker;
|
|
8264
|
+
exports.DoubleRangeSlider = DoubleRangeSlider;
|
|
6202
8265
|
exports.Dropdown = Dropdown;
|
|
6203
8266
|
exports.EmailInput = EmailInput;
|
|
6204
8267
|
exports.FileInput = FileInput;
|
|
@@ -6207,16 +8270,20 @@ exports.FlatButton = FlatButton;
|
|
|
6207
8270
|
exports.FloatingActionButton = FloatingActionButton;
|
|
6208
8271
|
exports.HelperText = HelperText;
|
|
6209
8272
|
exports.Icon = Icon;
|
|
8273
|
+
exports.ImageList = ImageList;
|
|
6210
8274
|
exports.InputCheckbox = InputCheckbox;
|
|
6211
8275
|
exports.Label = Label;
|
|
6212
8276
|
exports.LargeButton = LargeButton;
|
|
6213
8277
|
exports.ListItem = ListItem;
|
|
6214
8278
|
exports.Mandatory = Mandatory;
|
|
8279
|
+
exports.Masonry = Masonry;
|
|
6215
8280
|
exports.MaterialBox = MaterialBox;
|
|
8281
|
+
exports.MaterialIcon = MaterialIcon;
|
|
6216
8282
|
exports.ModalPanel = ModalPanel;
|
|
6217
8283
|
exports.NumberInput = NumberInput;
|
|
6218
8284
|
exports.Options = Options;
|
|
6219
8285
|
exports.Pagination = Pagination;
|
|
8286
|
+
exports.PaginationControls = PaginationControls;
|
|
6220
8287
|
exports.Parallax = Parallax;
|
|
6221
8288
|
exports.PasswordInput = PasswordInput;
|
|
6222
8289
|
exports.Pushpin = Pushpin;
|
|
@@ -6231,6 +8298,7 @@ exports.Select = Select;
|
|
|
6231
8298
|
exports.Sidenav = Sidenav;
|
|
6232
8299
|
exports.SidenavItem = SidenavItem;
|
|
6233
8300
|
exports.SidenavManager = SidenavManager;
|
|
8301
|
+
exports.SingleRangeSlider = SingleRangeSlider;
|
|
6234
8302
|
exports.SmallButton = SmallButton;
|
|
6235
8303
|
exports.Stepper = Stepper;
|
|
6236
8304
|
exports.SubmitButton = SubmitButton;
|
|
@@ -6242,10 +8310,12 @@ exports.ThemeManager = ThemeManager;
|
|
|
6242
8310
|
exports.ThemeSwitcher = ThemeSwitcher;
|
|
6243
8311
|
exports.ThemeToggle = ThemeToggle;
|
|
6244
8312
|
exports.TimePicker = TimePicker;
|
|
8313
|
+
exports.Timeline = Timeline;
|
|
6245
8314
|
exports.Toast = Toast;
|
|
6246
8315
|
exports.ToastComponent = ToastComponent;
|
|
6247
8316
|
exports.Tooltip = Tooltip;
|
|
6248
8317
|
exports.TooltipComponent = TooltipComponent;
|
|
8318
|
+
exports.TreeView = TreeView;
|
|
6249
8319
|
exports.UrlInput = UrlInput;
|
|
6250
8320
|
exports.Wizard = Wizard;
|
|
6251
8321
|
exports.createBreadcrumb = createBreadcrumb;
|
|
@@ -6253,6 +8323,8 @@ exports.getDropdownStyles = getDropdownStyles;
|
|
|
6253
8323
|
exports.initPushpins = initPushpins;
|
|
6254
8324
|
exports.initTooltips = initTooltips;
|
|
6255
8325
|
exports.isNumeric = isNumeric;
|
|
8326
|
+
exports.isValidationError = isValidationError;
|
|
8327
|
+
exports.isValidationSuccess = isValidationSuccess;
|
|
6256
8328
|
exports.padLeft = padLeft;
|
|
6257
8329
|
exports.range = range;
|
|
6258
8330
|
exports.toast = toast;
|