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