mithril-materialized 2.0.0-beta.9 → 2.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/file-upload.d.ts +23 -0
- package/dist/floating-action-button.d.ts +1 -1
- 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 +2736 -659
- package/dist/index.js +2746 -658
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +2746 -658
- package/dist/input-options.d.ts +18 -4
- package/dist/input.d.ts +0 -1
- 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/theme-switcher.d.ts +23 -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
|
@@ -418,12 +418,18 @@ const ButtonFactory = (element, defaultClassNames, type = '') => {
|
|
|
418
418
|
return () => {
|
|
419
419
|
return {
|
|
420
420
|
view: ({ attrs }) => {
|
|
421
|
-
const { modalId, tooltip,
|
|
422
|
-
|
|
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]
|
|
423
427
|
.filter(Boolean)
|
|
424
428
|
.join(' ')
|
|
425
429
|
.trim();
|
|
426
|
-
|
|
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 }),
|
|
427
433
|
// `${dca}${modalId ? `.modal-trigger[href=#${modalId}]` : ''}${
|
|
428
434
|
// tooltip ? `.tooltipped[data-position=${tooltipPostion || 'top'}][data-tooltip=${tooltip}]` : ''
|
|
429
435
|
// }${toAttributeString(attr)}`, {}
|
|
@@ -832,6 +838,18 @@ const iconPaths = {
|
|
|
832
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',
|
|
833
839
|
'M0 0h24v24H0z',
|
|
834
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
|
+
],
|
|
835
853
|
};
|
|
836
854
|
const MaterialIcon = () => {
|
|
837
855
|
return {
|
|
@@ -841,8 +859,8 @@ const MaterialIcon = () => {
|
|
|
841
859
|
const rotationMap = {
|
|
842
860
|
down: 0,
|
|
843
861
|
up: 180,
|
|
844
|
-
left:
|
|
845
|
-
right: 90,
|
|
862
|
+
left: 90,
|
|
863
|
+
right: -90,
|
|
846
864
|
};
|
|
847
865
|
const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
|
|
848
866
|
const transform = rotation ? `rotate(${rotation}deg)` : undefined;
|
|
@@ -1151,7 +1169,7 @@ const CodeBlock = () => ({
|
|
|
1151
1169
|
const lang = language || 'lang-TypeScript';
|
|
1152
1170
|
const label = lang.replace('lang-', '');
|
|
1153
1171
|
const cb = code instanceof Array ? code.join('\n') : code;
|
|
1154
|
-
const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim();
|
|
1172
|
+
const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim() || undefined;
|
|
1155
1173
|
return m(`pre.codeblock${newRow ? '.clear' : ''}`, attrs, [
|
|
1156
1174
|
m('div', m('label', label)),
|
|
1157
1175
|
m('code', Object.assign(Object.assign({}, params), { className: cn }), cb),
|
|
@@ -1334,7 +1352,7 @@ const Collection = () => {
|
|
|
1334
1352
|
};
|
|
1335
1353
|
};
|
|
1336
1354
|
|
|
1337
|
-
const defaultI18n = {
|
|
1355
|
+
const defaultI18n$2 = {
|
|
1338
1356
|
cancel: 'Cancel',
|
|
1339
1357
|
clear: 'Clear',
|
|
1340
1358
|
done: 'Ok',
|
|
@@ -1442,9 +1460,9 @@ const DatePicker = () => {
|
|
|
1442
1460
|
else if (attrs.displayFormat) {
|
|
1443
1461
|
finalFormat = attrs.displayFormat;
|
|
1444
1462
|
}
|
|
1445
|
-
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n, onSelect: null, onOpen: null, onClose: null }, attrs);
|
|
1463
|
+
const merged = Object.assign({ autoClose: false, format: finalFormat, parse: null, defaultDate: null, setDefaultDate: false, disableWeekends: false, disableDayFn: null, firstDay: 0, minDate: null, maxDate: null, yearRange, showClearBtn: false, showWeekNumbers: false, weekNumbering: 'iso', i18n: defaultI18n$2, onSelect: null, onOpen: null, onClose: null }, attrs);
|
|
1446
1464
|
// Merge i18n properly
|
|
1447
|
-
merged.i18n = Object.assign(Object.assign({}, defaultI18n), attrs.i18n);
|
|
1465
|
+
merged.i18n = Object.assign(Object.assign({}, defaultI18n$2), attrs.i18n);
|
|
1448
1466
|
return merged;
|
|
1449
1467
|
};
|
|
1450
1468
|
const toString = (date, format) => {
|
|
@@ -1463,6 +1481,42 @@ const DatePicker = () => {
|
|
|
1463
1481
|
});
|
|
1464
1482
|
return result;
|
|
1465
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
|
+
};
|
|
1466
1520
|
const setDate = (date, preventOnSelect = false, options) => {
|
|
1467
1521
|
if (!date) {
|
|
1468
1522
|
state.date = null;
|
|
@@ -1489,6 +1543,55 @@ const DatePicker = () => {
|
|
|
1489
1543
|
options.onSelect(state.date);
|
|
1490
1544
|
}
|
|
1491
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
|
+
};
|
|
1492
1595
|
const gotoDate = (date) => {
|
|
1493
1596
|
if (!isDate(date)) {
|
|
1494
1597
|
return;
|
|
@@ -1547,6 +1650,21 @@ const DatePicker = () => {
|
|
|
1547
1650
|
arr.push('is-selected');
|
|
1548
1651
|
ariaSelected = 'true';
|
|
1549
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
|
+
}
|
|
1550
1668
|
if (opts.hasEvent) {
|
|
1551
1669
|
arr.push('has-event');
|
|
1552
1670
|
}
|
|
@@ -1567,9 +1685,14 @@ const DatePicker = () => {
|
|
|
1567
1685
|
const month = parseInt(target.getAttribute('data-month') || '0', 10);
|
|
1568
1686
|
const day = parseInt(target.getAttribute('data-day') || '0', 10);
|
|
1569
1687
|
const selectedDate = new Date(year, month, day);
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
+
}
|
|
1573
1696
|
}
|
|
1574
1697
|
}
|
|
1575
1698
|
},
|
|
@@ -1629,16 +1752,37 @@ const DatePicker = () => {
|
|
|
1629
1752
|
(options.maxDate && day > options.maxDate) ||
|
|
1630
1753
|
(options.disableWeekends && isWeekend(day)) ||
|
|
1631
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
|
+
}
|
|
1632
1772
|
const dayConfig = {
|
|
1633
1773
|
day: dayNumber,
|
|
1634
1774
|
month: monthNumber,
|
|
1635
1775
|
year: yearNumber,
|
|
1636
1776
|
hasEvent: false,
|
|
1637
|
-
isSelected: isSelected,
|
|
1777
|
+
isSelected: !options.dateRange && isSelected, // Only use isSelected in single date mode
|
|
1638
1778
|
isToday: isToday,
|
|
1639
1779
|
isDisabled: isDisabled,
|
|
1640
1780
|
isEmpty: isEmpty,
|
|
1641
1781
|
showDaysInNextAndPreviousMonths: false,
|
|
1782
|
+
isRangeStart: isRangeStart,
|
|
1783
|
+
isRangeEnd: isRangeEnd,
|
|
1784
|
+
isInRange: isInRange,
|
|
1785
|
+
isRangePreview: isRangePreview,
|
|
1642
1786
|
};
|
|
1643
1787
|
// Add week number cell at the beginning of each row
|
|
1644
1788
|
if (r === 0 && options.showWeekNumbers) {
|
|
@@ -1685,14 +1829,58 @@ const DatePicker = () => {
|
|
|
1685
1829
|
return {
|
|
1686
1830
|
view: ({ attrs }) => {
|
|
1687
1831
|
const { options } = attrs;
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
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
|
+
}
|
|
1696
1884
|
}
|
|
1697
1885
|
};
|
|
1698
1886
|
};
|
|
@@ -1841,6 +2029,10 @@ const DatePicker = () => {
|
|
|
1841
2029
|
id: uniqueId(),
|
|
1842
2030
|
isOpen: false,
|
|
1843
2031
|
date: null,
|
|
2032
|
+
startDate: null,
|
|
2033
|
+
endDate: null,
|
|
2034
|
+
selectionMode: null,
|
|
2035
|
+
isSelectingRange: false,
|
|
1844
2036
|
calendars: [{ month: 0, year: 0 }],
|
|
1845
2037
|
monthDropdownOpen: false,
|
|
1846
2038
|
yearDropdownOpen: false,
|
|
@@ -1865,17 +2057,35 @@ const DatePicker = () => {
|
|
|
1865
2057
|
yyyy: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0; },
|
|
1866
2058
|
},
|
|
1867
2059
|
};
|
|
1868
|
-
// Initialize date
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
+
}
|
|
1876
2075
|
}
|
|
1877
2076
|
else {
|
|
1878
|
-
|
|
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
|
+
}
|
|
1879
2089
|
}
|
|
1880
2090
|
// Add document click listener to close dropdowns
|
|
1881
2091
|
document.addEventListener('click', handleDocumentClick);
|
|
@@ -1891,19 +2101,61 @@ const DatePicker = () => {
|
|
|
1891
2101
|
const className = cn1 || cn2 || 'col s12';
|
|
1892
2102
|
// Calculate display value for the input
|
|
1893
2103
|
let displayValue = '';
|
|
1894
|
-
if (
|
|
1895
|
-
|
|
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
|
+
}
|
|
1896
2142
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
// const formatRegex = /(yyyy|mm|dd)/gi;
|
|
1900
|
-
let customDisplayValue = attrs.displayFormat;
|
|
2143
|
+
else {
|
|
2144
|
+
// Single date display (original behavior)
|
|
1901
2145
|
if (state.date) {
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
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
|
+
}
|
|
1907
2159
|
}
|
|
1908
2160
|
}
|
|
1909
2161
|
return m('.input-field', {
|
|
@@ -2007,8 +2259,19 @@ const DatePicker = () => {
|
|
|
2007
2259
|
type: 'button',
|
|
2008
2260
|
onclick: () => {
|
|
2009
2261
|
state.isOpen = false;
|
|
2010
|
-
if (
|
|
2011
|
-
|
|
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
|
+
}
|
|
2012
2275
|
}
|
|
2013
2276
|
if (options.onClose)
|
|
2014
2277
|
options.onClose();
|
|
@@ -2037,246 +2300,458 @@ const DatePicker = () => {
|
|
|
2037
2300
|
};
|
|
2038
2301
|
};
|
|
2039
2302
|
|
|
2040
|
-
|
|
2041
|
-
const
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
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;
|
|
2058
2473
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2474
|
+
updateSingleValue(state.singleValue, false);
|
|
2475
|
+
if (finalValueDisplay === 'auto') {
|
|
2476
|
+
m.redraw();
|
|
2061
2477
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
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);
|
|
2067
2600
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
case ' ':
|
|
2071
|
-
e.preventDefault();
|
|
2072
|
-
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
2073
|
-
const selectedItem = availableItems[state.focusedIndex];
|
|
2074
|
-
const value = (selectedItem.id || selectedItem.label);
|
|
2075
|
-
state.initialValue = value;
|
|
2076
|
-
state.isOpen = false;
|
|
2077
|
-
state.focusedIndex = -1;
|
|
2078
|
-
if (onchange)
|
|
2079
|
-
onchange(value);
|
|
2601
|
+
else {
|
|
2602
|
+
updateRangeValues(state.rangeMinValue, Math.max(steppedValue, state.rangeMinValue), attrs, state, true);
|
|
2080
2603
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
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;
|
|
2084
2612
|
}
|
|
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
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
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
|
+
},
|
|
2121
2674
|
}, [
|
|
2122
|
-
m(
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
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'),
|
|
2128
2688
|
onclick: (e) => {
|
|
2129
|
-
e.preventDefault();
|
|
2130
2689
|
e.stopPropagation();
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
2134
|
-
}
|
|
2690
|
+
state.activeThumb = 'min';
|
|
2691
|
+
e.currentTarget.focus();
|
|
2135
2692
|
},
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
state.
|
|
2148
|
-
},
|
|
2149
|
-
style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
|
|
2150
|
-
// Convert dropdown items to format expected by getDropdownStyles
|
|
2151
|
-
group: undefined }))), true),
|
|
2152
|
-
}, items.map((item, index) => {
|
|
2153
|
-
if (item.divider) {
|
|
2154
|
-
return m('li.divider', {
|
|
2155
|
-
key: `divider-${index}`,
|
|
2156
|
-
});
|
|
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);
|
|
2157
2705
|
}
|
|
2158
|
-
const itemIndex = availableItems.indexOf(item);
|
|
2159
|
-
const isFocused = itemIndex === state.focusedIndex;
|
|
2160
|
-
return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
|
|
2161
|
-
item.disabled ? 'disabled' : '',
|
|
2162
|
-
isFocused ? 'focused' : '',
|
|
2163
|
-
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
2164
|
-
]
|
|
2165
|
-
.filter(Boolean)
|
|
2166
|
-
.join(' ') }, (item.disabled
|
|
2167
|
-
? {}
|
|
2168
|
-
: {
|
|
2169
|
-
onclick: (e) => {
|
|
2170
|
-
e.stopPropagation();
|
|
2171
|
-
const value = (item.id || item.label);
|
|
2172
|
-
state.initialValue = value;
|
|
2173
|
-
state.isOpen = false;
|
|
2174
|
-
state.focusedIndex = -1;
|
|
2175
|
-
if (onchange)
|
|
2176
|
-
onchange(value);
|
|
2177
|
-
},
|
|
2178
|
-
})), m('span', {
|
|
2179
|
-
style: {
|
|
2180
|
-
display: 'flex',
|
|
2181
|
-
alignItems: 'center',
|
|
2182
|
-
padding: '14px 16px',
|
|
2183
|
-
},
|
|
2184
|
-
}, [
|
|
2185
|
-
item.iconName
|
|
2186
|
-
? m('i.material-icons', {
|
|
2187
|
-
style: { marginRight: '32px' },
|
|
2188
|
-
}, item.iconName)
|
|
2189
|
-
: undefined,
|
|
2190
|
-
item.label,
|
|
2191
|
-
]));
|
|
2192
|
-
})),
|
|
2193
|
-
m(MaterialIcon, {
|
|
2194
|
-
name: 'caret',
|
|
2195
|
-
direction: 'down',
|
|
2196
|
-
class: 'caret',
|
|
2197
|
-
}),
|
|
2198
|
-
]),
|
|
2199
|
-
]);
|
|
2200
|
-
},
|
|
2201
|
-
};
|
|
2202
|
-
};
|
|
2203
|
-
|
|
2204
|
-
/**
|
|
2205
|
-
* Floating Action Button
|
|
2206
|
-
*/
|
|
2207
|
-
const FloatingActionButton = () => {
|
|
2208
|
-
const state = {
|
|
2209
|
-
isOpen: false,
|
|
2210
|
-
};
|
|
2211
|
-
const handleClickOutside = (e) => {
|
|
2212
|
-
const target = e.target;
|
|
2213
|
-
if (!target.closest('.fixed-action-btn')) {
|
|
2214
|
-
state.isOpen = false;
|
|
2215
|
-
}
|
|
2216
|
-
};
|
|
2217
|
-
return {
|
|
2218
|
-
oncreate: () => {
|
|
2219
|
-
document.addEventListener('click', handleClickOutside);
|
|
2220
|
-
},
|
|
2221
|
-
onremove: () => {
|
|
2222
|
-
document.removeEventListener('click', handleClickOutside);
|
|
2223
|
-
},
|
|
2224
|
-
view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
|
|
2225
|
-
? 'position: absolute; display: inline-block; left: 24px;'
|
|
2226
|
-
: position === 'right' || position === 'inline-right'
|
|
2227
|
-
? 'position: absolute; display: inline-block; right: 24px;'
|
|
2228
|
-
: undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
|
|
2229
|
-
const fabClasses = [
|
|
2230
|
-
'fixed-action-btn',
|
|
2231
|
-
direction ? `direction-${direction}` : '',
|
|
2232
|
-
state.isOpen ? 'active' : '',
|
|
2233
|
-
// hoverEnabled ? 'hover-enabled' : '',
|
|
2234
|
-
]
|
|
2235
|
-
.filter(Boolean)
|
|
2236
|
-
.join(' ');
|
|
2237
|
-
return m('div', {
|
|
2238
|
-
style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
|
|
2239
|
-
}, m(`.${fabClasses}`, {
|
|
2240
|
-
style,
|
|
2241
|
-
onclick: (e) => {
|
|
2242
|
-
e.stopPropagation();
|
|
2243
|
-
if (buttons && buttons.length > 0) {
|
|
2244
|
-
state.isOpen = !state.isOpen;
|
|
2245
|
-
}
|
|
2246
|
-
},
|
|
2247
|
-
onmouseover: hoverEnabled
|
|
2248
|
-
? () => {
|
|
2249
|
-
if (buttons && buttons.length > 0) {
|
|
2250
|
-
state.isOpen = true;
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
: undefined,
|
|
2254
|
-
onmouseleave: hoverEnabled
|
|
2255
|
-
? () => {
|
|
2256
|
-
state.isOpen = false;
|
|
2257
|
-
}
|
|
2258
|
-
: undefined,
|
|
2259
|
-
}, [
|
|
2260
|
-
m('a.btn-floating.btn-large', {
|
|
2261
|
-
className,
|
|
2262
|
-
}, m('i.material-icons', { className: iconClass }, iconName)),
|
|
2263
|
-
buttons &&
|
|
2264
|
-
buttons.length > 0 &&
|
|
2265
|
-
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
|
|
2266
|
-
style: {
|
|
2267
|
-
opacity: state.isOpen ? '1' : '0',
|
|
2268
|
-
transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
|
|
2269
|
-
transition: `all 0.3s ease ${index * 40}ms`,
|
|
2270
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'),
|
|
2271
2724
|
onclick: (e) => {
|
|
2272
2725
|
e.stopPropagation();
|
|
2273
|
-
|
|
2274
|
-
|
|
2726
|
+
state.activeThumb = 'max';
|
|
2727
|
+
e.currentTarget.focus();
|
|
2275
2728
|
},
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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
|
+
},
|
|
2280
2755
|
};
|
|
2281
2756
|
|
|
2282
2757
|
/** Character counter component that tracks text length against maxLength */
|
|
@@ -2416,6 +2891,12 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2416
2891
|
isValid: true,
|
|
2417
2892
|
active: false,
|
|
2418
2893
|
inputElement: null,
|
|
2894
|
+
// Range-specific state
|
|
2895
|
+
rangeMinValue: undefined,
|
|
2896
|
+
rangeMaxValue: undefined,
|
|
2897
|
+
singleValue: undefined,
|
|
2898
|
+
isDragging: false,
|
|
2899
|
+
activeThumb: null,
|
|
2419
2900
|
};
|
|
2420
2901
|
// let labelManager: { updateLabelState: () => void; cleanup: () => void } | null = null;
|
|
2421
2902
|
// let lengthUpdateHandler: (() => void) | null = null;
|
|
@@ -2441,18 +2922,39 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2441
2922
|
state.hasInteracted = length > 0;
|
|
2442
2923
|
}
|
|
2443
2924
|
};
|
|
2925
|
+
// Range slider helper functions
|
|
2926
|
+
// Range slider rendering functions are now in separate module
|
|
2444
2927
|
return {
|
|
2445
2928
|
view: ({ attrs }) => {
|
|
2446
2929
|
var _a;
|
|
2447
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"]);
|
|
2448
2931
|
// const attributes = toAttrs(params);
|
|
2449
|
-
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim();
|
|
2932
|
+
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
2450
2933
|
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
2451
2934
|
? true
|
|
2452
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
|
+
}
|
|
2453
2947
|
return m('.input-field', { className: cn, style }, [
|
|
2454
2948
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2455
|
-
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,
|
|
2456
2958
|
// attributes,
|
|
2457
2959
|
oncreate: ({ dom }) => {
|
|
2458
2960
|
const input = (state.inputElement = dom);
|
|
@@ -2468,7 +2970,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2468
2970
|
state.currentLength = input.value.length; // Initial count
|
|
2469
2971
|
}
|
|
2470
2972
|
// Range input functionality
|
|
2471
|
-
if (type === 'range') {
|
|
2973
|
+
if (type === 'range' && !attrs.minmax) {
|
|
2472
2974
|
const updateThumb = () => {
|
|
2473
2975
|
const value = input.value;
|
|
2474
2976
|
const min = input.min || '0';
|
|
@@ -2665,83 +3167,1010 @@ const FileInput = () => {
|
|
|
2665
3167
|
};
|
|
2666
3168
|
};
|
|
2667
3169
|
|
|
2668
|
-
/**
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
const
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
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
|
+
},
|
|
2678
3191
|
};
|
|
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
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
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%);
|
|
4172
|
+
color: white;
|
|
4173
|
+
font-size: 16px;
|
|
2745
4174
|
text-align: center;
|
|
2746
4175
|
opacity: 0;
|
|
2747
4176
|
transition: opacity ${attrs.inDuration || 275}ms ease ${attrs.inDuration || 275}ms;
|
|
@@ -2839,7 +4268,7 @@ const MaterialBox = () => {
|
|
|
2839
4268
|
view: ({ attrs }) => {
|
|
2840
4269
|
const { src, alt, width, height, caption, className, style } = attrs, otherAttrs = __rest(attrs, ["src", "alt", "width", "height", "caption", "className", "style"]);
|
|
2841
4270
|
return m('img.materialboxed', Object.assign(Object.assign({}, otherAttrs), { src, alt: alt || '', width,
|
|
2842
|
-
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) => {
|
|
2843
4272
|
e.preventDefault();
|
|
2844
4273
|
openBox(e.target, attrs);
|
|
2845
4274
|
} }));
|
|
@@ -2921,7 +4350,7 @@ const ModalPanel = () => {
|
|
|
2921
4350
|
.filter(Boolean)
|
|
2922
4351
|
.join(' ')
|
|
2923
4352
|
.trim();
|
|
2924
|
-
const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim();
|
|
4353
|
+
const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined;
|
|
2925
4354
|
return m('div', { className: 'modal-container' }, [
|
|
2926
4355
|
// Modal overlay
|
|
2927
4356
|
m('div', {
|
|
@@ -2946,21 +4375,25 @@ const ModalPanel = () => {
|
|
|
2946
4375
|
role: 'dialog',
|
|
2947
4376
|
'aria-labelledby': `${id}-title`,
|
|
2948
4377
|
'aria-describedby': description ? `${id}-desc` : undefined,
|
|
2949
|
-
style: {
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
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)' }),
|
|
2964
4397
|
onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
2965
4398
|
}, [
|
|
2966
4399
|
// Close button
|
|
@@ -2980,7 +4413,12 @@ const ModalPanel = () => {
|
|
|
2980
4413
|
}, '×'),
|
|
2981
4414
|
// Modal content
|
|
2982
4415
|
m('.modal-content', {
|
|
2983
|
-
style: {
|
|
4416
|
+
style: {
|
|
4417
|
+
padding: '24px',
|
|
4418
|
+
paddingTop: showCloseButton ? '48px' : '24px',
|
|
4419
|
+
minHeight: 'auto',
|
|
4420
|
+
flex: '1 1 auto',
|
|
4421
|
+
},
|
|
2984
4422
|
}, [
|
|
2985
4423
|
m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
|
|
2986
4424
|
description &&
|
|
@@ -2991,119 +4429,16 @@ const ModalPanel = () => {
|
|
|
2991
4429
|
buttons.length > 0 &&
|
|
2992
4430
|
m('.modal-footer', {
|
|
2993
4431
|
style: {
|
|
2994
|
-
padding: '4px 6px',
|
|
2995
|
-
borderTop: '1px solid rgba(160,160,160,0.2)',
|
|
2996
|
-
textAlign: 'right',
|
|
2997
|
-
},
|
|
2998
|
-
}, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
|
|
2999
|
-
if (buttonProps.onclick)
|
|
3000
|
-
buttonProps.onclick(e);
|
|
3001
|
-
closeModal(attrs);
|
|
3002
|
-
} })))),
|
|
3003
|
-
]),
|
|
3004
|
-
]);
|
|
3005
|
-
},
|
|
3006
|
-
};
|
|
3007
|
-
};
|
|
3008
|
-
|
|
3009
|
-
/** Component to show a check box */
|
|
3010
|
-
const InputCheckbox = () => {
|
|
3011
|
-
return {
|
|
3012
|
-
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3013
|
-
const checkboxId = inputId || uniqueId();
|
|
3014
|
-
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3015
|
-
m('input[type=checkbox][tabindex=0]', {
|
|
3016
|
-
id: checkboxId,
|
|
3017
|
-
checked,
|
|
3018
|
-
disabled,
|
|
3019
|
-
onclick: onchange
|
|
3020
|
-
? (e) => {
|
|
3021
|
-
if (e.target && typeof e.target.checked !== 'undefined') {
|
|
3022
|
-
onchange(e.target.checked);
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
|
-
: undefined,
|
|
3026
|
-
}),
|
|
3027
|
-
label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
|
|
3028
|
-
]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
|
|
3029
|
-
},
|
|
3030
|
-
};
|
|
3031
|
-
};
|
|
3032
|
-
/** A list of checkboxes */
|
|
3033
|
-
const Options = () => {
|
|
3034
|
-
const state = {};
|
|
3035
|
-
const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
|
|
3036
|
-
const selectAll = (options, callback) => {
|
|
3037
|
-
const allIds = options.map((option) => option.id);
|
|
3038
|
-
state.checkedIds = [...allIds];
|
|
3039
|
-
if (callback)
|
|
3040
|
-
callback(allIds);
|
|
3041
|
-
};
|
|
3042
|
-
const selectNone = (callback) => {
|
|
3043
|
-
state.checkedIds = [];
|
|
3044
|
-
if (callback)
|
|
3045
|
-
callback([]);
|
|
3046
|
-
};
|
|
3047
|
-
return {
|
|
3048
|
-
oninit: ({ attrs: { initialValue, checkedId, id } }) => {
|
|
3049
|
-
const iv = checkedId || initialValue;
|
|
3050
|
-
state.checkedId = checkedId;
|
|
3051
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3052
|
-
state.componentId = id || uniqueId();
|
|
3053
|
-
},
|
|
3054
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
|
|
3055
|
-
const onchange = callback
|
|
3056
|
-
? (propId, checked) => {
|
|
3057
|
-
const checkedIds = state.checkedIds.filter((i) => i !== propId);
|
|
3058
|
-
if (checked) {
|
|
3059
|
-
checkedIds.push(propId);
|
|
3060
|
-
}
|
|
3061
|
-
state.checkedIds = checkedIds;
|
|
3062
|
-
callback(checkedIds);
|
|
3063
|
-
}
|
|
3064
|
-
: undefined;
|
|
3065
|
-
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
3066
|
-
const optionsContent = layout === 'horizontal'
|
|
3067
|
-
? m('div.grid-container', options.map((option) => m(InputCheckbox, {
|
|
3068
|
-
disabled: disabled || option.disabled,
|
|
3069
|
-
label: option.label,
|
|
3070
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3071
|
-
className: option.className || checkboxClass,
|
|
3072
|
-
checked: isChecked(option.id),
|
|
3073
|
-
description: option.description,
|
|
3074
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3075
|
-
})))
|
|
3076
|
-
: options.map((option) => m(InputCheckbox, {
|
|
3077
|
-
disabled: disabled || option.disabled,
|
|
3078
|
-
label: option.label,
|
|
3079
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3080
|
-
className: option.className || checkboxClass,
|
|
3081
|
-
checked: isChecked(option.id),
|
|
3082
|
-
description: option.description,
|
|
3083
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3084
|
-
}));
|
|
3085
|
-
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3086
|
-
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3087
|
-
showSelectAll &&
|
|
3088
|
-
m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
|
|
3089
|
-
m('a', {
|
|
3090
|
-
href: '#',
|
|
3091
|
-
onclick: (e) => {
|
|
3092
|
-
e.preventDefault();
|
|
3093
|
-
selectAll(options, callback);
|
|
3094
|
-
},
|
|
3095
|
-
style: 'margin-right: 15px;',
|
|
3096
|
-
}, 'Select All'),
|
|
3097
|
-
m('a', {
|
|
3098
|
-
href: '#',
|
|
3099
|
-
onclick: (e) => {
|
|
3100
|
-
e.preventDefault();
|
|
3101
|
-
selectNone(callback);
|
|
3102
|
-
},
|
|
3103
|
-
}, 'Select None'),
|
|
3104
|
-
]),
|
|
3105
|
-
description && m(HelperText, { helperText: description }),
|
|
3106
|
-
m('form', { action: '#' }, optionsContent),
|
|
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
|
+
]),
|
|
3107
4442
|
]);
|
|
3108
4443
|
},
|
|
3109
4444
|
};
|
|
@@ -3373,12 +4708,15 @@ const TimePicker = () => {
|
|
|
3373
4708
|
};
|
|
3374
4709
|
const updateTimeFromInput = (inputValue) => {
|
|
3375
4710
|
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
4711
|
+
let amPmWasProvided = false;
|
|
3376
4712
|
if (options.twelveHour && value.length > 1) {
|
|
3377
4713
|
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
3378
4714
|
state.amOrPm = 'AM';
|
|
4715
|
+
amPmWasProvided = true;
|
|
3379
4716
|
}
|
|
3380
4717
|
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
3381
4718
|
state.amOrPm = 'PM';
|
|
4719
|
+
amPmWasProvided = true;
|
|
3382
4720
|
}
|
|
3383
4721
|
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
3384
4722
|
}
|
|
@@ -3387,21 +4725,33 @@ const TimePicker = () => {
|
|
|
3387
4725
|
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
3388
4726
|
if (options.twelveHour) {
|
|
3389
4727
|
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
4728
|
+
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
3390
4729
|
}
|
|
3391
4730
|
}
|
|
3392
4731
|
let hours = +value[0] || 0;
|
|
3393
4732
|
let minutes = +value[1] || 0;
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
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
|
+
}
|
|
3399
4748
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
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
|
+
}
|
|
3405
4755
|
}
|
|
3406
4756
|
}
|
|
3407
4757
|
state.hours = hours;
|
|
@@ -4081,7 +5431,7 @@ const RadioButtons = () => {
|
|
|
4081
5431
|
callback(propId);
|
|
4082
5432
|
}
|
|
4083
5433
|
};
|
|
4084
|
-
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
5434
|
+
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
4085
5435
|
const optionsContent = layout === 'horizontal'
|
|
4086
5436
|
? m('div.grid-container', options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
|
|
4087
5437
|
groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` }))))
|
|
@@ -4354,7 +5704,7 @@ const Switch = () => {
|
|
|
4354
5704
|
view: ({ attrs }) => {
|
|
4355
5705
|
const id = attrs.id || state.id;
|
|
4356
5706
|
const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
|
|
4357
|
-
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
5707
|
+
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
4358
5708
|
return m('div', {
|
|
4359
5709
|
className: cn,
|
|
4360
5710
|
onclick: (e) => {
|
|
@@ -4505,7 +5855,7 @@ const Tabs = () => {
|
|
|
4505
5855
|
},
|
|
4506
5856
|
view: ({ attrs }) => {
|
|
4507
5857
|
const { tabWidth, tabs, className, style, swipeable = false } = attrs;
|
|
4508
|
-
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;
|
|
4509
5859
|
const anchoredTabs = tabs.map(toAnchored());
|
|
4510
5860
|
const activeTab = setActiveTabId(anchoredTabs, attrs.selectedTabId);
|
|
4511
5861
|
updateIndicator();
|
|
@@ -5325,6 +6675,17 @@ const initTooltips = (selector = '[data-tooltip]', options = {}) => {
|
|
|
5325
6675
|
return tooltips;
|
|
5326
6676
|
};
|
|
5327
6677
|
|
|
6678
|
+
const defaultI18n$1 = {
|
|
6679
|
+
theme: 'Theme:',
|
|
6680
|
+
light: 'Light',
|
|
6681
|
+
dark: 'Dark',
|
|
6682
|
+
auto: 'Auto',
|
|
6683
|
+
lightTitle: 'Light theme',
|
|
6684
|
+
darkTitle: 'Dark theme',
|
|
6685
|
+
autoTitle: 'Auto theme (system preference)',
|
|
6686
|
+
switchToDark: 'Switch to dark theme',
|
|
6687
|
+
switchToLight: 'Switch to light theme',
|
|
6688
|
+
};
|
|
5328
6689
|
/**
|
|
5329
6690
|
* Theme switching utilities and component
|
|
5330
6691
|
*/
|
|
@@ -5407,44 +6768,36 @@ const ThemeSwitcher = () => {
|
|
|
5407
6768
|
ThemeManager.initialize();
|
|
5408
6769
|
}
|
|
5409
6770
|
},
|
|
5410
|
-
view: ({ attrs }) => {
|
|
5411
|
-
const { theme = ThemeManager.getTheme(), onThemeChange, showLabels = true, className
|
|
6771
|
+
view: ({ attrs = {} }) => {
|
|
6772
|
+
const { theme = ThemeManager.getTheme(), onThemeChange, showLabels = true, className, i18n } = attrs;
|
|
6773
|
+
const labels = Object.assign(Object.assign({}, defaultI18n$1), i18n);
|
|
5412
6774
|
const handleThemeChange = (newTheme) => {
|
|
5413
6775
|
ThemeManager.setTheme(newTheme);
|
|
5414
6776
|
if (onThemeChange) {
|
|
5415
6777
|
onThemeChange(newTheme);
|
|
5416
6778
|
}
|
|
5417
6779
|
};
|
|
5418
|
-
return m('.theme-switcher', {
|
|
5419
|
-
showLabels && m('span.theme-switcher-label',
|
|
6780
|
+
return m('.theme-switcher', { className }, [
|
|
6781
|
+
showLabels && m('span.theme-switcher-label', labels.theme),
|
|
5420
6782
|
m('.theme-switcher-buttons', [
|
|
5421
6783
|
m('button.btn-flat', {
|
|
5422
6784
|
class: theme === 'light' ? 'active' : '',
|
|
5423
6785
|
onclick: () => handleThemeChange('light'),
|
|
5424
|
-
title:
|
|
5425
|
-
}, [
|
|
5426
|
-
m('i.material-icons', 'light_mode'),
|
|
5427
|
-
showLabels && m('span', 'Light')
|
|
5428
|
-
]),
|
|
6786
|
+
title: labels.lightTitle,
|
|
6787
|
+
}, [m('i.material-icons', 'light_mode'), showLabels && m('span', labels.light)]),
|
|
5429
6788
|
m('button.btn-flat', {
|
|
5430
6789
|
class: theme === 'auto' ? 'active' : '',
|
|
5431
6790
|
onclick: () => handleThemeChange('auto'),
|
|
5432
|
-
title:
|
|
5433
|
-
}, [
|
|
5434
|
-
m('i.material-icons', 'brightness_auto'),
|
|
5435
|
-
showLabels && m('span', 'Auto')
|
|
5436
|
-
]),
|
|
6791
|
+
title: labels.autoTitle,
|
|
6792
|
+
}, [m('i.material-icons', 'brightness_auto'), showLabels && m('span', labels.auto)]),
|
|
5437
6793
|
m('button.btn-flat', {
|
|
5438
6794
|
class: theme === 'dark' ? 'active' : '',
|
|
5439
6795
|
onclick: () => handleThemeChange('dark'),
|
|
5440
|
-
title:
|
|
5441
|
-
}, [
|
|
5442
|
-
|
|
5443
|
-
showLabels && m('span', 'Dark')
|
|
5444
|
-
])
|
|
5445
|
-
])
|
|
6796
|
+
title: labels.darkTitle,
|
|
6797
|
+
}, [m('i.material-icons', 'dark_mode'), showLabels && m('span', labels.dark)]),
|
|
6798
|
+
]),
|
|
5446
6799
|
]);
|
|
5447
|
-
}
|
|
6800
|
+
},
|
|
5448
6801
|
};
|
|
5449
6802
|
};
|
|
5450
6803
|
/**
|
|
@@ -5458,41 +6811,55 @@ const ThemeToggle = () => {
|
|
|
5458
6811
|
ThemeManager.initialize();
|
|
5459
6812
|
}
|
|
5460
6813
|
},
|
|
5461
|
-
view: ({ attrs }) => {
|
|
6814
|
+
view: ({ attrs = {} }) => {
|
|
5462
6815
|
const currentTheme = ThemeManager.getEffectiveTheme();
|
|
6816
|
+
const labels = Object.assign(Object.assign({}, defaultI18n$1), attrs.i18n);
|
|
5463
6817
|
return m('button.btn-flat.theme-toggle', {
|
|
5464
6818
|
class: attrs.className || '',
|
|
5465
6819
|
onclick: () => {
|
|
5466
6820
|
ThemeManager.toggle();
|
|
5467
|
-
m.redraw();
|
|
5468
6821
|
},
|
|
5469
|
-
title:
|
|
5470
|
-
style: 'margin: 0; height: 64px; line-height: 64px; border-radius: 0; min-width: 64px; padding: 0;'
|
|
6822
|
+
title: currentTheme === 'light' ? labels.switchToDark : labels.switchToLight,
|
|
6823
|
+
style: 'margin: 0; height: 64px; line-height: 64px; border-radius: 0; min-width: 64px; padding: 0;',
|
|
5471
6824
|
}, [
|
|
5472
6825
|
m('i.material-icons', {
|
|
5473
|
-
style: 'color: inherit; font-size: 24px;'
|
|
5474
|
-
}, currentTheme === 'light' ? 'dark_mode' : 'light_mode')
|
|
6826
|
+
style: 'color: inherit; font-size: 24px;',
|
|
6827
|
+
}, currentTheme === 'light' ? 'dark_mode' : 'light_mode'),
|
|
5475
6828
|
]);
|
|
5476
|
-
}
|
|
6829
|
+
},
|
|
5477
6830
|
};
|
|
5478
6831
|
};
|
|
5479
6832
|
|
|
6833
|
+
const defaultI18n = {
|
|
6834
|
+
label: 'Choose files or drag them here',
|
|
6835
|
+
selectedFiles: 'Selected Files:',
|
|
6836
|
+
acceptedTypes: 'Accepted:',
|
|
6837
|
+
removeFile: 'Remove file',
|
|
6838
|
+
fileSizeExceeds: 'File size exceeds {maxSize}MB limit',
|
|
6839
|
+
fileTypeNotAccepted: 'File type not accepted. Accepted: {accept}',
|
|
6840
|
+
fileSizeUnits: {
|
|
6841
|
+
bytes: 'B',
|
|
6842
|
+
kb: 'KB',
|
|
6843
|
+
mb: 'MB',
|
|
6844
|
+
gb: 'GB',
|
|
6845
|
+
},
|
|
6846
|
+
};
|
|
5480
6847
|
/**
|
|
5481
6848
|
* File Upload Component with Drag and Drop
|
|
5482
6849
|
* Supports multiple files, file type validation, size limits, and image preview
|
|
5483
6850
|
*/
|
|
5484
6851
|
const FileUpload = () => {
|
|
5485
6852
|
let state;
|
|
5486
|
-
const validateFile = (file, attrs) => {
|
|
6853
|
+
const validateFile = (file, attrs, labels) => {
|
|
5487
6854
|
// Check file size
|
|
5488
6855
|
if (attrs.maxSize && file.size > attrs.maxSize) {
|
|
5489
6856
|
const maxSizeMB = (attrs.maxSize / (1024 * 1024)).toFixed(1);
|
|
5490
|
-
return
|
|
6857
|
+
return labels.fileSizeExceeds.replace('{maxSize}', maxSizeMB);
|
|
5491
6858
|
}
|
|
5492
6859
|
// Check file type
|
|
5493
6860
|
if (attrs.accept) {
|
|
5494
|
-
const acceptedTypes = attrs.accept.split(',').map(type => type.trim());
|
|
5495
|
-
const isAccepted = acceptedTypes.some(acceptedType => {
|
|
6861
|
+
const acceptedTypes = attrs.accept.split(',').map((type) => type.trim());
|
|
6862
|
+
const isAccepted = acceptedTypes.some((acceptedType) => {
|
|
5496
6863
|
if (acceptedType.startsWith('.')) {
|
|
5497
6864
|
// Extension check
|
|
5498
6865
|
return file.name.toLowerCase().endsWith(acceptedType.toLowerCase());
|
|
@@ -5503,7 +6870,7 @@ const FileUpload = () => {
|
|
|
5503
6870
|
}
|
|
5504
6871
|
});
|
|
5505
6872
|
if (!isAccepted) {
|
|
5506
|
-
return
|
|
6873
|
+
return labels.fileTypeNotAccepted.replace('{accept}', attrs.accept);
|
|
5507
6874
|
}
|
|
5508
6875
|
}
|
|
5509
6876
|
return null;
|
|
@@ -5519,12 +6886,12 @@ const FileUpload = () => {
|
|
|
5519
6886
|
reader.readAsDataURL(file);
|
|
5520
6887
|
}
|
|
5521
6888
|
};
|
|
5522
|
-
const handleFiles = (fileList, attrs) => {
|
|
6889
|
+
const handleFiles = (fileList, attrs, labels) => {
|
|
5523
6890
|
const newFiles = Array.from(fileList);
|
|
5524
6891
|
const validFiles = [];
|
|
5525
6892
|
// Validate each file
|
|
5526
6893
|
for (const file of newFiles) {
|
|
5527
|
-
const error = validateFile(file, attrs);
|
|
6894
|
+
const error = validateFile(file, attrs, labels);
|
|
5528
6895
|
if (error) {
|
|
5529
6896
|
file.uploadError = error;
|
|
5530
6897
|
}
|
|
@@ -5552,20 +6919,20 @@ const FileUpload = () => {
|
|
|
5552
6919
|
}
|
|
5553
6920
|
// Notify parent component
|
|
5554
6921
|
if (attrs.onFilesSelected) {
|
|
5555
|
-
attrs.onFilesSelected(state.files.filter(f => !f.uploadError));
|
|
6922
|
+
attrs.onFilesSelected(state.files.filter((f) => !f.uploadError));
|
|
5556
6923
|
}
|
|
5557
6924
|
};
|
|
5558
6925
|
const removeFile = (fileToRemove, attrs) => {
|
|
5559
|
-
state.files = state.files.filter(file => file !== fileToRemove);
|
|
6926
|
+
state.files = state.files.filter((file) => file !== fileToRemove);
|
|
5560
6927
|
if (attrs.onFileRemoved) {
|
|
5561
6928
|
attrs.onFileRemoved(fileToRemove);
|
|
5562
6929
|
}
|
|
5563
6930
|
};
|
|
5564
|
-
const formatFileSize = (bytes) => {
|
|
6931
|
+
const formatFileSize = (bytes, labels) => {
|
|
5565
6932
|
if (bytes === 0)
|
|
5566
|
-
return
|
|
6933
|
+
return `0 ${labels.fileSizeUnits.bytes}`;
|
|
5567
6934
|
const k = 1024;
|
|
5568
|
-
const sizes = [
|
|
6935
|
+
const sizes = [labels.fileSizeUnits.bytes, labels.fileSizeUnits.kb, labels.fileSizeUnits.mb, labels.fileSizeUnits.gb];
|
|
5569
6936
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
5570
6937
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
5571
6938
|
};
|
|
@@ -5575,11 +6942,13 @@ const FileUpload = () => {
|
|
|
5575
6942
|
id: uniqueId(),
|
|
5576
6943
|
files: [],
|
|
5577
6944
|
isDragOver: false,
|
|
5578
|
-
isUploading: false
|
|
6945
|
+
isUploading: false,
|
|
5579
6946
|
};
|
|
5580
6947
|
},
|
|
5581
6948
|
view: ({ attrs }) => {
|
|
5582
|
-
const { accept, multiple = false, disabled = false, label
|
|
6949
|
+
const { accept, multiple = false, disabled = false, label, helperText, showPreview = true, className = '', error, i18n, } = attrs;
|
|
6950
|
+
const labels = Object.assign(Object.assign({}, defaultI18n), i18n);
|
|
6951
|
+
const displayLabel = label || labels.label;
|
|
5583
6952
|
return m('.file-upload-container', { class: className }, [
|
|
5584
6953
|
// Upload area
|
|
5585
6954
|
m('.file-upload-area', {
|
|
@@ -5587,8 +6956,10 @@ const FileUpload = () => {
|
|
|
5587
6956
|
state.isDragOver ? 'drag-over' : '',
|
|
5588
6957
|
disabled ? 'disabled' : '',
|
|
5589
6958
|
error ? 'error' : '',
|
|
5590
|
-
state.files.length > 0 ? 'has-files' : ''
|
|
5591
|
-
]
|
|
6959
|
+
state.files.length > 0 ? 'has-files' : '',
|
|
6960
|
+
]
|
|
6961
|
+
.filter(Boolean)
|
|
6962
|
+
.join(' ') || undefined,
|
|
5592
6963
|
ondragover: (e) => {
|
|
5593
6964
|
if (disabled)
|
|
5594
6965
|
return;
|
|
@@ -5611,7 +6982,7 @@ const FileUpload = () => {
|
|
|
5611
6982
|
e.stopPropagation();
|
|
5612
6983
|
state.isDragOver = false;
|
|
5613
6984
|
if ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) {
|
|
5614
|
-
handleFiles(e.dataTransfer.files, attrs);
|
|
6985
|
+
handleFiles(e.dataTransfer.files, attrs, labels);
|
|
5615
6986
|
}
|
|
5616
6987
|
},
|
|
5617
6988
|
onclick: () => {
|
|
@@ -5619,7 +6990,7 @@ const FileUpload = () => {
|
|
|
5619
6990
|
return;
|
|
5620
6991
|
const input = document.getElementById(state.id);
|
|
5621
6992
|
input === null || input === void 0 ? void 0 : input.click();
|
|
5622
|
-
}
|
|
6993
|
+
},
|
|
5623
6994
|
}, [
|
|
5624
6995
|
m('input[type="file"]', {
|
|
5625
6996
|
id: state.id,
|
|
@@ -5630,59 +7001,57 @@ const FileUpload = () => {
|
|
|
5630
7001
|
onchange: (e) => {
|
|
5631
7002
|
const target = e.target;
|
|
5632
7003
|
if (target.files) {
|
|
5633
|
-
handleFiles(target.files, attrs);
|
|
7004
|
+
handleFiles(target.files, attrs, labels);
|
|
5634
7005
|
}
|
|
5635
|
-
}
|
|
7006
|
+
},
|
|
5636
7007
|
}),
|
|
5637
7008
|
m('.file-upload-content', [
|
|
5638
7009
|
m('i.material-icons.file-upload-icon', 'cloud_upload'),
|
|
5639
|
-
m('p.file-upload-label',
|
|
7010
|
+
m('p.file-upload-label', displayLabel),
|
|
5640
7011
|
helperText && m('p.file-upload-helper', helperText),
|
|
5641
|
-
accept && m('p.file-upload-types',
|
|
5642
|
-
])
|
|
7012
|
+
accept && m('p.file-upload-types', `${labels.acceptedTypes} ${accept}`),
|
|
7013
|
+
]),
|
|
5643
7014
|
]),
|
|
5644
7015
|
// Error message
|
|
5645
7016
|
error && m('.file-upload-error', error),
|
|
5646
7017
|
// File list
|
|
5647
|
-
state.files.length > 0 &&
|
|
5648
|
-
m('
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
m('img', { src: file.preview, alt: file.name })
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
7018
|
+
state.files.length > 0 &&
|
|
7019
|
+
m('.file-upload-list', [
|
|
7020
|
+
m('h6', labels.selectedFiles),
|
|
7021
|
+
state.files.map((file) => m('.file-upload-item', { key: file.name + file.size }, [
|
|
7022
|
+
// Preview thumbnail
|
|
7023
|
+
showPreview && file.preview && m('.file-preview', [m('img', { src: file.preview, alt: file.name })]),
|
|
7024
|
+
// File info
|
|
7025
|
+
m('.file-info', [
|
|
7026
|
+
m('.file-name', file.name),
|
|
7027
|
+
m('.file-details', [
|
|
7028
|
+
m('span.file-size', formatFileSize(file.size, labels)),
|
|
7029
|
+
file.type && m('span.file-type', file.type),
|
|
7030
|
+
]),
|
|
7031
|
+
// Progress bar (if uploading)
|
|
7032
|
+
file.uploadProgress !== undefined &&
|
|
7033
|
+
m('.file-progress', [
|
|
7034
|
+
m('.progress', [
|
|
7035
|
+
m('.determinate', {
|
|
7036
|
+
style: { width: `${file.uploadProgress}%` },
|
|
7037
|
+
}),
|
|
7038
|
+
]),
|
|
7039
|
+
]),
|
|
7040
|
+
// Error message
|
|
7041
|
+
file.uploadError && m('.file-error', file.uploadError),
|
|
5668
7042
|
]),
|
|
5669
|
-
//
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
}, [
|
|
5680
|
-
m('i.material-icons', 'close')
|
|
5681
|
-
])
|
|
5682
|
-
]))
|
|
5683
|
-
])
|
|
7043
|
+
// Remove button
|
|
7044
|
+
m('button.btn-flat.file-remove', {
|
|
7045
|
+
onclick: (e) => {
|
|
7046
|
+
e.stopPropagation();
|
|
7047
|
+
removeFile(file, attrs);
|
|
7048
|
+
},
|
|
7049
|
+
title: labels.removeFile,
|
|
7050
|
+
}, [m('i.material-icons', 'close')]),
|
|
7051
|
+
])),
|
|
7052
|
+
]),
|
|
5684
7053
|
]);
|
|
5685
|
-
}
|
|
7054
|
+
},
|
|
5686
7055
|
};
|
|
5687
7056
|
};
|
|
5688
7057
|
|
|
@@ -5712,7 +7081,7 @@ const Sidenav = () => {
|
|
|
5712
7081
|
state = {
|
|
5713
7082
|
id: attrs.id || uniqueId(),
|
|
5714
7083
|
isOpen: attrs.isOpen || false,
|
|
5715
|
-
isAnimating: false
|
|
7084
|
+
isAnimating: false,
|
|
5716
7085
|
};
|
|
5717
7086
|
// Set up keyboard listener
|
|
5718
7087
|
if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
|
|
@@ -5741,34 +7110,33 @@ const Sidenav = () => {
|
|
|
5741
7110
|
}
|
|
5742
7111
|
},
|
|
5743
7112
|
view: ({ attrs, children }) => {
|
|
5744
|
-
const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false } = attrs;
|
|
7113
|
+
const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, } = attrs;
|
|
5745
7114
|
const isOpen = state.isOpen;
|
|
5746
7115
|
return [
|
|
5747
7116
|
// Backdrop (using existing materialize class)
|
|
5748
|
-
showBackdrop &&
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
7117
|
+
showBackdrop &&
|
|
7118
|
+
mode === 'overlay' &&
|
|
7119
|
+
m('.sidenav-overlay', {
|
|
7120
|
+
style: {
|
|
7121
|
+
display: isOpen ? 'block' : 'none',
|
|
7122
|
+
opacity: isOpen ? '1' : '0',
|
|
7123
|
+
},
|
|
7124
|
+
onclick: () => handleBackdropClick(attrs),
|
|
7125
|
+
}),
|
|
5755
7126
|
// Sidenav (using existing materialize structure)
|
|
5756
7127
|
m('ul.sidenav', {
|
|
5757
7128
|
id: state.id,
|
|
5758
|
-
class: [
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
className
|
|
5762
|
-
].filter(Boolean).join(' '),
|
|
7129
|
+
class: [position === 'right' ? 'right-aligned' : '', fixed ? 'sidenav-fixed' : '', className]
|
|
7130
|
+
.filter(Boolean)
|
|
7131
|
+
.join(' ') || undefined,
|
|
5763
7132
|
style: {
|
|
5764
7133
|
width: `${width}px`,
|
|
5765
|
-
transform: isOpen ? 'translateX(0)' :
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
}, children)
|
|
7134
|
+
transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
|
|
7135
|
+
'transition-duration': `${animationDuration}ms`,
|
|
7136
|
+
},
|
|
7137
|
+
}, children),
|
|
5770
7138
|
];
|
|
5771
|
-
}
|
|
7139
|
+
},
|
|
5772
7140
|
};
|
|
5773
7141
|
};
|
|
5774
7142
|
/**
|
|
@@ -5778,37 +7146,30 @@ const Sidenav = () => {
|
|
|
5778
7146
|
const SidenavItem = () => {
|
|
5779
7147
|
return {
|
|
5780
7148
|
view: ({ attrs, children }) => {
|
|
5781
|
-
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false } = attrs;
|
|
7149
|
+
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, } = attrs;
|
|
5782
7150
|
if (divider) {
|
|
5783
7151
|
return m('li.divider');
|
|
5784
7152
|
}
|
|
5785
7153
|
if (subheader) {
|
|
5786
7154
|
return m('li.subheader', text || children);
|
|
5787
7155
|
}
|
|
5788
|
-
const itemClasses = [
|
|
5789
|
-
|
|
5790
|
-
disabled ? 'disabled' : '',
|
|
5791
|
-
className
|
|
5792
|
-
].filter(Boolean).join(' ');
|
|
5793
|
-
const content = [
|
|
5794
|
-
icon && m('i.material-icons', icon),
|
|
5795
|
-
text || children
|
|
5796
|
-
];
|
|
7156
|
+
const itemClasses = [active ? 'active' : '', disabled ? 'disabled' : '', className].filter(Boolean).join(' ') || undefined;
|
|
7157
|
+
const content = [icon && m('i.material-icons', icon), text || children];
|
|
5797
7158
|
if (href && !disabled) {
|
|
5798
7159
|
return m('li', { class: itemClasses }, [
|
|
5799
7160
|
m('a', {
|
|
5800
7161
|
href,
|
|
5801
|
-
onclick: disabled ? undefined : onclick
|
|
5802
|
-
}, content)
|
|
7162
|
+
onclick: disabled ? undefined : onclick,
|
|
7163
|
+
}, content),
|
|
5803
7164
|
]);
|
|
5804
7165
|
}
|
|
5805
7166
|
return m('li', { class: itemClasses }, [
|
|
5806
7167
|
m('a', {
|
|
5807
7168
|
onclick: disabled ? undefined : onclick,
|
|
5808
|
-
href: '#!'
|
|
5809
|
-
}, content)
|
|
7169
|
+
href: '#!',
|
|
7170
|
+
}, content),
|
|
5810
7171
|
]);
|
|
5811
|
-
}
|
|
7172
|
+
},
|
|
5812
7173
|
};
|
|
5813
7174
|
};
|
|
5814
7175
|
/**
|
|
@@ -5859,7 +7220,7 @@ class SidenavManager {
|
|
|
5859
7220
|
const Breadcrumb = () => {
|
|
5860
7221
|
return {
|
|
5861
7222
|
view: ({ attrs }) => {
|
|
5862
|
-
const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false } = attrs;
|
|
7223
|
+
const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false, } = attrs;
|
|
5863
7224
|
if (items.length === 0) {
|
|
5864
7225
|
return null;
|
|
5865
7226
|
}
|
|
@@ -5868,52 +7229,46 @@ const Breadcrumb = () => {
|
|
|
5868
7229
|
if (maxItems && items.length > maxItems) {
|
|
5869
7230
|
const firstItem = items[0];
|
|
5870
7231
|
const lastItems = items.slice(-(maxItems - 2));
|
|
5871
|
-
displayItems = [
|
|
5872
|
-
firstItem,
|
|
5873
|
-
{ text: '...', disabled: true, className: 'breadcrumb-ellipsis' },
|
|
5874
|
-
...lastItems
|
|
5875
|
-
];
|
|
7232
|
+
displayItems = [firstItem, { text: '...', disabled: true, className: 'breadcrumb-ellipsis' }, ...lastItems];
|
|
5876
7233
|
}
|
|
5877
7234
|
return m('nav.breadcrumb', { class: className }, [
|
|
5878
|
-
m('ol.breadcrumb-list', displayItems
|
|
7235
|
+
m('ol.breadcrumb-list', displayItems
|
|
7236
|
+
.map((item, index) => {
|
|
5879
7237
|
const isLast = index === displayItems.length - 1;
|
|
5880
7238
|
const isFirst = index === 0;
|
|
5881
7239
|
return [
|
|
5882
7240
|
// Breadcrumb item
|
|
5883
7241
|
m('li.breadcrumb-item', {
|
|
5884
|
-
class: [
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
item.className || ''
|
|
5888
|
-
].filter(Boolean).join(' ')
|
|
7242
|
+
class: [item.active || isLast ? 'active' : '', item.disabled ? 'disabled' : '', item.className || '']
|
|
7243
|
+
.filter(Boolean)
|
|
7244
|
+
.join(' ') || undefined,
|
|
5889
7245
|
}, [
|
|
5890
|
-
item.href && !item.disabled && !isLast
|
|
5891
|
-
// Link item
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
// Text item (active or disabled)
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
7246
|
+
item.href && !item.disabled && !isLast
|
|
7247
|
+
? // Link item
|
|
7248
|
+
m('a.breadcrumb-link', {
|
|
7249
|
+
href: item.href,
|
|
7250
|
+
onclick: item.onclick,
|
|
7251
|
+
}, [
|
|
7252
|
+
showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
7253
|
+
showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
7254
|
+
m('span.breadcrumb-text', item.text),
|
|
7255
|
+
])
|
|
7256
|
+
: // Text item (active or disabled)
|
|
7257
|
+
m('span.breadcrumb-text', {
|
|
7258
|
+
onclick: item.disabled ? undefined : item.onclick,
|
|
7259
|
+
}, [
|
|
7260
|
+
showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
7261
|
+
showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
7262
|
+
item.text,
|
|
7263
|
+
]),
|
|
5908
7264
|
]),
|
|
5909
7265
|
// Separator (except for last item)
|
|
5910
|
-
!isLast && m('li.breadcrumb-separator', [
|
|
5911
|
-
m('i.material-icons', separator)
|
|
5912
|
-
])
|
|
7266
|
+
!isLast && m('li.breadcrumb-separator', [m('i.material-icons', separator)]),
|
|
5913
7267
|
];
|
|
5914
|
-
})
|
|
7268
|
+
})
|
|
7269
|
+
.reduce((acc, val) => acc.concat(val), [])),
|
|
5915
7270
|
]);
|
|
5916
|
-
}
|
|
7271
|
+
},
|
|
5917
7272
|
};
|
|
5918
7273
|
};
|
|
5919
7274
|
/**
|
|
@@ -5926,7 +7281,7 @@ const createBreadcrumb = (path, basePath = '/') => {
|
|
|
5926
7281
|
items.push({
|
|
5927
7282
|
text: 'Home',
|
|
5928
7283
|
href: basePath,
|
|
5929
|
-
icon: 'home'
|
|
7284
|
+
icon: 'home',
|
|
5930
7285
|
});
|
|
5931
7286
|
// Add path segments
|
|
5932
7287
|
let currentPath = basePath;
|
|
@@ -5936,7 +7291,7 @@ const createBreadcrumb = (path, basePath = '/') => {
|
|
|
5936
7291
|
items.push({
|
|
5937
7292
|
text: segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' '),
|
|
5938
7293
|
href: isLast ? undefined : currentPath,
|
|
5939
|
-
active: isLast
|
|
7294
|
+
active: isLast,
|
|
5940
7295
|
});
|
|
5941
7296
|
});
|
|
5942
7297
|
return items;
|
|
@@ -5955,19 +7310,18 @@ class BreadcrumbManager {
|
|
|
5955
7310
|
items.push({
|
|
5956
7311
|
text: 'Home',
|
|
5957
7312
|
href: '/',
|
|
5958
|
-
icon: 'home'
|
|
7313
|
+
icon: 'home',
|
|
5959
7314
|
});
|
|
5960
7315
|
let currentPath = '';
|
|
5961
7316
|
segments.forEach((segment, index) => {
|
|
5962
7317
|
currentPath += '/' + segment;
|
|
5963
7318
|
const isLast = index === segments.length - 1;
|
|
5964
7319
|
// Use custom text from config or format segment
|
|
5965
|
-
const text = routeConfig[currentPath] ||
|
|
5966
|
-
segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
|
|
7320
|
+
const text = routeConfig[currentPath] || segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
|
|
5967
7321
|
items.push({
|
|
5968
7322
|
text,
|
|
5969
7323
|
href: isLast ? undefined : currentPath,
|
|
5970
|
-
active: isLast
|
|
7324
|
+
active: isLast,
|
|
5971
7325
|
});
|
|
5972
7326
|
});
|
|
5973
7327
|
return items;
|
|
@@ -5979,7 +7333,7 @@ class BreadcrumbManager {
|
|
|
5979
7333
|
return hierarchy.map((item, index) => ({
|
|
5980
7334
|
text: item[textKey],
|
|
5981
7335
|
href: index === hierarchy.length - 1 ? undefined : item[pathKey],
|
|
5982
|
-
active: index === hierarchy.length - 1
|
|
7336
|
+
active: index === hierarchy.length - 1,
|
|
5983
7337
|
}));
|
|
5984
7338
|
}
|
|
5985
7339
|
}
|
|
@@ -6113,7 +7467,7 @@ const Wizard = () => {
|
|
|
6113
7467
|
hasError ? 'error' : '',
|
|
6114
7468
|
step.disabled ? 'disabled' : '',
|
|
6115
7469
|
step.optional ? 'optional' : ''
|
|
6116
|
-
].filter(Boolean).join(' '),
|
|
7470
|
+
].filter(Boolean).join(' ') || undefined,
|
|
6117
7471
|
onclick: allowHeaderNavigation && !step.disabled ?
|
|
6118
7472
|
() => goToStep(index, attrs) : undefined
|
|
6119
7473
|
}, [
|
|
@@ -6187,6 +7541,729 @@ const Stepper = () => {
|
|
|
6187
7541
|
};
|
|
6188
7542
|
};
|
|
6189
7543
|
|
|
7544
|
+
// Utility function to check if a node is the last in its branch
|
|
7545
|
+
const isNodeLastInBranch = (nodePath, rootNodes) => {
|
|
7546
|
+
// Navigate to the node's position and check if it's the last child at every level
|
|
7547
|
+
let currentNodes = rootNodes;
|
|
7548
|
+
for (let i = 0; i < nodePath.length; i++) {
|
|
7549
|
+
const index = nodePath[i];
|
|
7550
|
+
const isLastAtThisLevel = index === currentNodes.length - 1;
|
|
7551
|
+
// If this is not the last child at this level, then this node is not last in branch
|
|
7552
|
+
if (!isLastAtThisLevel) {
|
|
7553
|
+
return false;
|
|
7554
|
+
}
|
|
7555
|
+
// Move to the next level if it exists
|
|
7556
|
+
if (i < nodePath.length - 1) {
|
|
7557
|
+
const currentNode = currentNodes[index];
|
|
7558
|
+
if (currentNode.children) {
|
|
7559
|
+
currentNodes = currentNode.children;
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
return true;
|
|
7564
|
+
};
|
|
7565
|
+
const TreeNodeComponent = () => {
|
|
7566
|
+
return {
|
|
7567
|
+
view: ({ attrs }) => {
|
|
7568
|
+
const { node, level, isSelected, isExpanded, isFocused, showConnectors, iconType, selectionMode, onToggleExpand, onToggleSelect, onFocus, } = attrs;
|
|
7569
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
7570
|
+
const indentLevel = level * 24; // 24px per level
|
|
7571
|
+
return m('li.tree-node', {
|
|
7572
|
+
class: [
|
|
7573
|
+
isSelected && 'selected',
|
|
7574
|
+
isFocused && 'focused',
|
|
7575
|
+
node.disabled && 'disabled',
|
|
7576
|
+
hasChildren && 'has-children',
|
|
7577
|
+
attrs.isLastInBranch && 'tree-last-in-branch',
|
|
7578
|
+
]
|
|
7579
|
+
.filter(Boolean)
|
|
7580
|
+
.join(' ') || undefined,
|
|
7581
|
+
'data-node-id': node.id,
|
|
7582
|
+
'data-level': level,
|
|
7583
|
+
}, [
|
|
7584
|
+
// Node content
|
|
7585
|
+
m('.tree-node-content', {
|
|
7586
|
+
style: {
|
|
7587
|
+
paddingLeft: `${indentLevel}px`,
|
|
7588
|
+
},
|
|
7589
|
+
onclick: node.disabled
|
|
7590
|
+
? undefined
|
|
7591
|
+
: () => {
|
|
7592
|
+
if (selectionMode !== 'none') {
|
|
7593
|
+
onToggleSelect(node.id);
|
|
7594
|
+
}
|
|
7595
|
+
onFocus(node.id);
|
|
7596
|
+
},
|
|
7597
|
+
onkeydown: (e) => {
|
|
7598
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
7599
|
+
e.preventDefault();
|
|
7600
|
+
if (!node.disabled && selectionMode !== 'none') {
|
|
7601
|
+
onToggleSelect(node.id);
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
},
|
|
7605
|
+
tabindex: node.disabled ? -1 : 0,
|
|
7606
|
+
role: selectionMode === 'multiple' ? 'option' : 'treeitem',
|
|
7607
|
+
'aria-selected': selectionMode !== 'none' ? isSelected.toString() : undefined,
|
|
7608
|
+
'aria-expanded': hasChildren ? isExpanded.toString() : undefined,
|
|
7609
|
+
'aria-disabled': node.disabled ? 'true' : undefined,
|
|
7610
|
+
}, [
|
|
7611
|
+
// Connector lines
|
|
7612
|
+
showConnectors &&
|
|
7613
|
+
level > 0 &&
|
|
7614
|
+
m('.tree-connectors', Array.from({ length: level }, (_, i) => m('.tree-connector', {
|
|
7615
|
+
key: i,
|
|
7616
|
+
style: { left: `${i * 24 + 12}px` },
|
|
7617
|
+
}))),
|
|
7618
|
+
// Expand/collapse icon or spacer
|
|
7619
|
+
hasChildren
|
|
7620
|
+
? m('.tree-expand-icon', {
|
|
7621
|
+
onclick: (e) => {
|
|
7622
|
+
e.stopPropagation();
|
|
7623
|
+
if (!node.disabled) {
|
|
7624
|
+
onToggleExpand(node.id);
|
|
7625
|
+
}
|
|
7626
|
+
},
|
|
7627
|
+
class: iconType,
|
|
7628
|
+
}, [
|
|
7629
|
+
iconType === 'plus-minus'
|
|
7630
|
+
? m('span.tree-plus-minus', isExpanded ? '−' : '+')
|
|
7631
|
+
: iconType === 'triangle'
|
|
7632
|
+
? m('span.tree-triangle', { class: isExpanded ? 'expanded' : undefined }, '▶')
|
|
7633
|
+
: iconType === 'chevron'
|
|
7634
|
+
? m(MaterialIcon, {
|
|
7635
|
+
name: 'chevron',
|
|
7636
|
+
direction: isExpanded ? 'down' : 'right',
|
|
7637
|
+
class: 'tree-chevron-icon',
|
|
7638
|
+
})
|
|
7639
|
+
: m(MaterialIcon, {
|
|
7640
|
+
name: 'caret',
|
|
7641
|
+
direction: isExpanded ? 'down' : 'right',
|
|
7642
|
+
class: 'tree-caret-icon',
|
|
7643
|
+
}),
|
|
7644
|
+
])
|
|
7645
|
+
: m('.tree-expand-spacer'), // Spacer for alignment
|
|
7646
|
+
// Selection indicator for multiple selection
|
|
7647
|
+
selectionMode === 'multiple' &&
|
|
7648
|
+
m('.tree-selection-indicator', [
|
|
7649
|
+
m('input[type=checkbox]', {
|
|
7650
|
+
checked: isSelected,
|
|
7651
|
+
disabled: node.disabled,
|
|
7652
|
+
onchange: () => {
|
|
7653
|
+
if (!node.disabled) {
|
|
7654
|
+
onToggleSelect(node.id);
|
|
7655
|
+
}
|
|
7656
|
+
},
|
|
7657
|
+
onclick: (e) => e.stopPropagation(),
|
|
7658
|
+
}),
|
|
7659
|
+
]),
|
|
7660
|
+
// Node icon (optional)
|
|
7661
|
+
node.icon && m('i.tree-node-icon.material-icons', node.icon),
|
|
7662
|
+
// Node label
|
|
7663
|
+
m('span.tree-node-label', node.label),
|
|
7664
|
+
]),
|
|
7665
|
+
// Children (recursive)
|
|
7666
|
+
hasChildren &&
|
|
7667
|
+
isExpanded &&
|
|
7668
|
+
m('ul.tree-children', {
|
|
7669
|
+
role: 'group',
|
|
7670
|
+
'aria-expanded': 'true',
|
|
7671
|
+
}, node.children.map((child, childIndex) => {
|
|
7672
|
+
var _a, _b, _c, _d, _e, _f;
|
|
7673
|
+
// Calculate state for each child using treeState
|
|
7674
|
+
const childIsSelected = (_b = (_a = attrs.treeState) === null || _a === void 0 ? void 0 : _a.selectedIds.has(child.id)) !== null && _b !== void 0 ? _b : false;
|
|
7675
|
+
const childIsExpanded = (_d = (_c = attrs.treeState) === null || _c === void 0 ? void 0 : _c.expandedIds.has(child.id)) !== null && _d !== void 0 ? _d : false;
|
|
7676
|
+
const childIsFocused = ((_e = attrs.treeState) === null || _e === void 0 ? void 0 : _e.focusedNodeId) === child.id;
|
|
7677
|
+
// Calculate if this child is last in branch
|
|
7678
|
+
const childPath = [...(attrs.currentPath || []), childIndex];
|
|
7679
|
+
const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data) ?
|
|
7680
|
+
isNodeLastInBranch(childPath, attrs.treeAttrs.data) : false;
|
|
7681
|
+
return m(TreeNodeComponent, {
|
|
7682
|
+
key: child.id,
|
|
7683
|
+
node: child,
|
|
7684
|
+
level: level + 1,
|
|
7685
|
+
isSelected: childIsSelected,
|
|
7686
|
+
isExpanded: childIsExpanded,
|
|
7687
|
+
isFocused: childIsFocused,
|
|
7688
|
+
showConnectors,
|
|
7689
|
+
iconType,
|
|
7690
|
+
selectionMode,
|
|
7691
|
+
onToggleExpand,
|
|
7692
|
+
onToggleSelect,
|
|
7693
|
+
onFocus,
|
|
7694
|
+
isLastInBranch: childIsLastInBranch,
|
|
7695
|
+
currentPath: childPath,
|
|
7696
|
+
treeState: attrs.treeState,
|
|
7697
|
+
treeAttrs: attrs.treeAttrs,
|
|
7698
|
+
});
|
|
7699
|
+
})),
|
|
7700
|
+
]);
|
|
7701
|
+
},
|
|
7702
|
+
};
|
|
7703
|
+
};
|
|
7704
|
+
const TreeView = () => {
|
|
7705
|
+
const state = {
|
|
7706
|
+
selectedIds: new Set(),
|
|
7707
|
+
expandedIds: new Set(),
|
|
7708
|
+
focusedNodeId: null,
|
|
7709
|
+
treeMap: new Map(),
|
|
7710
|
+
};
|
|
7711
|
+
const buildTreeMap = (nodes, map) => {
|
|
7712
|
+
nodes.forEach((node) => {
|
|
7713
|
+
map.set(node.id, node);
|
|
7714
|
+
if (node.children) {
|
|
7715
|
+
buildTreeMap(node.children, map);
|
|
7716
|
+
}
|
|
7717
|
+
});
|
|
7718
|
+
};
|
|
7719
|
+
const initializeExpandedNodes = (nodes) => {
|
|
7720
|
+
nodes.forEach((node) => {
|
|
7721
|
+
if (node.expanded) {
|
|
7722
|
+
state.expandedIds.add(node.id);
|
|
7723
|
+
}
|
|
7724
|
+
if (node.children) {
|
|
7725
|
+
initializeExpandedNodes(node.children);
|
|
7726
|
+
}
|
|
7727
|
+
});
|
|
7728
|
+
};
|
|
7729
|
+
const handleToggleExpand = (nodeId, attrs) => {
|
|
7730
|
+
var _a;
|
|
7731
|
+
const isExpanded = state.expandedIds.has(nodeId);
|
|
7732
|
+
if (isExpanded) {
|
|
7733
|
+
state.expandedIds.delete(nodeId);
|
|
7734
|
+
}
|
|
7735
|
+
else {
|
|
7736
|
+
state.expandedIds.add(nodeId);
|
|
7737
|
+
}
|
|
7738
|
+
(_a = attrs.onexpand) === null || _a === void 0 ? void 0 : _a.call(attrs, { nodeId, expanded: !isExpanded });
|
|
7739
|
+
};
|
|
7740
|
+
const handleToggleSelect = (nodeId, attrs) => {
|
|
7741
|
+
var _a;
|
|
7742
|
+
const { selectionMode = 'single' } = attrs;
|
|
7743
|
+
if (selectionMode === 'single') {
|
|
7744
|
+
state.selectedIds.clear();
|
|
7745
|
+
state.selectedIds.add(nodeId);
|
|
7746
|
+
}
|
|
7747
|
+
else if (selectionMode === 'multiple') {
|
|
7748
|
+
if (state.selectedIds.has(nodeId)) {
|
|
7749
|
+
state.selectedIds.delete(nodeId);
|
|
7750
|
+
}
|
|
7751
|
+
else {
|
|
7752
|
+
state.selectedIds.add(nodeId);
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
(_a = attrs.onselection) === null || _a === void 0 ? void 0 : _a.call(attrs, Array.from(state.selectedIds));
|
|
7756
|
+
};
|
|
7757
|
+
const handleFocus = (nodeId) => {
|
|
7758
|
+
state.focusedNodeId = nodeId;
|
|
7759
|
+
};
|
|
7760
|
+
const renderNodes = (nodes, attrs, level = 0, parentPath = []) => {
|
|
7761
|
+
return nodes.map((node, index) => {
|
|
7762
|
+
var _a, _b, _c;
|
|
7763
|
+
const isSelected = state.selectedIds.has(node.id);
|
|
7764
|
+
const isExpanded = state.expandedIds.has(node.id);
|
|
7765
|
+
const isFocused = state.focusedNodeId === node.id;
|
|
7766
|
+
const currentPath = [...parentPath, index];
|
|
7767
|
+
const isLastInBranch = isNodeLastInBranch(currentPath, attrs.data);
|
|
7768
|
+
return m(TreeNodeComponent, {
|
|
7769
|
+
key: node.id,
|
|
7770
|
+
node,
|
|
7771
|
+
level,
|
|
7772
|
+
isSelected,
|
|
7773
|
+
isExpanded,
|
|
7774
|
+
isFocused,
|
|
7775
|
+
showConnectors: (_a = attrs.showConnectors) !== null && _a !== void 0 ? _a : true,
|
|
7776
|
+
iconType: (_b = attrs.iconType) !== null && _b !== void 0 ? _b : 'caret',
|
|
7777
|
+
selectionMode: (_c = attrs.selectionMode) !== null && _c !== void 0 ? _c : 'single',
|
|
7778
|
+
onToggleExpand: (nodeId) => handleToggleExpand(nodeId, attrs),
|
|
7779
|
+
onToggleSelect: (nodeId) => handleToggleSelect(nodeId, attrs),
|
|
7780
|
+
onFocus: handleFocus,
|
|
7781
|
+
isLastInBranch,
|
|
7782
|
+
currentPath,
|
|
7783
|
+
// Pass state and attrs for recursive rendering
|
|
7784
|
+
treeState: state,
|
|
7785
|
+
treeAttrs: attrs,
|
|
7786
|
+
});
|
|
7787
|
+
});
|
|
7788
|
+
};
|
|
7789
|
+
return {
|
|
7790
|
+
oninit: ({ attrs }) => {
|
|
7791
|
+
// Build internal tree map for efficient lookups
|
|
7792
|
+
buildTreeMap(attrs.data, state.treeMap);
|
|
7793
|
+
// Initialize expanded nodes from data
|
|
7794
|
+
initializeExpandedNodes(attrs.data);
|
|
7795
|
+
// Initialize selected nodes from props
|
|
7796
|
+
if (attrs.selectedIds) {
|
|
7797
|
+
state.selectedIds = new Set(attrs.selectedIds);
|
|
7798
|
+
}
|
|
7799
|
+
},
|
|
7800
|
+
onupdate: ({ attrs }) => {
|
|
7801
|
+
// Sync selectedIds prop with internal state
|
|
7802
|
+
if (attrs.selectedIds) {
|
|
7803
|
+
const newSelection = new Set(attrs.selectedIds);
|
|
7804
|
+
if (newSelection.size !== state.selectedIds.size ||
|
|
7805
|
+
!Array.from(newSelection).every((id) => state.selectedIds.has(id))) {
|
|
7806
|
+
state.selectedIds = newSelection;
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
},
|
|
7810
|
+
view: ({ attrs }) => {
|
|
7811
|
+
const { data, className, style, id, selectionMode = 'single', showConnectors = true } = attrs;
|
|
7812
|
+
return m('div.tree-view', {
|
|
7813
|
+
class: [
|
|
7814
|
+
className,
|
|
7815
|
+
showConnectors && 'show-connectors'
|
|
7816
|
+
].filter(Boolean).join(' ') || undefined,
|
|
7817
|
+
style,
|
|
7818
|
+
id,
|
|
7819
|
+
role: selectionMode === 'multiple' ? 'listbox' : 'tree',
|
|
7820
|
+
'aria-multiselectable': selectionMode === 'multiple' ? 'true' : 'false',
|
|
7821
|
+
}, [
|
|
7822
|
+
m('ul.tree-root', {
|
|
7823
|
+
role: 'group',
|
|
7824
|
+
}, renderNodes(data, attrs)),
|
|
7825
|
+
]);
|
|
7826
|
+
},
|
|
7827
|
+
};
|
|
7828
|
+
};
|
|
7829
|
+
|
|
7830
|
+
/**
|
|
7831
|
+
* Timeline Component
|
|
7832
|
+
* Displays a sequence of events in chronological order with connecting lines
|
|
7833
|
+
* Supports both vertical and horizontal orientations with Material Design styling
|
|
7834
|
+
*/
|
|
7835
|
+
const Timeline = () => {
|
|
7836
|
+
const formatTimestamp = (timestamp) => {
|
|
7837
|
+
if (!timestamp)
|
|
7838
|
+
return '';
|
|
7839
|
+
if (typeof timestamp === 'string')
|
|
7840
|
+
return timestamp;
|
|
7841
|
+
return timestamp.toLocaleDateString();
|
|
7842
|
+
};
|
|
7843
|
+
const getColorClass = (color) => {
|
|
7844
|
+
switch (color) {
|
|
7845
|
+
case 'primary':
|
|
7846
|
+
return 'timeline-primary';
|
|
7847
|
+
case 'secondary':
|
|
7848
|
+
return 'timeline-secondary';
|
|
7849
|
+
case 'success':
|
|
7850
|
+
return 'timeline-success';
|
|
7851
|
+
case 'warning':
|
|
7852
|
+
return 'timeline-warning';
|
|
7853
|
+
case 'error':
|
|
7854
|
+
return 'timeline-error';
|
|
7855
|
+
case 'info':
|
|
7856
|
+
return 'timeline-info';
|
|
7857
|
+
default:
|
|
7858
|
+
return 'timeline-default';
|
|
7859
|
+
}
|
|
7860
|
+
};
|
|
7861
|
+
return {
|
|
7862
|
+
view: ({ attrs }) => {
|
|
7863
|
+
const { items = [], orientation = 'vertical', position = 'right', showConnector = true, className = '', showTimestamps = true, compact = false, } = attrs;
|
|
7864
|
+
const timelineClasses = [
|
|
7865
|
+
'timeline',
|
|
7866
|
+
`timeline-${orientation}`,
|
|
7867
|
+
`timeline-${position}`,
|
|
7868
|
+
showConnector ? 'timeline-connector' : '',
|
|
7869
|
+
compact ? 'timeline-compact' : '',
|
|
7870
|
+
className,
|
|
7871
|
+
]
|
|
7872
|
+
.filter(Boolean)
|
|
7873
|
+
.join(' ') || undefined;
|
|
7874
|
+
return m('div', { className: timelineClasses }, [
|
|
7875
|
+
items.map((item, index) => {
|
|
7876
|
+
const isAlternate = position === 'alternate';
|
|
7877
|
+
const itemPosition = isAlternate ? (index % 2 === 0 ? 'right' : 'left') : position;
|
|
7878
|
+
const itemClasses = [
|
|
7879
|
+
'timeline-item',
|
|
7880
|
+
`timeline-item-${itemPosition}`,
|
|
7881
|
+
getColorClass(item.color),
|
|
7882
|
+
item.disabled ? 'timeline-item-disabled' : '',
|
|
7883
|
+
item.className || '',
|
|
7884
|
+
]
|
|
7885
|
+
.filter(Boolean)
|
|
7886
|
+
.join(' ') || undefined;
|
|
7887
|
+
const handleItemClick = (e) => {
|
|
7888
|
+
if (item.disabled)
|
|
7889
|
+
return;
|
|
7890
|
+
if (item.onclick) {
|
|
7891
|
+
e.preventDefault();
|
|
7892
|
+
item.onclick(item, e);
|
|
7893
|
+
}
|
|
7894
|
+
};
|
|
7895
|
+
const isVertical = orientation === 'vertical';
|
|
7896
|
+
return m('div', {
|
|
7897
|
+
key: item.id || index,
|
|
7898
|
+
className: itemClasses,
|
|
7899
|
+
onclick: item.onclick ? handleItemClick : undefined,
|
|
7900
|
+
role: item.onclick ? 'button' : undefined,
|
|
7901
|
+
tabindex: item.onclick && !item.disabled ? 0 : undefined,
|
|
7902
|
+
'aria-disabled': item.disabled ? 'true' : undefined,
|
|
7903
|
+
}, [
|
|
7904
|
+
// Timestamp (on opposite side of content from bullet)
|
|
7905
|
+
isVertical &&
|
|
7906
|
+
showTimestamps &&
|
|
7907
|
+
item.timestamp &&
|
|
7908
|
+
m(`.timeline-timestamp-separate${!item.icon ? '.timeline-dot-small' : ''}`, formatTimestamp(item.timestamp)),
|
|
7909
|
+
// Timeline separator containing dot and connector
|
|
7910
|
+
m('.timeline-separator', [
|
|
7911
|
+
// Timeline dot/icon
|
|
7912
|
+
m(`.timeline-dot${!item.icon ? '.timeline-dot-small' : ''}`, [
|
|
7913
|
+
item.icon ? m('i.material-icons.timeline-icon', item.icon) : m('.timeline-marker'),
|
|
7914
|
+
]),
|
|
7915
|
+
// Timeline connector (only show if not the last item and connectors are enabled)
|
|
7916
|
+
showConnector && index < items.length - 1 && m('.timeline-connector'),
|
|
7917
|
+
]),
|
|
7918
|
+
// Content container
|
|
7919
|
+
m('.timeline-content', [
|
|
7920
|
+
// Timestamp for horizontal layout or when not shown separately
|
|
7921
|
+
!isVertical &&
|
|
7922
|
+
showTimestamps &&
|
|
7923
|
+
item.timestamp &&
|
|
7924
|
+
m('.timeline-timestamp', formatTimestamp(item.timestamp)),
|
|
7925
|
+
// Main content
|
|
7926
|
+
item.content
|
|
7927
|
+
? item.content
|
|
7928
|
+
: m('.timeline-text', [
|
|
7929
|
+
item.label && m('.timeline-label', item.label),
|
|
7930
|
+
item.description && m('.timeline-description', item.description),
|
|
7931
|
+
]),
|
|
7932
|
+
]),
|
|
7933
|
+
]);
|
|
7934
|
+
}),
|
|
7935
|
+
]);
|
|
7936
|
+
},
|
|
7937
|
+
};
|
|
7938
|
+
};
|
|
7939
|
+
|
|
7940
|
+
const Masonry = () => {
|
|
7941
|
+
let containerRef = null;
|
|
7942
|
+
const itemHeights = []; // measured heights
|
|
7943
|
+
let resizeObserver = null;
|
|
7944
|
+
const defaultBreakpoints = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5 };
|
|
7945
|
+
const getColumnsCount = (columns = 3) => {
|
|
7946
|
+
if (typeof columns === 'number')
|
|
7947
|
+
return columns;
|
|
7948
|
+
const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), columns);
|
|
7949
|
+
const width = window.innerWidth;
|
|
7950
|
+
if (width >= 1200)
|
|
7951
|
+
return breakpoints.xl || 5;
|
|
7952
|
+
if (width >= 992)
|
|
7953
|
+
return breakpoints.lg || 4;
|
|
7954
|
+
if (width >= 768)
|
|
7955
|
+
return breakpoints.md || 3;
|
|
7956
|
+
if (width >= 576)
|
|
7957
|
+
return breakpoints.sm || 2;
|
|
7958
|
+
return breakpoints.xs || 1;
|
|
7959
|
+
};
|
|
7960
|
+
const setupResizeObserver = () => {
|
|
7961
|
+
if (resizeObserver)
|
|
7962
|
+
return;
|
|
7963
|
+
if (typeof ResizeObserver === 'undefined')
|
|
7964
|
+
return;
|
|
7965
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
7966
|
+
for (const entry of entries) {
|
|
7967
|
+
const el = entry.target;
|
|
7968
|
+
const index = Number(el.dataset.index);
|
|
7969
|
+
if (!isNaN(index)) {
|
|
7970
|
+
const h = el.offsetHeight;
|
|
7971
|
+
if (itemHeights[index] !== h) {
|
|
7972
|
+
itemHeights[index] = h;
|
|
7973
|
+
m.redraw();
|
|
7974
|
+
}
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
});
|
|
7978
|
+
};
|
|
7979
|
+
const cleanup = () => {
|
|
7980
|
+
if (resizeObserver) {
|
|
7981
|
+
resizeObserver.disconnect();
|
|
7982
|
+
resizeObserver = null;
|
|
7983
|
+
}
|
|
7984
|
+
};
|
|
7985
|
+
return {
|
|
7986
|
+
onremove: cleanup,
|
|
7987
|
+
view: ({ attrs, children }) => {
|
|
7988
|
+
const { columns = 3, spacing = 16, className = '', onItemClick, cssOnly = false, animationDelay } = attrs;
|
|
7989
|
+
const gap = typeof spacing === 'number' ? spacing : parseInt(spacing, 10) || 16;
|
|
7990
|
+
const columnsCount = typeof columns === 'number' ? columns : getColumnsCount(columns);
|
|
7991
|
+
const containerClasses = [
|
|
7992
|
+
'masonry',
|
|
7993
|
+
cssOnly ? 'masonry-css' : 'masonry-js',
|
|
7994
|
+
animationDelay ? 'masonry-animated' : '',
|
|
7995
|
+
className,
|
|
7996
|
+
]
|
|
7997
|
+
.filter(Boolean)
|
|
7998
|
+
.join(' ');
|
|
7999
|
+
const containerStyle = { position: 'relative' };
|
|
8000
|
+
// --- CSS-only fallback ---
|
|
8001
|
+
if (cssOnly) {
|
|
8002
|
+
containerStyle.display = 'grid';
|
|
8003
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
8004
|
+
containerStyle.gap = `${gap}px`;
|
|
8005
|
+
return m('div', { className: containerClasses, style: containerStyle }, children);
|
|
8006
|
+
}
|
|
8007
|
+
// --- JS Masonry ---
|
|
8008
|
+
const containerWidth = (containerRef === null || containerRef === void 0 ? void 0 : containerRef.offsetWidth) || 800;
|
|
8009
|
+
const totalGapWidth = gap * (columnsCount - 1);
|
|
8010
|
+
const columnWidth = (containerWidth - totalGapWidth) / columnsCount;
|
|
8011
|
+
const columnHeights = new Array(columnsCount).fill(0);
|
|
8012
|
+
const positionedChildren = [];
|
|
8013
|
+
(Array.isArray(children) ? children : [children]).forEach((child, index) => {
|
|
8014
|
+
var _a;
|
|
8015
|
+
const shortestColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
|
|
8016
|
+
const x = shortestColumnIndex * (columnWidth + gap);
|
|
8017
|
+
const y = columnHeights[shortestColumnIndex];
|
|
8018
|
+
const itemHeight = (_a = itemHeights[index]) !== null && _a !== void 0 ? _a : 200; // fallback until measured
|
|
8019
|
+
columnHeights[shortestColumnIndex] += itemHeight + gap;
|
|
8020
|
+
const itemStyle = {
|
|
8021
|
+
position: 'absolute',
|
|
8022
|
+
left: `${x}px`,
|
|
8023
|
+
top: `${y}px`,
|
|
8024
|
+
width: `${columnWidth}px`,
|
|
8025
|
+
transition: 'all 0.3s ease',
|
|
8026
|
+
animationDelay: animationDelay ? `${index * animationDelay}ms` : undefined,
|
|
8027
|
+
};
|
|
8028
|
+
positionedChildren.push(m('div', {
|
|
8029
|
+
key: `masonry-item-${index}`,
|
|
8030
|
+
className: 'masonry-item',
|
|
8031
|
+
style: itemStyle,
|
|
8032
|
+
'data-index': index,
|
|
8033
|
+
onclick: onItemClick ? (e) => onItemClick(index, e) : undefined,
|
|
8034
|
+
oncreate: ({ dom }) => {
|
|
8035
|
+
const el = dom;
|
|
8036
|
+
setupResizeObserver();
|
|
8037
|
+
resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(el);
|
|
8038
|
+
const h = el.offsetHeight;
|
|
8039
|
+
if (itemHeights[index] !== h) {
|
|
8040
|
+
itemHeights[index] = h;
|
|
8041
|
+
m.redraw();
|
|
8042
|
+
}
|
|
8043
|
+
},
|
|
8044
|
+
onremove: ({ dom }) => {
|
|
8045
|
+
resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.unobserve(dom);
|
|
8046
|
+
},
|
|
8047
|
+
}, child));
|
|
8048
|
+
});
|
|
8049
|
+
containerStyle.height = `${Math.max(...columnHeights) - gap}px`;
|
|
8050
|
+
return m('div', {
|
|
8051
|
+
className: containerClasses,
|
|
8052
|
+
style: containerStyle,
|
|
8053
|
+
oncreate: ({ dom }) => {
|
|
8054
|
+
containerRef = dom;
|
|
8055
|
+
},
|
|
8056
|
+
}, positionedChildren);
|
|
8057
|
+
},
|
|
8058
|
+
};
|
|
8059
|
+
};
|
|
8060
|
+
|
|
8061
|
+
/**
|
|
8062
|
+
* ImageList Component
|
|
8063
|
+
* Displays a collection of images in various grid layouts
|
|
8064
|
+
* Supports standard grid, quilted (varied sizes), masonry, and woven patterns
|
|
8065
|
+
*/
|
|
8066
|
+
const ImageList = () => {
|
|
8067
|
+
const defaultBreakpoints = {
|
|
8068
|
+
xs: 1,
|
|
8069
|
+
sm: 2,
|
|
8070
|
+
md: 3,
|
|
8071
|
+
lg: 4,
|
|
8072
|
+
xl: 5
|
|
8073
|
+
};
|
|
8074
|
+
const getColumnsCount = (cols = 3) => {
|
|
8075
|
+
if (typeof cols === 'number')
|
|
8076
|
+
return cols;
|
|
8077
|
+
const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), cols);
|
|
8078
|
+
const width = typeof window !== 'undefined' ? window.innerWidth : 1024;
|
|
8079
|
+
if (width >= 1200)
|
|
8080
|
+
return breakpoints.xl || 5;
|
|
8081
|
+
if (width >= 992)
|
|
8082
|
+
return breakpoints.lg || 4;
|
|
8083
|
+
if (width >= 768)
|
|
8084
|
+
return breakpoints.md || 3;
|
|
8085
|
+
if (width >= 576)
|
|
8086
|
+
return breakpoints.sm || 2;
|
|
8087
|
+
return breakpoints.xs || 1;
|
|
8088
|
+
};
|
|
8089
|
+
const handleImageLoad = (e) => {
|
|
8090
|
+
const img = e.target;
|
|
8091
|
+
img.classList.add('loaded');
|
|
8092
|
+
};
|
|
8093
|
+
const handleImageError = (e) => {
|
|
8094
|
+
const img = e.target;
|
|
8095
|
+
img.classList.add('error');
|
|
8096
|
+
// Could set a placeholder image here
|
|
8097
|
+
img.alt = 'Failed to load image';
|
|
8098
|
+
};
|
|
8099
|
+
const renderImage = (item, index, options) => {
|
|
8100
|
+
const { src, alt = '', title, subtitle, onclick, actionButton, className = '', loading = options.loading || 'lazy', aspectRatio, cols = 1, rows = 1, featured = false } = item;
|
|
8101
|
+
const itemClasses = [
|
|
8102
|
+
'image-list-item',
|
|
8103
|
+
featured ? 'image-list-item-featured' : '',
|
|
8104
|
+
onclick ? 'image-list-item-clickable' : '',
|
|
8105
|
+
className
|
|
8106
|
+
].filter(Boolean).join(' ');
|
|
8107
|
+
const itemStyle = {};
|
|
8108
|
+
// Quilted layout with fixed alternating pattern
|
|
8109
|
+
if (options.variant === 'quilted') {
|
|
8110
|
+
if (featured || index % 7 === 0) {
|
|
8111
|
+
itemStyle.gridColumnEnd = 'span 2';
|
|
8112
|
+
itemStyle.gridRowEnd = 'span 2';
|
|
8113
|
+
}
|
|
8114
|
+
else if (index % 3 === 0) {
|
|
8115
|
+
itemStyle.gridColumnEnd = 'span 2';
|
|
8116
|
+
itemStyle.gridRowEnd = 'span 1';
|
|
8117
|
+
}
|
|
8118
|
+
else {
|
|
8119
|
+
itemStyle.gridColumnEnd = 'span 1';
|
|
8120
|
+
itemStyle.gridRowEnd = 'span 1';
|
|
8121
|
+
}
|
|
8122
|
+
}
|
|
8123
|
+
// Woven layout with varied sizes based on item properties
|
|
8124
|
+
if (options.variant === 'woven') {
|
|
8125
|
+
itemStyle.gridColumnEnd = `span ${cols}`;
|
|
8126
|
+
itemStyle.gridRowEnd = `span ${rows}`;
|
|
8127
|
+
}
|
|
8128
|
+
// Masonry layout - prevent break inside items
|
|
8129
|
+
if (options.variant === 'masonry') {
|
|
8130
|
+
itemStyle.breakInside = 'avoid';
|
|
8131
|
+
itemStyle.marginBottom = typeof options.gap === 'number' ? `${options.gap}px` : options.gap || '4px';
|
|
8132
|
+
itemStyle.display = 'inline-block';
|
|
8133
|
+
itemStyle.width = '100%';
|
|
8134
|
+
}
|
|
8135
|
+
// Custom aspect ratio
|
|
8136
|
+
if (aspectRatio && options.variant !== 'masonry') {
|
|
8137
|
+
itemStyle.aspectRatio = aspectRatio.toString();
|
|
8138
|
+
}
|
|
8139
|
+
const handleItemClick = (e) => {
|
|
8140
|
+
if (onclick) {
|
|
8141
|
+
e.preventDefault();
|
|
8142
|
+
onclick(item, e);
|
|
8143
|
+
}
|
|
8144
|
+
};
|
|
8145
|
+
const handleActionClick = (e) => {
|
|
8146
|
+
e.stopPropagation();
|
|
8147
|
+
if (actionButton === null || actionButton === void 0 ? void 0 : actionButton.onclick) {
|
|
8148
|
+
actionButton.onclick(item, e);
|
|
8149
|
+
}
|
|
8150
|
+
};
|
|
8151
|
+
return m(`.${itemClasses}`, {
|
|
8152
|
+
key: `image-${index}`,
|
|
8153
|
+
style: itemStyle,
|
|
8154
|
+
onclick: onclick ? handleItemClick : undefined,
|
|
8155
|
+
role: onclick ? 'button' : undefined,
|
|
8156
|
+
tabindex: onclick ? 0 : undefined,
|
|
8157
|
+
}, [
|
|
8158
|
+
m('.image-list-item-img', [
|
|
8159
|
+
m('img', {
|
|
8160
|
+
src,
|
|
8161
|
+
alt,
|
|
8162
|
+
loading,
|
|
8163
|
+
onload: handleImageLoad,
|
|
8164
|
+
onerror: handleImageError,
|
|
8165
|
+
draggable: false,
|
|
8166
|
+
}),
|
|
8167
|
+
// Loading placeholder
|
|
8168
|
+
m('.image-list-item-placeholder'),
|
|
8169
|
+
]),
|
|
8170
|
+
// Title overlay
|
|
8171
|
+
(options.showTitles && (title || subtitle)) &&
|
|
8172
|
+
m('.image-list-item-bar', [
|
|
8173
|
+
m('.image-list-item-title-wrap', [
|
|
8174
|
+
title && m('.image-list-item-title', title),
|
|
8175
|
+
subtitle && m('.image-list-item-subtitle', subtitle)
|
|
8176
|
+
])
|
|
8177
|
+
]),
|
|
8178
|
+
// Action button
|
|
8179
|
+
(options.showActions && actionButton) &&
|
|
8180
|
+
m('button.image-list-item-action', {
|
|
8181
|
+
class: `image-list-action-${actionButton.position || 'top-right'}`,
|
|
8182
|
+
onclick: handleActionClick,
|
|
8183
|
+
'aria-label': actionButton.ariaLabel || `Action for ${title || alt}`,
|
|
8184
|
+
}, [
|
|
8185
|
+
m('i.material-icons', actionButton.icon)
|
|
8186
|
+
])
|
|
8187
|
+
]);
|
|
8188
|
+
};
|
|
8189
|
+
return {
|
|
8190
|
+
view: ({ attrs }) => {
|
|
8191
|
+
const { items = [], variant = 'standard', cols = 3, gap = 4, rowHeight = 'auto', className = '', showTitles = false, showActions = false } = attrs;
|
|
8192
|
+
const columnsCount = getColumnsCount(cols);
|
|
8193
|
+
const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
|
|
8194
|
+
const containerClasses = [
|
|
8195
|
+
'image-list',
|
|
8196
|
+
`image-list-${variant}`,
|
|
8197
|
+
showTitles ? 'image-list-with-titles' : '',
|
|
8198
|
+
showActions ? 'image-list-with-actions' : '',
|
|
8199
|
+
className
|
|
8200
|
+
].filter(Boolean).join(' ');
|
|
8201
|
+
const containerStyle = {
|
|
8202
|
+
gap: gapValue
|
|
8203
|
+
};
|
|
8204
|
+
// Set up grid based on variant
|
|
8205
|
+
switch (variant) {
|
|
8206
|
+
case 'standard':
|
|
8207
|
+
containerStyle.display = 'grid';
|
|
8208
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
8209
|
+
if (rowHeight !== 'auto') {
|
|
8210
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight;
|
|
8211
|
+
}
|
|
8212
|
+
break;
|
|
8213
|
+
case 'quilted':
|
|
8214
|
+
// Fixed pattern like woven
|
|
8215
|
+
containerStyle.display = 'grid';
|
|
8216
|
+
containerStyle.gridTemplateColumns = `repeat(${Math.max(columnsCount * 2, 4)}, 1fr)`;
|
|
8217
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '150px';
|
|
8218
|
+
containerStyle.gridAutoFlow = 'dense';
|
|
8219
|
+
break;
|
|
8220
|
+
case 'masonry':
|
|
8221
|
+
// Use CSS columns for masonry effect
|
|
8222
|
+
containerStyle.display = 'block';
|
|
8223
|
+
containerStyle.columnCount = columnsCount;
|
|
8224
|
+
containerStyle.columnGap = gapValue;
|
|
8225
|
+
containerStyle.columnFill = 'balance';
|
|
8226
|
+
containerStyle.orphans = 1;
|
|
8227
|
+
containerStyle.widows = 1;
|
|
8228
|
+
break;
|
|
8229
|
+
case 'woven':
|
|
8230
|
+
// Varied sizes based on item cols/rows
|
|
8231
|
+
containerStyle.display = 'grid';
|
|
8232
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
8233
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '200px';
|
|
8234
|
+
containerStyle.gridAutoFlow = 'dense';
|
|
8235
|
+
break;
|
|
8236
|
+
}
|
|
8237
|
+
return m(`.${containerClasses}`, {
|
|
8238
|
+
style: containerStyle
|
|
8239
|
+
}, [
|
|
8240
|
+
items.map((item, index) => renderImage(item, index, attrs))
|
|
8241
|
+
]);
|
|
8242
|
+
}
|
|
8243
|
+
};
|
|
8244
|
+
};
|
|
8245
|
+
|
|
8246
|
+
/**
|
|
8247
|
+
* @fileoverview Core TypeScript utility types for mithril-materialized library
|
|
8248
|
+
* These types improve type safety and developer experience across all components
|
|
8249
|
+
*/
|
|
8250
|
+
/**
|
|
8251
|
+
* Type guard to check if validation result indicates success
|
|
8252
|
+
* @param result - The validation result to check
|
|
8253
|
+
* @returns True if validation passed
|
|
8254
|
+
*/
|
|
8255
|
+
const isValidationSuccess = (result) => result === true || result === '';
|
|
8256
|
+
/**
|
|
8257
|
+
* Type guard to check if validation result indicates an error
|
|
8258
|
+
* @param result - The validation result to check
|
|
8259
|
+
* @returns True if validation failed
|
|
8260
|
+
*/
|
|
8261
|
+
const isValidationError = (result) => !isValidationSuccess(result);
|
|
8262
|
+
// ============================================================================
|
|
8263
|
+
// EXPORTS
|
|
8264
|
+
// ============================================================================
|
|
8265
|
+
// All types are already exported via individual export declarations above
|
|
8266
|
+
|
|
6190
8267
|
exports.AnchorItem = AnchorItem;
|
|
6191
8268
|
exports.Autocomplete = Autocomplete;
|
|
6192
8269
|
exports.Breadcrumb = Breadcrumb;
|
|
@@ -6201,7 +8278,9 @@ exports.Collapsible = Collapsible;
|
|
|
6201
8278
|
exports.CollapsibleItem = CollapsibleItem;
|
|
6202
8279
|
exports.Collection = Collection;
|
|
6203
8280
|
exports.ColorInput = ColorInput;
|
|
8281
|
+
exports.DataTable = DataTable;
|
|
6204
8282
|
exports.DatePicker = DatePicker;
|
|
8283
|
+
exports.DoubleRangeSlider = DoubleRangeSlider;
|
|
6205
8284
|
exports.Dropdown = Dropdown;
|
|
6206
8285
|
exports.EmailInput = EmailInput;
|
|
6207
8286
|
exports.FileInput = FileInput;
|
|
@@ -6210,16 +8289,20 @@ exports.FlatButton = FlatButton;
|
|
|
6210
8289
|
exports.FloatingActionButton = FloatingActionButton;
|
|
6211
8290
|
exports.HelperText = HelperText;
|
|
6212
8291
|
exports.Icon = Icon;
|
|
8292
|
+
exports.ImageList = ImageList;
|
|
6213
8293
|
exports.InputCheckbox = InputCheckbox;
|
|
6214
8294
|
exports.Label = Label;
|
|
6215
8295
|
exports.LargeButton = LargeButton;
|
|
6216
8296
|
exports.ListItem = ListItem;
|
|
6217
8297
|
exports.Mandatory = Mandatory;
|
|
8298
|
+
exports.Masonry = Masonry;
|
|
6218
8299
|
exports.MaterialBox = MaterialBox;
|
|
8300
|
+
exports.MaterialIcon = MaterialIcon;
|
|
6219
8301
|
exports.ModalPanel = ModalPanel;
|
|
6220
8302
|
exports.NumberInput = NumberInput;
|
|
6221
8303
|
exports.Options = Options;
|
|
6222
8304
|
exports.Pagination = Pagination;
|
|
8305
|
+
exports.PaginationControls = PaginationControls;
|
|
6223
8306
|
exports.Parallax = Parallax;
|
|
6224
8307
|
exports.PasswordInput = PasswordInput;
|
|
6225
8308
|
exports.Pushpin = Pushpin;
|
|
@@ -6234,6 +8317,7 @@ exports.Select = Select;
|
|
|
6234
8317
|
exports.Sidenav = Sidenav;
|
|
6235
8318
|
exports.SidenavItem = SidenavItem;
|
|
6236
8319
|
exports.SidenavManager = SidenavManager;
|
|
8320
|
+
exports.SingleRangeSlider = SingleRangeSlider;
|
|
6237
8321
|
exports.SmallButton = SmallButton;
|
|
6238
8322
|
exports.Stepper = Stepper;
|
|
6239
8323
|
exports.SubmitButton = SubmitButton;
|
|
@@ -6245,10 +8329,12 @@ exports.ThemeManager = ThemeManager;
|
|
|
6245
8329
|
exports.ThemeSwitcher = ThemeSwitcher;
|
|
6246
8330
|
exports.ThemeToggle = ThemeToggle;
|
|
6247
8331
|
exports.TimePicker = TimePicker;
|
|
8332
|
+
exports.Timeline = Timeline;
|
|
6248
8333
|
exports.Toast = Toast;
|
|
6249
8334
|
exports.ToastComponent = ToastComponent;
|
|
6250
8335
|
exports.Tooltip = Tooltip;
|
|
6251
8336
|
exports.TooltipComponent = TooltipComponent;
|
|
8337
|
+
exports.TreeView = TreeView;
|
|
6252
8338
|
exports.UrlInput = UrlInput;
|
|
6253
8339
|
exports.Wizard = Wizard;
|
|
6254
8340
|
exports.createBreadcrumb = createBreadcrumb;
|
|
@@ -6256,6 +8342,8 @@ exports.getDropdownStyles = getDropdownStyles;
|
|
|
6256
8342
|
exports.initPushpins = initPushpins;
|
|
6257
8343
|
exports.initTooltips = initTooltips;
|
|
6258
8344
|
exports.isNumeric = isNumeric;
|
|
8345
|
+
exports.isValidationError = isValidationError;
|
|
8346
|
+
exports.isValidationSuccess = isValidationSuccess;
|
|
6259
8347
|
exports.padLeft = padLeft;
|
|
6260
8348
|
exports.range = range;
|
|
6261
8349
|
exports.toast = toast;
|