mithril-materialized 2.0.0-beta.8 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +353 -10
- package/dist/advanced.css +6 -6
- package/dist/button.d.ts +56 -11
- package/dist/components.css +1674 -6
- package/dist/core.css +13 -13
- package/dist/datatable.d.ts +291 -0
- package/dist/datepicker.d.ts +12 -2
- package/dist/forms.css +344 -13
- package/dist/icon.d.ts +2 -2
- package/dist/image-list.d.ts +70 -0
- package/dist/index.css +1940 -20
- package/dist/index.d.ts +8 -0
- package/dist/index.esm.js +2700 -639
- package/dist/index.js +2710 -638
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +2710 -638
- package/dist/input-options.d.ts +18 -4
- package/dist/input.d.ts +0 -1
- package/dist/label.d.ts +2 -0
- package/dist/masonry.d.ts +17 -0
- package/dist/material-icon.d.ts +3 -0
- package/dist/pickers.css +45 -0
- package/dist/range-slider.d.ts +42 -0
- package/dist/timeline.d.ts +43 -0
- package/dist/treeview.d.ts +39 -0
- package/dist/types.d.ts +226 -0
- package/dist/utilities.css +16 -9
- package/package.json +12 -9
- package/sass/components/_cards.scss +10 -3
- package/sass/components/_datatable.scss +417 -0
- package/sass/components/_datepicker.scss +57 -0
- package/sass/components/_global.scss +6 -6
- package/sass/components/_image-list.scss +421 -0
- package/sass/components/_masonry.scss +241 -0
- package/sass/components/_timeline.scss +452 -0
- package/sass/components/_treeview.scss +353 -0
- package/sass/components/forms/_forms.scss +1 -1
- package/sass/components/forms/_range-enhanced.scss +406 -0
- package/sass/components/forms/_range.scss +5 -5
- package/sass/components/forms/_select.scss +1 -1
- package/sass/materialize.scss +6 -0
package/dist/index.esm.js
CHANGED
|
@@ -166,9 +166,14 @@ const Mandatory = { view: ({ attrs }) => m('span.mandatory', attrs, '*') };
|
|
|
166
166
|
const Label = () => {
|
|
167
167
|
return {
|
|
168
168
|
view: (_a) => {
|
|
169
|
-
var _b = _a.attrs, { label, id, isMandatory, isActive, className } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className"]);
|
|
169
|
+
var _b = _a.attrs, { label, id, isMandatory, isActive, className, initialValue } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className", "initialValue"]);
|
|
170
170
|
return label
|
|
171
|
-
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id
|
|
171
|
+
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id, oncreate: ({ dom }) => {
|
|
172
|
+
if (!initialValue)
|
|
173
|
+
return;
|
|
174
|
+
const labelEl = dom;
|
|
175
|
+
labelEl.classList.add('active');
|
|
176
|
+
} }), [m.trust(label), isMandatory ? m(Mandatory) : undefined])
|
|
172
177
|
: undefined;
|
|
173
178
|
},
|
|
174
179
|
};
|
|
@@ -411,12 +416,18 @@ const ButtonFactory = (element, defaultClassNames, type = '') => {
|
|
|
411
416
|
return () => {
|
|
412
417
|
return {
|
|
413
418
|
view: ({ attrs }) => {
|
|
414
|
-
const { modalId, tooltip,
|
|
415
|
-
|
|
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]
|
|
416
425
|
.filter(Boolean)
|
|
417
426
|
.join(' ')
|
|
418
427
|
.trim();
|
|
419
|
-
|
|
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 }),
|
|
420
431
|
// `${dca}${modalId ? `.modal-trigger[href=#${modalId}]` : ''}${
|
|
421
432
|
// tooltip ? `.tooltipped[data-position=${tooltipPostion || 'top'}][data-tooltip=${tooltip}]` : ''
|
|
422
433
|
// }${toAttributeString(attr)}`, {}
|
|
@@ -825,6 +836,18 @@ const iconPaths = {
|
|
|
825
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',
|
|
826
837
|
'M0 0h24v24H0z',
|
|
827
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
|
+
],
|
|
828
851
|
};
|
|
829
852
|
const MaterialIcon = () => {
|
|
830
853
|
return {
|
|
@@ -834,8 +857,8 @@ const MaterialIcon = () => {
|
|
|
834
857
|
const rotationMap = {
|
|
835
858
|
down: 0,
|
|
836
859
|
up: 180,
|
|
837
|
-
left:
|
|
838
|
-
right: 90,
|
|
860
|
+
left: 90,
|
|
861
|
+
right: -90,
|
|
839
862
|
};
|
|
840
863
|
const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
|
|
841
864
|
const transform = rotation ? `rotate(${rotation}deg)` : undefined;
|
|
@@ -1144,7 +1167,7 @@ const CodeBlock = () => ({
|
|
|
1144
1167
|
const lang = language || 'lang-TypeScript';
|
|
1145
1168
|
const label = lang.replace('lang-', '');
|
|
1146
1169
|
const cb = code instanceof Array ? code.join('\n') : code;
|
|
1147
|
-
const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim();
|
|
1170
|
+
const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim() || undefined;
|
|
1148
1171
|
return m(`pre.codeblock${newRow ? '.clear' : ''}`, attrs, [
|
|
1149
1172
|
m('div', m('label', label)),
|
|
1150
1173
|
m('code', Object.assign(Object.assign({}, params), { className: cn }), cb),
|
|
@@ -1456,6 +1479,42 @@ const DatePicker = () => {
|
|
|
1456
1479
|
});
|
|
1457
1480
|
return result;
|
|
1458
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
|
+
};
|
|
1459
1518
|
const setDate = (date, preventOnSelect = false, options) => {
|
|
1460
1519
|
if (!date) {
|
|
1461
1520
|
state.date = null;
|
|
@@ -1482,6 +1541,55 @@ const DatePicker = () => {
|
|
|
1482
1541
|
options.onSelect(state.date);
|
|
1483
1542
|
}
|
|
1484
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
|
+
};
|
|
1485
1593
|
const gotoDate = (date) => {
|
|
1486
1594
|
if (!isDate(date)) {
|
|
1487
1595
|
return;
|
|
@@ -1540,6 +1648,21 @@ const DatePicker = () => {
|
|
|
1540
1648
|
arr.push('is-selected');
|
|
1541
1649
|
ariaSelected = 'true';
|
|
1542
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
|
+
}
|
|
1543
1666
|
if (opts.hasEvent) {
|
|
1544
1667
|
arr.push('has-event');
|
|
1545
1668
|
}
|
|
@@ -1560,9 +1683,14 @@ const DatePicker = () => {
|
|
|
1560
1683
|
const month = parseInt(target.getAttribute('data-month') || '0', 10);
|
|
1561
1684
|
const day = parseInt(target.getAttribute('data-day') || '0', 10);
|
|
1562
1685
|
const selectedDate = new Date(year, month, day);
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
+
}
|
|
1566
1694
|
}
|
|
1567
1695
|
}
|
|
1568
1696
|
},
|
|
@@ -1622,16 +1750,37 @@ const DatePicker = () => {
|
|
|
1622
1750
|
(options.maxDate && day > options.maxDate) ||
|
|
1623
1751
|
(options.disableWeekends && isWeekend(day)) ||
|
|
1624
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
|
+
}
|
|
1625
1770
|
const dayConfig = {
|
|
1626
1771
|
day: dayNumber,
|
|
1627
1772
|
month: monthNumber,
|
|
1628
1773
|
year: yearNumber,
|
|
1629
1774
|
hasEvent: false,
|
|
1630
|
-
isSelected: isSelected,
|
|
1775
|
+
isSelected: !options.dateRange && isSelected, // Only use isSelected in single date mode
|
|
1631
1776
|
isToday: isToday,
|
|
1632
1777
|
isDisabled: isDisabled,
|
|
1633
1778
|
isEmpty: isEmpty,
|
|
1634
1779
|
showDaysInNextAndPreviousMonths: false,
|
|
1780
|
+
isRangeStart: isRangeStart,
|
|
1781
|
+
isRangeEnd: isRangeEnd,
|
|
1782
|
+
isInRange: isInRange,
|
|
1783
|
+
isRangePreview: isRangePreview,
|
|
1635
1784
|
};
|
|
1636
1785
|
// Add week number cell at the beginning of each row
|
|
1637
1786
|
if (r === 0 && options.showWeekNumbers) {
|
|
@@ -1678,14 +1827,58 @@ const DatePicker = () => {
|
|
|
1678
1827
|
return {
|
|
1679
1828
|
view: ({ attrs }) => {
|
|
1680
1829
|
const { options } = attrs;
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
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
|
+
}
|
|
1689
1882
|
}
|
|
1690
1883
|
};
|
|
1691
1884
|
};
|
|
@@ -1834,6 +2027,10 @@ const DatePicker = () => {
|
|
|
1834
2027
|
id: uniqueId(),
|
|
1835
2028
|
isOpen: false,
|
|
1836
2029
|
date: null,
|
|
2030
|
+
startDate: null,
|
|
2031
|
+
endDate: null,
|
|
2032
|
+
selectionMode: null,
|
|
2033
|
+
isSelectingRange: false,
|
|
1837
2034
|
calendars: [{ month: 0, year: 0 }],
|
|
1838
2035
|
monthDropdownOpen: false,
|
|
1839
2036
|
yearDropdownOpen: false,
|
|
@@ -1858,17 +2055,35 @@ const DatePicker = () => {
|
|
|
1858
2055
|
yyyy: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getFullYear()) || 0; },
|
|
1859
2056
|
},
|
|
1860
2057
|
};
|
|
1861
|
-
// Initialize date
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
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
|
+
}
|
|
1869
2073
|
}
|
|
1870
2074
|
else {
|
|
1871
|
-
|
|
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
|
+
}
|
|
1872
2087
|
}
|
|
1873
2088
|
// Add document click listener to close dropdowns
|
|
1874
2089
|
document.addEventListener('click', handleDocumentClick);
|
|
@@ -1884,19 +2099,61 @@ const DatePicker = () => {
|
|
|
1884
2099
|
const className = cn1 || cn2 || 'col s12';
|
|
1885
2100
|
// Calculate display value for the input
|
|
1886
2101
|
let displayValue = '';
|
|
1887
|
-
if (
|
|
1888
|
-
|
|
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
|
+
}
|
|
1889
2140
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
// const formatRegex = /(yyyy|mm|dd)/gi;
|
|
1893
|
-
let customDisplayValue = attrs.displayFormat;
|
|
2141
|
+
else {
|
|
2142
|
+
// Single date display (original behavior)
|
|
1894
2143
|
if (state.date) {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
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
|
+
}
|
|
1900
2157
|
}
|
|
1901
2158
|
}
|
|
1902
2159
|
return m('.input-field', {
|
|
@@ -2000,8 +2257,19 @@ const DatePicker = () => {
|
|
|
2000
2257
|
type: 'button',
|
|
2001
2258
|
onclick: () => {
|
|
2002
2259
|
state.isOpen = false;
|
|
2003
|
-
if (
|
|
2004
|
-
|
|
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
|
+
}
|
|
2005
2273
|
}
|
|
2006
2274
|
if (options.onClose)
|
|
2007
2275
|
options.onClose();
|
|
@@ -2030,246 +2298,458 @@ const DatePicker = () => {
|
|
|
2030
2298
|
};
|
|
2031
2299
|
};
|
|
2032
2300
|
|
|
2033
|
-
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
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;
|
|
2051
2471
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2472
|
+
updateSingleValue(state.singleValue, false);
|
|
2473
|
+
if (finalValueDisplay === 'auto') {
|
|
2474
|
+
m.redraw();
|
|
2054
2475
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
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);
|
|
2060
2598
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
case ' ':
|
|
2064
|
-
e.preventDefault();
|
|
2065
|
-
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
2066
|
-
const selectedItem = availableItems[state.focusedIndex];
|
|
2067
|
-
const value = (selectedItem.id || selectedItem.label);
|
|
2068
|
-
state.initialValue = value;
|
|
2069
|
-
state.isOpen = false;
|
|
2070
|
-
state.focusedIndex = -1;
|
|
2071
|
-
if (onchange)
|
|
2072
|
-
onchange(value);
|
|
2599
|
+
else {
|
|
2600
|
+
updateRangeValues(state.rangeMinValue, Math.max(steppedValue, state.rangeMinValue), attrs, state, true);
|
|
2073
2601
|
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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;
|
|
2077
2610
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
},
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
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
|
+
},
|
|
2114
2672
|
}, [
|
|
2115
|
-
m(
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
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'),
|
|
2121
2686
|
onclick: (e) => {
|
|
2122
|
-
e.preventDefault();
|
|
2123
2687
|
e.stopPropagation();
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
2127
|
-
}
|
|
2688
|
+
state.activeThumb = 'min';
|
|
2689
|
+
e.currentTarget.focus();
|
|
2128
2690
|
},
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
state.
|
|
2141
|
-
},
|
|
2142
|
-
style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
|
|
2143
|
-
// Convert dropdown items to format expected by getDropdownStyles
|
|
2144
|
-
group: undefined }))), true),
|
|
2145
|
-
}, items.map((item, index) => {
|
|
2146
|
-
if (item.divider) {
|
|
2147
|
-
return m('li.divider', {
|
|
2148
|
-
key: `divider-${index}`,
|
|
2149
|
-
});
|
|
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);
|
|
2150
2703
|
}
|
|
2151
|
-
const itemIndex = availableItems.indexOf(item);
|
|
2152
|
-
const isFocused = itemIndex === state.focusedIndex;
|
|
2153
|
-
return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
|
|
2154
|
-
item.disabled ? 'disabled' : '',
|
|
2155
|
-
isFocused ? 'focused' : '',
|
|
2156
|
-
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
2157
|
-
]
|
|
2158
|
-
.filter(Boolean)
|
|
2159
|
-
.join(' ') }, (item.disabled
|
|
2160
|
-
? {}
|
|
2161
|
-
: {
|
|
2162
|
-
onclick: (e) => {
|
|
2163
|
-
e.stopPropagation();
|
|
2164
|
-
const value = (item.id || item.label);
|
|
2165
|
-
state.initialValue = value;
|
|
2166
|
-
state.isOpen = false;
|
|
2167
|
-
state.focusedIndex = -1;
|
|
2168
|
-
if (onchange)
|
|
2169
|
-
onchange(value);
|
|
2170
|
-
},
|
|
2171
|
-
})), m('span', {
|
|
2172
|
-
style: {
|
|
2173
|
-
display: 'flex',
|
|
2174
|
-
alignItems: 'center',
|
|
2175
|
-
padding: '14px 16px',
|
|
2176
|
-
},
|
|
2177
|
-
}, [
|
|
2178
|
-
item.iconName
|
|
2179
|
-
? m('i.material-icons', {
|
|
2180
|
-
style: { marginRight: '32px' },
|
|
2181
|
-
}, item.iconName)
|
|
2182
|
-
: undefined,
|
|
2183
|
-
item.label,
|
|
2184
|
-
]));
|
|
2185
|
-
})),
|
|
2186
|
-
m(MaterialIcon, {
|
|
2187
|
-
name: 'caret',
|
|
2188
|
-
direction: 'down',
|
|
2189
|
-
class: 'caret',
|
|
2190
|
-
}),
|
|
2191
|
-
]),
|
|
2192
|
-
]);
|
|
2193
|
-
},
|
|
2194
|
-
};
|
|
2195
|
-
};
|
|
2196
|
-
|
|
2197
|
-
/**
|
|
2198
|
-
* Floating Action Button
|
|
2199
|
-
*/
|
|
2200
|
-
const FloatingActionButton = () => {
|
|
2201
|
-
const state = {
|
|
2202
|
-
isOpen: false,
|
|
2203
|
-
};
|
|
2204
|
-
const handleClickOutside = (e) => {
|
|
2205
|
-
const target = e.target;
|
|
2206
|
-
if (!target.closest('.fixed-action-btn')) {
|
|
2207
|
-
state.isOpen = false;
|
|
2208
|
-
}
|
|
2209
|
-
};
|
|
2210
|
-
return {
|
|
2211
|
-
oncreate: () => {
|
|
2212
|
-
document.addEventListener('click', handleClickOutside);
|
|
2213
|
-
},
|
|
2214
|
-
onremove: () => {
|
|
2215
|
-
document.removeEventListener('click', handleClickOutside);
|
|
2216
|
-
},
|
|
2217
|
-
view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
|
|
2218
|
-
? 'position: absolute; display: inline-block; left: 24px;'
|
|
2219
|
-
: position === 'right' || position === 'inline-right'
|
|
2220
|
-
? 'position: absolute; display: inline-block; right: 24px;'
|
|
2221
|
-
: undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
|
|
2222
|
-
const fabClasses = [
|
|
2223
|
-
'fixed-action-btn',
|
|
2224
|
-
direction ? `direction-${direction}` : '',
|
|
2225
|
-
state.isOpen ? 'active' : '',
|
|
2226
|
-
// hoverEnabled ? 'hover-enabled' : '',
|
|
2227
|
-
]
|
|
2228
|
-
.filter(Boolean)
|
|
2229
|
-
.join(' ');
|
|
2230
|
-
return m('div', {
|
|
2231
|
-
style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
|
|
2232
|
-
}, m(`.${fabClasses}`, {
|
|
2233
|
-
style,
|
|
2234
|
-
onclick: (e) => {
|
|
2235
|
-
e.stopPropagation();
|
|
2236
|
-
if (buttons && buttons.length > 0) {
|
|
2237
|
-
state.isOpen = !state.isOpen;
|
|
2238
|
-
}
|
|
2239
|
-
},
|
|
2240
|
-
onmouseover: hoverEnabled
|
|
2241
|
-
? () => {
|
|
2242
|
-
if (buttons && buttons.length > 0) {
|
|
2243
|
-
state.isOpen = true;
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
: undefined,
|
|
2247
|
-
onmouseleave: hoverEnabled
|
|
2248
|
-
? () => {
|
|
2249
|
-
state.isOpen = false;
|
|
2250
|
-
}
|
|
2251
|
-
: undefined,
|
|
2252
|
-
}, [
|
|
2253
|
-
m('a.btn-floating.btn-large', {
|
|
2254
|
-
className,
|
|
2255
|
-
}, m('i.material-icons', { className: iconClass }, iconName)),
|
|
2256
|
-
buttons &&
|
|
2257
|
-
buttons.length > 0 &&
|
|
2258
|
-
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
|
|
2259
|
-
style: {
|
|
2260
|
-
opacity: state.isOpen ? '1' : '0',
|
|
2261
|
-
transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
|
|
2262
|
-
transition: `all 0.3s ease ${index * 40}ms`,
|
|
2263
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'),
|
|
2264
2722
|
onclick: (e) => {
|
|
2265
2723
|
e.stopPropagation();
|
|
2266
|
-
|
|
2267
|
-
|
|
2724
|
+
state.activeThumb = 'max';
|
|
2725
|
+
e.currentTarget.focus();
|
|
2268
2726
|
},
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
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
|
+
},
|
|
2273
2753
|
};
|
|
2274
2754
|
|
|
2275
2755
|
/** Character counter component that tracks text length against maxLength */
|
|
@@ -2382,6 +2862,7 @@ const TextArea = () => {
|
|
|
2382
2862
|
id,
|
|
2383
2863
|
isMandatory,
|
|
2384
2864
|
isActive: ((_a = state.textarea) === null || _a === void 0 ? void 0 : _a.value) || placeholder || state.active,
|
|
2865
|
+
initialValue: initialValue !== undefined,
|
|
2385
2866
|
}),
|
|
2386
2867
|
m(HelperText, {
|
|
2387
2868
|
helperText,
|
|
@@ -2408,6 +2889,12 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2408
2889
|
isValid: true,
|
|
2409
2890
|
active: false,
|
|
2410
2891
|
inputElement: null,
|
|
2892
|
+
// Range-specific state
|
|
2893
|
+
rangeMinValue: undefined,
|
|
2894
|
+
rangeMaxValue: undefined,
|
|
2895
|
+
singleValue: undefined,
|
|
2896
|
+
isDragging: false,
|
|
2897
|
+
activeThumb: null,
|
|
2411
2898
|
};
|
|
2412
2899
|
// let labelManager: { updateLabelState: () => void; cleanup: () => void } | null = null;
|
|
2413
2900
|
// let lengthUpdateHandler: (() => void) | null = null;
|
|
@@ -2433,15 +2920,39 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2433
2920
|
state.hasInteracted = length > 0;
|
|
2434
2921
|
}
|
|
2435
2922
|
};
|
|
2923
|
+
// Range slider helper functions
|
|
2924
|
+
// Range slider rendering functions are now in separate module
|
|
2436
2925
|
return {
|
|
2437
2926
|
view: ({ attrs }) => {
|
|
2438
2927
|
var _a;
|
|
2439
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"]);
|
|
2440
2929
|
// const attributes = toAttrs(params);
|
|
2441
|
-
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim();
|
|
2930
|
+
const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
|
|
2931
|
+
const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
|
|
2932
|
+
? true
|
|
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
|
+
}
|
|
2442
2945
|
return m('.input-field', { className: cn, style }, [
|
|
2443
2946
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2444
|
-
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,
|
|
2445
2956
|
// attributes,
|
|
2446
2957
|
oncreate: ({ dom }) => {
|
|
2447
2958
|
const input = (state.inputElement = dom);
|
|
@@ -2457,7 +2968,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2457
2968
|
state.currentLength = input.value.length; // Initial count
|
|
2458
2969
|
}
|
|
2459
2970
|
// Range input functionality
|
|
2460
|
-
if (type === 'range') {
|
|
2971
|
+
if (type === 'range' && !attrs.minmax) {
|
|
2461
2972
|
const updateThumb = () => {
|
|
2462
2973
|
const value = input.value;
|
|
2463
2974
|
const min = input.min || '0';
|
|
@@ -2558,14 +3069,8 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
2558
3069
|
label,
|
|
2559
3070
|
id,
|
|
2560
3071
|
isMandatory,
|
|
2561
|
-
isActive
|
|
2562
|
-
|
|
2563
|
-
placeholder ||
|
|
2564
|
-
type === 'number' ||
|
|
2565
|
-
type === 'color' ||
|
|
2566
|
-
type === 'range'
|
|
2567
|
-
? true
|
|
2568
|
-
: false,
|
|
3072
|
+
isActive,
|
|
3073
|
+
initialValue: initialValue !== undefined,
|
|
2569
3074
|
}),
|
|
2570
3075
|
m(HelperText, {
|
|
2571
3076
|
helperText,
|
|
@@ -2660,81 +3165,1008 @@ const FileInput = () => {
|
|
|
2660
3165
|
};
|
|
2661
3166
|
};
|
|
2662
3167
|
|
|
2663
|
-
/**
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
const
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
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
|
+
},
|
|
2673
3189
|
};
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
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%);
|
|
2738
4170
|
color: white;
|
|
2739
4171
|
font-size: 16px;
|
|
2740
4172
|
text-align: center;
|
|
@@ -2834,7 +4266,7 @@ const MaterialBox = () => {
|
|
|
2834
4266
|
view: ({ attrs }) => {
|
|
2835
4267
|
const { src, alt, width, height, caption, className, style } = attrs, otherAttrs = __rest(attrs, ["src", "alt", "width", "height", "caption", "className", "style"]);
|
|
2836
4268
|
return m('img.materialboxed', Object.assign(Object.assign({}, otherAttrs), { src, alt: alt || '', width,
|
|
2837
|
-
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) => {
|
|
2838
4270
|
e.preventDefault();
|
|
2839
4271
|
openBox(e.target, attrs);
|
|
2840
4272
|
} }));
|
|
@@ -2916,7 +4348,7 @@ const ModalPanel = () => {
|
|
|
2916
4348
|
.filter(Boolean)
|
|
2917
4349
|
.join(' ')
|
|
2918
4350
|
.trim();
|
|
2919
|
-
const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim();
|
|
4351
|
+
const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined;
|
|
2920
4352
|
return m('div', { className: 'modal-container' }, [
|
|
2921
4353
|
// Modal overlay
|
|
2922
4354
|
m('div', {
|
|
@@ -2941,21 +4373,25 @@ const ModalPanel = () => {
|
|
|
2941
4373
|
role: 'dialog',
|
|
2942
4374
|
'aria-labelledby': `${id}-title`,
|
|
2943
4375
|
'aria-describedby': description ? `${id}-desc` : undefined,
|
|
2944
|
-
style: {
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
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)' }),
|
|
2959
4395
|
onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
2960
4396
|
}, [
|
|
2961
4397
|
// Close button
|
|
@@ -2970,135 +4406,37 @@ const ModalPanel = () => {
|
|
|
2970
4406
|
minWidth: 'auto',
|
|
2971
4407
|
lineHeight: 1,
|
|
2972
4408
|
},
|
|
2973
|
-
onclick: () => closeModal(attrs),
|
|
2974
|
-
'aria-label': 'Close modal',
|
|
2975
|
-
}, '×'),
|
|
2976
|
-
// Modal content
|
|
2977
|
-
m('.modal-content', {
|
|
2978
|
-
style: {
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
style: {
|
|
2989
|
-
padding: '4px 6px',
|
|
2990
|
-
borderTop: '1px solid rgba(160,160,160,0.2)',
|
|
2991
|
-
textAlign: 'right',
|
|
2992
|
-
},
|
|
2993
|
-
}, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
|
|
2994
|
-
if (buttonProps.onclick)
|
|
2995
|
-
buttonProps.onclick(e);
|
|
2996
|
-
closeModal(attrs);
|
|
2997
|
-
} })))),
|
|
2998
|
-
]),
|
|
2999
|
-
]);
|
|
3000
|
-
},
|
|
3001
|
-
};
|
|
3002
|
-
};
|
|
3003
|
-
|
|
3004
|
-
/** Component to show a check box */
|
|
3005
|
-
const InputCheckbox = () => {
|
|
3006
|
-
return {
|
|
3007
|
-
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3008
|
-
const checkboxId = inputId || uniqueId();
|
|
3009
|
-
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3010
|
-
m('input[type=checkbox][tabindex=0]', {
|
|
3011
|
-
id: checkboxId,
|
|
3012
|
-
checked,
|
|
3013
|
-
disabled,
|
|
3014
|
-
onclick: onchange
|
|
3015
|
-
? (e) => {
|
|
3016
|
-
if (e.target && typeof e.target.checked !== 'undefined') {
|
|
3017
|
-
onchange(e.target.checked);
|
|
3018
|
-
}
|
|
3019
|
-
}
|
|
3020
|
-
: undefined,
|
|
3021
|
-
}),
|
|
3022
|
-
label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
|
|
3023
|
-
]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
|
|
3024
|
-
},
|
|
3025
|
-
};
|
|
3026
|
-
};
|
|
3027
|
-
/** A list of checkboxes */
|
|
3028
|
-
const Options = () => {
|
|
3029
|
-
const state = {};
|
|
3030
|
-
const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
|
|
3031
|
-
const selectAll = (options, callback) => {
|
|
3032
|
-
const allIds = options.map((option) => option.id);
|
|
3033
|
-
state.checkedIds = [...allIds];
|
|
3034
|
-
if (callback)
|
|
3035
|
-
callback(allIds);
|
|
3036
|
-
};
|
|
3037
|
-
const selectNone = (callback) => {
|
|
3038
|
-
state.checkedIds = [];
|
|
3039
|
-
if (callback)
|
|
3040
|
-
callback([]);
|
|
3041
|
-
};
|
|
3042
|
-
return {
|
|
3043
|
-
oninit: ({ attrs: { initialValue, checkedId, id } }) => {
|
|
3044
|
-
const iv = checkedId || initialValue;
|
|
3045
|
-
state.checkedId = checkedId;
|
|
3046
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3047
|
-
state.componentId = id || uniqueId();
|
|
3048
|
-
},
|
|
3049
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
|
|
3050
|
-
const onchange = callback
|
|
3051
|
-
? (propId, checked) => {
|
|
3052
|
-
const checkedIds = state.checkedIds.filter((i) => i !== propId);
|
|
3053
|
-
if (checked) {
|
|
3054
|
-
checkedIds.push(propId);
|
|
3055
|
-
}
|
|
3056
|
-
state.checkedIds = checkedIds;
|
|
3057
|
-
callback(checkedIds);
|
|
3058
|
-
}
|
|
3059
|
-
: undefined;
|
|
3060
|
-
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
3061
|
-
const optionsContent = layout === 'horizontal'
|
|
3062
|
-
? m('div.grid-container', options.map((option) => m(InputCheckbox, {
|
|
3063
|
-
disabled: disabled || option.disabled,
|
|
3064
|
-
label: option.label,
|
|
3065
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3066
|
-
className: option.className || checkboxClass,
|
|
3067
|
-
checked: isChecked(option.id),
|
|
3068
|
-
description: option.description,
|
|
3069
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3070
|
-
})))
|
|
3071
|
-
: options.map((option) => m(InputCheckbox, {
|
|
3072
|
-
disabled: disabled || option.disabled,
|
|
3073
|
-
label: option.label,
|
|
3074
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3075
|
-
className: option.className || checkboxClass,
|
|
3076
|
-
checked: isChecked(option.id),
|
|
3077
|
-
description: option.description,
|
|
3078
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3079
|
-
}));
|
|
3080
|
-
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3081
|
-
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3082
|
-
showSelectAll &&
|
|
3083
|
-
m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
|
|
3084
|
-
m('a', {
|
|
3085
|
-
href: '#',
|
|
3086
|
-
onclick: (e) => {
|
|
3087
|
-
e.preventDefault();
|
|
3088
|
-
selectAll(options, callback);
|
|
3089
|
-
},
|
|
3090
|
-
style: 'margin-right: 15px;',
|
|
3091
|
-
}, 'Select All'),
|
|
3092
|
-
m('a', {
|
|
3093
|
-
href: '#',
|
|
3094
|
-
onclick: (e) => {
|
|
3095
|
-
e.preventDefault();
|
|
3096
|
-
selectNone(callback);
|
|
3097
|
-
},
|
|
3098
|
-
}, 'Select None'),
|
|
4409
|
+
onclick: () => closeModal(attrs),
|
|
4410
|
+
'aria-label': 'Close modal',
|
|
4411
|
+
}, '×'),
|
|
4412
|
+
// Modal content
|
|
4413
|
+
m('.modal-content', {
|
|
4414
|
+
style: {
|
|
4415
|
+
padding: '24px',
|
|
4416
|
+
paddingTop: showCloseButton ? '48px' : '24px',
|
|
4417
|
+
minHeight: 'auto',
|
|
4418
|
+
flex: '1 1 auto',
|
|
4419
|
+
},
|
|
4420
|
+
}, [
|
|
4421
|
+
m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
|
|
4422
|
+
description &&
|
|
4423
|
+
m('div', Object.assign({ id: `${id}-desc` }, (richContent && typeof description === 'string' ? { innerHTML: description } : {})), richContent && typeof description === 'string' ? undefined : description),
|
|
3099
4424
|
]),
|
|
3100
|
-
|
|
3101
|
-
|
|
4425
|
+
// Modal footer with buttons
|
|
4426
|
+
buttons &&
|
|
4427
|
+
buttons.length > 0 &&
|
|
4428
|
+
m('.modal-footer', {
|
|
4429
|
+
style: {
|
|
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
|
+
]),
|
|
3102
4440
|
]);
|
|
3103
4441
|
},
|
|
3104
4442
|
};
|
|
@@ -3368,12 +4706,15 @@ const TimePicker = () => {
|
|
|
3368
4706
|
};
|
|
3369
4707
|
const updateTimeFromInput = (inputValue) => {
|
|
3370
4708
|
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
4709
|
+
let amPmWasProvided = false;
|
|
3371
4710
|
if (options.twelveHour && value.length > 1) {
|
|
3372
4711
|
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
3373
4712
|
state.amOrPm = 'AM';
|
|
4713
|
+
amPmWasProvided = true;
|
|
3374
4714
|
}
|
|
3375
4715
|
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
3376
4716
|
state.amOrPm = 'PM';
|
|
4717
|
+
amPmWasProvided = true;
|
|
3377
4718
|
}
|
|
3378
4719
|
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
3379
4720
|
}
|
|
@@ -3382,21 +4723,33 @@ const TimePicker = () => {
|
|
|
3382
4723
|
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
3383
4724
|
if (options.twelveHour) {
|
|
3384
4725
|
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
4726
|
+
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
3385
4727
|
}
|
|
3386
4728
|
}
|
|
3387
4729
|
let hours = +value[0] || 0;
|
|
3388
4730
|
let minutes = +value[1] || 0;
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
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
|
+
}
|
|
3394
4746
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
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
|
+
}
|
|
3400
4753
|
}
|
|
3401
4754
|
}
|
|
3402
4755
|
state.hours = hours;
|
|
@@ -4076,7 +5429,7 @@ const RadioButtons = () => {
|
|
|
4076
5429
|
callback(propId);
|
|
4077
5430
|
}
|
|
4078
5431
|
};
|
|
4079
|
-
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
5432
|
+
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
4080
5433
|
const optionsContent = layout === 'horizontal'
|
|
4081
5434
|
? m('div.grid-container', options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
|
|
4082
5435
|
groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` }))))
|
|
@@ -4349,7 +5702,7 @@ const Switch = () => {
|
|
|
4349
5702
|
view: ({ attrs }) => {
|
|
4350
5703
|
const id = attrs.id || state.id;
|
|
4351
5704
|
const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
|
|
4352
|
-
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
5705
|
+
const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
4353
5706
|
return m('div', {
|
|
4354
5707
|
className: cn,
|
|
4355
5708
|
onclick: (e) => {
|
|
@@ -4500,7 +5853,7 @@ const Tabs = () => {
|
|
|
4500
5853
|
},
|
|
4501
5854
|
view: ({ attrs }) => {
|
|
4502
5855
|
const { tabWidth, tabs, className, style, swipeable = false } = attrs;
|
|
4503
|
-
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;
|
|
4504
5857
|
const anchoredTabs = tabs.map(toAnchored());
|
|
4505
5858
|
const activeTab = setActiveTabId(anchoredTabs, attrs.selectedTabId);
|
|
4506
5859
|
updateIndicator();
|
|
@@ -5486,8 +6839,8 @@ const FileUpload = () => {
|
|
|
5486
6839
|
}
|
|
5487
6840
|
// Check file type
|
|
5488
6841
|
if (attrs.accept) {
|
|
5489
|
-
const acceptedTypes = attrs.accept.split(',').map(type => type.trim());
|
|
5490
|
-
const isAccepted = acceptedTypes.some(acceptedType => {
|
|
6842
|
+
const acceptedTypes = attrs.accept.split(',').map((type) => type.trim());
|
|
6843
|
+
const isAccepted = acceptedTypes.some((acceptedType) => {
|
|
5491
6844
|
if (acceptedType.startsWith('.')) {
|
|
5492
6845
|
// Extension check
|
|
5493
6846
|
return file.name.toLowerCase().endsWith(acceptedType.toLowerCase());
|
|
@@ -5547,11 +6900,11 @@ const FileUpload = () => {
|
|
|
5547
6900
|
}
|
|
5548
6901
|
// Notify parent component
|
|
5549
6902
|
if (attrs.onFilesSelected) {
|
|
5550
|
-
attrs.onFilesSelected(state.files.filter(f => !f.uploadError));
|
|
6903
|
+
attrs.onFilesSelected(state.files.filter((f) => !f.uploadError));
|
|
5551
6904
|
}
|
|
5552
6905
|
};
|
|
5553
6906
|
const removeFile = (fileToRemove, attrs) => {
|
|
5554
|
-
state.files = state.files.filter(file => file !== fileToRemove);
|
|
6907
|
+
state.files = state.files.filter((file) => file !== fileToRemove);
|
|
5555
6908
|
if (attrs.onFileRemoved) {
|
|
5556
6909
|
attrs.onFileRemoved(fileToRemove);
|
|
5557
6910
|
}
|
|
@@ -5570,11 +6923,11 @@ const FileUpload = () => {
|
|
|
5570
6923
|
id: uniqueId(),
|
|
5571
6924
|
files: [],
|
|
5572
6925
|
isDragOver: false,
|
|
5573
|
-
isUploading: false
|
|
6926
|
+
isUploading: false,
|
|
5574
6927
|
};
|
|
5575
6928
|
},
|
|
5576
6929
|
view: ({ attrs }) => {
|
|
5577
|
-
const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error } = attrs;
|
|
6930
|
+
const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error, } = attrs;
|
|
5578
6931
|
return m('.file-upload-container', { class: className }, [
|
|
5579
6932
|
// Upload area
|
|
5580
6933
|
m('.file-upload-area', {
|
|
@@ -5582,8 +6935,10 @@ const FileUpload = () => {
|
|
|
5582
6935
|
state.isDragOver ? 'drag-over' : '',
|
|
5583
6936
|
disabled ? 'disabled' : '',
|
|
5584
6937
|
error ? 'error' : '',
|
|
5585
|
-
state.files.length > 0 ? 'has-files' : ''
|
|
5586
|
-
]
|
|
6938
|
+
state.files.length > 0 ? 'has-files' : '',
|
|
6939
|
+
]
|
|
6940
|
+
.filter(Boolean)
|
|
6941
|
+
.join(' ') || undefined,
|
|
5587
6942
|
ondragover: (e) => {
|
|
5588
6943
|
if (disabled)
|
|
5589
6944
|
return;
|
|
@@ -5614,7 +6969,7 @@ const FileUpload = () => {
|
|
|
5614
6969
|
return;
|
|
5615
6970
|
const input = document.getElementById(state.id);
|
|
5616
6971
|
input === null || input === void 0 ? void 0 : input.click();
|
|
5617
|
-
}
|
|
6972
|
+
},
|
|
5618
6973
|
}, [
|
|
5619
6974
|
m('input[type="file"]', {
|
|
5620
6975
|
id: state.id,
|
|
@@ -5627,57 +6982,55 @@ const FileUpload = () => {
|
|
|
5627
6982
|
if (target.files) {
|
|
5628
6983
|
handleFiles(target.files, attrs);
|
|
5629
6984
|
}
|
|
5630
|
-
}
|
|
6985
|
+
},
|
|
5631
6986
|
}),
|
|
5632
6987
|
m('.file-upload-content', [
|
|
5633
6988
|
m('i.material-icons.file-upload-icon', 'cloud_upload'),
|
|
5634
6989
|
m('p.file-upload-label', label),
|
|
5635
6990
|
helperText && m('p.file-upload-helper', helperText),
|
|
5636
|
-
accept && m('p.file-upload-types', `Accepted: ${accept}`)
|
|
5637
|
-
])
|
|
6991
|
+
accept && m('p.file-upload-types', `Accepted: ${accept}`),
|
|
6992
|
+
]),
|
|
5638
6993
|
]),
|
|
5639
6994
|
// Error message
|
|
5640
6995
|
error && m('.file-upload-error', error),
|
|
5641
6996
|
// File list
|
|
5642
|
-
state.files.length > 0 &&
|
|
5643
|
-
m('
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
m('img', { src: file.preview, alt: file.name })
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
6997
|
+
state.files.length > 0 &&
|
|
6998
|
+
m('.file-upload-list', [
|
|
6999
|
+
m('h6', 'Selected Files:'),
|
|
7000
|
+
state.files.map((file) => m('.file-upload-item', { key: file.name + file.size }, [
|
|
7001
|
+
// Preview thumbnail
|
|
7002
|
+
showPreview && file.preview && m('.file-preview', [m('img', { src: file.preview, alt: file.name })]),
|
|
7003
|
+
// File info
|
|
7004
|
+
m('.file-info', [
|
|
7005
|
+
m('.file-name', file.name),
|
|
7006
|
+
m('.file-details', [
|
|
7007
|
+
m('span.file-size', formatFileSize(file.size)),
|
|
7008
|
+
file.type && m('span.file-type', file.type),
|
|
7009
|
+
]),
|
|
7010
|
+
// Progress bar (if uploading)
|
|
7011
|
+
file.uploadProgress !== undefined &&
|
|
7012
|
+
m('.file-progress', [
|
|
7013
|
+
m('.progress', [
|
|
7014
|
+
m('.determinate', {
|
|
7015
|
+
style: { width: `${file.uploadProgress}%` },
|
|
7016
|
+
}),
|
|
7017
|
+
]),
|
|
7018
|
+
]),
|
|
7019
|
+
// Error message
|
|
7020
|
+
file.uploadError && m('.file-error', file.uploadError),
|
|
5663
7021
|
]),
|
|
5664
|
-
//
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
}, [
|
|
5675
|
-
m('i.material-icons', 'close')
|
|
5676
|
-
])
|
|
5677
|
-
]))
|
|
5678
|
-
])
|
|
7022
|
+
// Remove button
|
|
7023
|
+
m('button.btn-flat.file-remove', {
|
|
7024
|
+
onclick: (e) => {
|
|
7025
|
+
e.stopPropagation();
|
|
7026
|
+
removeFile(file, attrs);
|
|
7027
|
+
},
|
|
7028
|
+
title: 'Remove file',
|
|
7029
|
+
}, [m('i.material-icons', 'close')]),
|
|
7030
|
+
])),
|
|
7031
|
+
]),
|
|
5679
7032
|
]);
|
|
5680
|
-
}
|
|
7033
|
+
},
|
|
5681
7034
|
};
|
|
5682
7035
|
};
|
|
5683
7036
|
|
|
@@ -5707,7 +7060,7 @@ const Sidenav = () => {
|
|
|
5707
7060
|
state = {
|
|
5708
7061
|
id: attrs.id || uniqueId(),
|
|
5709
7062
|
isOpen: attrs.isOpen || false,
|
|
5710
|
-
isAnimating: false
|
|
7063
|
+
isAnimating: false,
|
|
5711
7064
|
};
|
|
5712
7065
|
// Set up keyboard listener
|
|
5713
7066
|
if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
|
|
@@ -5736,34 +7089,33 @@ const Sidenav = () => {
|
|
|
5736
7089
|
}
|
|
5737
7090
|
},
|
|
5738
7091
|
view: ({ attrs, children }) => {
|
|
5739
|
-
const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false } = attrs;
|
|
7092
|
+
const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, } = attrs;
|
|
5740
7093
|
const isOpen = state.isOpen;
|
|
5741
7094
|
return [
|
|
5742
7095
|
// Backdrop (using existing materialize class)
|
|
5743
|
-
showBackdrop &&
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
7096
|
+
showBackdrop &&
|
|
7097
|
+
mode === 'overlay' &&
|
|
7098
|
+
m('.sidenav-overlay', {
|
|
7099
|
+
style: {
|
|
7100
|
+
display: isOpen ? 'block' : 'none',
|
|
7101
|
+
opacity: isOpen ? '1' : '0',
|
|
7102
|
+
},
|
|
7103
|
+
onclick: () => handleBackdropClick(attrs),
|
|
7104
|
+
}),
|
|
5750
7105
|
// Sidenav (using existing materialize structure)
|
|
5751
7106
|
m('ul.sidenav', {
|
|
5752
7107
|
id: state.id,
|
|
5753
|
-
class: [
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
className
|
|
5757
|
-
].filter(Boolean).join(' '),
|
|
7108
|
+
class: [position === 'right' ? 'right-aligned' : '', fixed ? 'sidenav-fixed' : '', className]
|
|
7109
|
+
.filter(Boolean)
|
|
7110
|
+
.join(' ') || undefined,
|
|
5758
7111
|
style: {
|
|
5759
7112
|
width: `${width}px`,
|
|
5760
|
-
transform: isOpen ? 'translateX(0)' :
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
}, children)
|
|
7113
|
+
transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
|
|
7114
|
+
'transition-duration': `${animationDuration}ms`,
|
|
7115
|
+
},
|
|
7116
|
+
}, children),
|
|
5765
7117
|
];
|
|
5766
|
-
}
|
|
7118
|
+
},
|
|
5767
7119
|
};
|
|
5768
7120
|
};
|
|
5769
7121
|
/**
|
|
@@ -5773,37 +7125,30 @@ const Sidenav = () => {
|
|
|
5773
7125
|
const SidenavItem = () => {
|
|
5774
7126
|
return {
|
|
5775
7127
|
view: ({ attrs, children }) => {
|
|
5776
|
-
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false } = attrs;
|
|
7128
|
+
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, } = attrs;
|
|
5777
7129
|
if (divider) {
|
|
5778
7130
|
return m('li.divider');
|
|
5779
7131
|
}
|
|
5780
7132
|
if (subheader) {
|
|
5781
7133
|
return m('li.subheader', text || children);
|
|
5782
7134
|
}
|
|
5783
|
-
const itemClasses = [
|
|
5784
|
-
|
|
5785
|
-
disabled ? 'disabled' : '',
|
|
5786
|
-
className
|
|
5787
|
-
].filter(Boolean).join(' ');
|
|
5788
|
-
const content = [
|
|
5789
|
-
icon && m('i.material-icons', icon),
|
|
5790
|
-
text || children
|
|
5791
|
-
];
|
|
7135
|
+
const itemClasses = [active ? 'active' : '', disabled ? 'disabled' : '', className].filter(Boolean).join(' ') || undefined;
|
|
7136
|
+
const content = [icon && m('i.material-icons', icon), text || children];
|
|
5792
7137
|
if (href && !disabled) {
|
|
5793
7138
|
return m('li', { class: itemClasses }, [
|
|
5794
7139
|
m('a', {
|
|
5795
7140
|
href,
|
|
5796
|
-
onclick: disabled ? undefined : onclick
|
|
5797
|
-
}, content)
|
|
7141
|
+
onclick: disabled ? undefined : onclick,
|
|
7142
|
+
}, content),
|
|
5798
7143
|
]);
|
|
5799
7144
|
}
|
|
5800
7145
|
return m('li', { class: itemClasses }, [
|
|
5801
7146
|
m('a', {
|
|
5802
7147
|
onclick: disabled ? undefined : onclick,
|
|
5803
|
-
href: '#!'
|
|
5804
|
-
}, content)
|
|
7148
|
+
href: '#!',
|
|
7149
|
+
}, content),
|
|
5805
7150
|
]);
|
|
5806
|
-
}
|
|
7151
|
+
},
|
|
5807
7152
|
};
|
|
5808
7153
|
};
|
|
5809
7154
|
/**
|
|
@@ -5854,7 +7199,7 @@ class SidenavManager {
|
|
|
5854
7199
|
const Breadcrumb = () => {
|
|
5855
7200
|
return {
|
|
5856
7201
|
view: ({ attrs }) => {
|
|
5857
|
-
const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false } = attrs;
|
|
7202
|
+
const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false, } = attrs;
|
|
5858
7203
|
if (items.length === 0) {
|
|
5859
7204
|
return null;
|
|
5860
7205
|
}
|
|
@@ -5863,52 +7208,46 @@ const Breadcrumb = () => {
|
|
|
5863
7208
|
if (maxItems && items.length > maxItems) {
|
|
5864
7209
|
const firstItem = items[0];
|
|
5865
7210
|
const lastItems = items.slice(-(maxItems - 2));
|
|
5866
|
-
displayItems = [
|
|
5867
|
-
firstItem,
|
|
5868
|
-
{ text: '...', disabled: true, className: 'breadcrumb-ellipsis' },
|
|
5869
|
-
...lastItems
|
|
5870
|
-
];
|
|
7211
|
+
displayItems = [firstItem, { text: '...', disabled: true, className: 'breadcrumb-ellipsis' }, ...lastItems];
|
|
5871
7212
|
}
|
|
5872
7213
|
return m('nav.breadcrumb', { class: className }, [
|
|
5873
|
-
m('ol.breadcrumb-list', displayItems
|
|
7214
|
+
m('ol.breadcrumb-list', displayItems
|
|
7215
|
+
.map((item, index) => {
|
|
5874
7216
|
const isLast = index === displayItems.length - 1;
|
|
5875
7217
|
const isFirst = index === 0;
|
|
5876
7218
|
return [
|
|
5877
7219
|
// Breadcrumb item
|
|
5878
7220
|
m('li.breadcrumb-item', {
|
|
5879
|
-
class: [
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
item.className || ''
|
|
5883
|
-
].filter(Boolean).join(' ')
|
|
7221
|
+
class: [item.active || isLast ? 'active' : '', item.disabled ? 'disabled' : '', item.className || '']
|
|
7222
|
+
.filter(Boolean)
|
|
7223
|
+
.join(' ') || undefined,
|
|
5884
7224
|
}, [
|
|
5885
|
-
item.href && !item.disabled && !isLast
|
|
5886
|
-
// Link item
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
// Text item (active or disabled)
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
7225
|
+
item.href && !item.disabled && !isLast
|
|
7226
|
+
? // Link item
|
|
7227
|
+
m('a.breadcrumb-link', {
|
|
7228
|
+
href: item.href,
|
|
7229
|
+
onclick: item.onclick,
|
|
7230
|
+
}, [
|
|
7231
|
+
showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
7232
|
+
showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
7233
|
+
m('span.breadcrumb-text', item.text),
|
|
7234
|
+
])
|
|
7235
|
+
: // Text item (active or disabled)
|
|
7236
|
+
m('span.breadcrumb-text', {
|
|
7237
|
+
onclick: item.disabled ? undefined : item.onclick,
|
|
7238
|
+
}, [
|
|
7239
|
+
showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
7240
|
+
showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
7241
|
+
item.text,
|
|
7242
|
+
]),
|
|
5903
7243
|
]),
|
|
5904
7244
|
// Separator (except for last item)
|
|
5905
|
-
!isLast && m('li.breadcrumb-separator', [
|
|
5906
|
-
m('i.material-icons', separator)
|
|
5907
|
-
])
|
|
7245
|
+
!isLast && m('li.breadcrumb-separator', [m('i.material-icons', separator)]),
|
|
5908
7246
|
];
|
|
5909
|
-
})
|
|
7247
|
+
})
|
|
7248
|
+
.reduce((acc, val) => acc.concat(val), [])),
|
|
5910
7249
|
]);
|
|
5911
|
-
}
|
|
7250
|
+
},
|
|
5912
7251
|
};
|
|
5913
7252
|
};
|
|
5914
7253
|
/**
|
|
@@ -5921,7 +7260,7 @@ const createBreadcrumb = (path, basePath = '/') => {
|
|
|
5921
7260
|
items.push({
|
|
5922
7261
|
text: 'Home',
|
|
5923
7262
|
href: basePath,
|
|
5924
|
-
icon: 'home'
|
|
7263
|
+
icon: 'home',
|
|
5925
7264
|
});
|
|
5926
7265
|
// Add path segments
|
|
5927
7266
|
let currentPath = basePath;
|
|
@@ -5931,7 +7270,7 @@ const createBreadcrumb = (path, basePath = '/') => {
|
|
|
5931
7270
|
items.push({
|
|
5932
7271
|
text: segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' '),
|
|
5933
7272
|
href: isLast ? undefined : currentPath,
|
|
5934
|
-
active: isLast
|
|
7273
|
+
active: isLast,
|
|
5935
7274
|
});
|
|
5936
7275
|
});
|
|
5937
7276
|
return items;
|
|
@@ -5950,19 +7289,18 @@ class BreadcrumbManager {
|
|
|
5950
7289
|
items.push({
|
|
5951
7290
|
text: 'Home',
|
|
5952
7291
|
href: '/',
|
|
5953
|
-
icon: 'home'
|
|
7292
|
+
icon: 'home',
|
|
5954
7293
|
});
|
|
5955
7294
|
let currentPath = '';
|
|
5956
7295
|
segments.forEach((segment, index) => {
|
|
5957
7296
|
currentPath += '/' + segment;
|
|
5958
7297
|
const isLast = index === segments.length - 1;
|
|
5959
7298
|
// Use custom text from config or format segment
|
|
5960
|
-
const text = routeConfig[currentPath] ||
|
|
5961
|
-
segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
|
|
7299
|
+
const text = routeConfig[currentPath] || segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
|
|
5962
7300
|
items.push({
|
|
5963
7301
|
text,
|
|
5964
7302
|
href: isLast ? undefined : currentPath,
|
|
5965
|
-
active: isLast
|
|
7303
|
+
active: isLast,
|
|
5966
7304
|
});
|
|
5967
7305
|
});
|
|
5968
7306
|
return items;
|
|
@@ -5974,7 +7312,7 @@ class BreadcrumbManager {
|
|
|
5974
7312
|
return hierarchy.map((item, index) => ({
|
|
5975
7313
|
text: item[textKey],
|
|
5976
7314
|
href: index === hierarchy.length - 1 ? undefined : item[pathKey],
|
|
5977
|
-
active: index === hierarchy.length - 1
|
|
7315
|
+
active: index === hierarchy.length - 1,
|
|
5978
7316
|
}));
|
|
5979
7317
|
}
|
|
5980
7318
|
}
|
|
@@ -6108,7 +7446,7 @@ const Wizard = () => {
|
|
|
6108
7446
|
hasError ? 'error' : '',
|
|
6109
7447
|
step.disabled ? 'disabled' : '',
|
|
6110
7448
|
step.optional ? 'optional' : ''
|
|
6111
|
-
].filter(Boolean).join(' '),
|
|
7449
|
+
].filter(Boolean).join(' ') || undefined,
|
|
6112
7450
|
onclick: allowHeaderNavigation && !step.disabled ?
|
|
6113
7451
|
() => goToStep(index, attrs) : undefined
|
|
6114
7452
|
}, [
|
|
@@ -6182,4 +7520,727 @@ const Stepper = () => {
|
|
|
6182
7520
|
};
|
|
6183
7521
|
};
|
|
6184
7522
|
|
|
6185
|
-
|
|
7523
|
+
// Utility function to check if a node is the last in its branch
|
|
7524
|
+
const isNodeLastInBranch = (nodePath, rootNodes) => {
|
|
7525
|
+
// Navigate to the node's position and check if it's the last child at every level
|
|
7526
|
+
let currentNodes = rootNodes;
|
|
7527
|
+
for (let i = 0; i < nodePath.length; i++) {
|
|
7528
|
+
const index = nodePath[i];
|
|
7529
|
+
const isLastAtThisLevel = index === currentNodes.length - 1;
|
|
7530
|
+
// If this is not the last child at this level, then this node is not last in branch
|
|
7531
|
+
if (!isLastAtThisLevel) {
|
|
7532
|
+
return false;
|
|
7533
|
+
}
|
|
7534
|
+
// Move to the next level if it exists
|
|
7535
|
+
if (i < nodePath.length - 1) {
|
|
7536
|
+
const currentNode = currentNodes[index];
|
|
7537
|
+
if (currentNode.children) {
|
|
7538
|
+
currentNodes = currentNode.children;
|
|
7539
|
+
}
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
return true;
|
|
7543
|
+
};
|
|
7544
|
+
const TreeNodeComponent = () => {
|
|
7545
|
+
return {
|
|
7546
|
+
view: ({ attrs }) => {
|
|
7547
|
+
const { node, level, isSelected, isExpanded, isFocused, showConnectors, iconType, selectionMode, onToggleExpand, onToggleSelect, onFocus, } = attrs;
|
|
7548
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
7549
|
+
const indentLevel = level * 24; // 24px per level
|
|
7550
|
+
return m('li.tree-node', {
|
|
7551
|
+
class: [
|
|
7552
|
+
isSelected && 'selected',
|
|
7553
|
+
isFocused && 'focused',
|
|
7554
|
+
node.disabled && 'disabled',
|
|
7555
|
+
hasChildren && 'has-children',
|
|
7556
|
+
attrs.isLastInBranch && 'tree-last-in-branch',
|
|
7557
|
+
]
|
|
7558
|
+
.filter(Boolean)
|
|
7559
|
+
.join(' ') || undefined,
|
|
7560
|
+
'data-node-id': node.id,
|
|
7561
|
+
'data-level': level,
|
|
7562
|
+
}, [
|
|
7563
|
+
// Node content
|
|
7564
|
+
m('.tree-node-content', {
|
|
7565
|
+
style: {
|
|
7566
|
+
paddingLeft: `${indentLevel}px`,
|
|
7567
|
+
},
|
|
7568
|
+
onclick: node.disabled
|
|
7569
|
+
? undefined
|
|
7570
|
+
: () => {
|
|
7571
|
+
if (selectionMode !== 'none') {
|
|
7572
|
+
onToggleSelect(node.id);
|
|
7573
|
+
}
|
|
7574
|
+
onFocus(node.id);
|
|
7575
|
+
},
|
|
7576
|
+
onkeydown: (e) => {
|
|
7577
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
7578
|
+
e.preventDefault();
|
|
7579
|
+
if (!node.disabled && selectionMode !== 'none') {
|
|
7580
|
+
onToggleSelect(node.id);
|
|
7581
|
+
}
|
|
7582
|
+
}
|
|
7583
|
+
},
|
|
7584
|
+
tabindex: node.disabled ? -1 : 0,
|
|
7585
|
+
role: selectionMode === 'multiple' ? 'option' : 'treeitem',
|
|
7586
|
+
'aria-selected': selectionMode !== 'none' ? isSelected.toString() : undefined,
|
|
7587
|
+
'aria-expanded': hasChildren ? isExpanded.toString() : undefined,
|
|
7588
|
+
'aria-disabled': node.disabled ? 'true' : undefined,
|
|
7589
|
+
}, [
|
|
7590
|
+
// Connector lines
|
|
7591
|
+
showConnectors &&
|
|
7592
|
+
level > 0 &&
|
|
7593
|
+
m('.tree-connectors', Array.from({ length: level }, (_, i) => m('.tree-connector', {
|
|
7594
|
+
key: i,
|
|
7595
|
+
style: { left: `${i * 24 + 12}px` },
|
|
7596
|
+
}))),
|
|
7597
|
+
// Expand/collapse icon or spacer
|
|
7598
|
+
hasChildren
|
|
7599
|
+
? m('.tree-expand-icon', {
|
|
7600
|
+
onclick: (e) => {
|
|
7601
|
+
e.stopPropagation();
|
|
7602
|
+
if (!node.disabled) {
|
|
7603
|
+
onToggleExpand(node.id);
|
|
7604
|
+
}
|
|
7605
|
+
},
|
|
7606
|
+
class: iconType,
|
|
7607
|
+
}, [
|
|
7608
|
+
iconType === 'plus-minus'
|
|
7609
|
+
? m('span.tree-plus-minus', isExpanded ? '−' : '+')
|
|
7610
|
+
: iconType === 'triangle'
|
|
7611
|
+
? m('span.tree-triangle', { class: isExpanded ? 'expanded' : undefined }, '▶')
|
|
7612
|
+
: iconType === 'chevron'
|
|
7613
|
+
? m(MaterialIcon, {
|
|
7614
|
+
name: 'chevron',
|
|
7615
|
+
direction: isExpanded ? 'down' : 'right',
|
|
7616
|
+
class: 'tree-chevron-icon',
|
|
7617
|
+
})
|
|
7618
|
+
: m(MaterialIcon, {
|
|
7619
|
+
name: 'caret',
|
|
7620
|
+
direction: isExpanded ? 'down' : 'right',
|
|
7621
|
+
class: 'tree-caret-icon',
|
|
7622
|
+
}),
|
|
7623
|
+
])
|
|
7624
|
+
: m('.tree-expand-spacer'), // Spacer for alignment
|
|
7625
|
+
// Selection indicator for multiple selection
|
|
7626
|
+
selectionMode === 'multiple' &&
|
|
7627
|
+
m('.tree-selection-indicator', [
|
|
7628
|
+
m('input[type=checkbox]', {
|
|
7629
|
+
checked: isSelected,
|
|
7630
|
+
disabled: node.disabled,
|
|
7631
|
+
onchange: () => {
|
|
7632
|
+
if (!node.disabled) {
|
|
7633
|
+
onToggleSelect(node.id);
|
|
7634
|
+
}
|
|
7635
|
+
},
|
|
7636
|
+
onclick: (e) => e.stopPropagation(),
|
|
7637
|
+
}),
|
|
7638
|
+
]),
|
|
7639
|
+
// Node icon (optional)
|
|
7640
|
+
node.icon && m('i.tree-node-icon.material-icons', node.icon),
|
|
7641
|
+
// Node label
|
|
7642
|
+
m('span.tree-node-label', node.label),
|
|
7643
|
+
]),
|
|
7644
|
+
// Children (recursive)
|
|
7645
|
+
hasChildren &&
|
|
7646
|
+
isExpanded &&
|
|
7647
|
+
m('ul.tree-children', {
|
|
7648
|
+
role: 'group',
|
|
7649
|
+
'aria-expanded': 'true',
|
|
7650
|
+
}, node.children.map((child, childIndex) => {
|
|
7651
|
+
var _a, _b, _c, _d, _e, _f;
|
|
7652
|
+
// Calculate state for each child using treeState
|
|
7653
|
+
const childIsSelected = (_b = (_a = attrs.treeState) === null || _a === void 0 ? void 0 : _a.selectedIds.has(child.id)) !== null && _b !== void 0 ? _b : false;
|
|
7654
|
+
const childIsExpanded = (_d = (_c = attrs.treeState) === null || _c === void 0 ? void 0 : _c.expandedIds.has(child.id)) !== null && _d !== void 0 ? _d : false;
|
|
7655
|
+
const childIsFocused = ((_e = attrs.treeState) === null || _e === void 0 ? void 0 : _e.focusedNodeId) === child.id;
|
|
7656
|
+
// Calculate if this child is last in branch
|
|
7657
|
+
const childPath = [...(attrs.currentPath || []), childIndex];
|
|
7658
|
+
const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data) ?
|
|
7659
|
+
isNodeLastInBranch(childPath, attrs.treeAttrs.data) : false;
|
|
7660
|
+
return m(TreeNodeComponent, {
|
|
7661
|
+
key: child.id,
|
|
7662
|
+
node: child,
|
|
7663
|
+
level: level + 1,
|
|
7664
|
+
isSelected: childIsSelected,
|
|
7665
|
+
isExpanded: childIsExpanded,
|
|
7666
|
+
isFocused: childIsFocused,
|
|
7667
|
+
showConnectors,
|
|
7668
|
+
iconType,
|
|
7669
|
+
selectionMode,
|
|
7670
|
+
onToggleExpand,
|
|
7671
|
+
onToggleSelect,
|
|
7672
|
+
onFocus,
|
|
7673
|
+
isLastInBranch: childIsLastInBranch,
|
|
7674
|
+
currentPath: childPath,
|
|
7675
|
+
treeState: attrs.treeState,
|
|
7676
|
+
treeAttrs: attrs.treeAttrs,
|
|
7677
|
+
});
|
|
7678
|
+
})),
|
|
7679
|
+
]);
|
|
7680
|
+
},
|
|
7681
|
+
};
|
|
7682
|
+
};
|
|
7683
|
+
const TreeView = () => {
|
|
7684
|
+
const state = {
|
|
7685
|
+
selectedIds: new Set(),
|
|
7686
|
+
expandedIds: new Set(),
|
|
7687
|
+
focusedNodeId: null,
|
|
7688
|
+
treeMap: new Map(),
|
|
7689
|
+
};
|
|
7690
|
+
const buildTreeMap = (nodes, map) => {
|
|
7691
|
+
nodes.forEach((node) => {
|
|
7692
|
+
map.set(node.id, node);
|
|
7693
|
+
if (node.children) {
|
|
7694
|
+
buildTreeMap(node.children, map);
|
|
7695
|
+
}
|
|
7696
|
+
});
|
|
7697
|
+
};
|
|
7698
|
+
const initializeExpandedNodes = (nodes) => {
|
|
7699
|
+
nodes.forEach((node) => {
|
|
7700
|
+
if (node.expanded) {
|
|
7701
|
+
state.expandedIds.add(node.id);
|
|
7702
|
+
}
|
|
7703
|
+
if (node.children) {
|
|
7704
|
+
initializeExpandedNodes(node.children);
|
|
7705
|
+
}
|
|
7706
|
+
});
|
|
7707
|
+
};
|
|
7708
|
+
const handleToggleExpand = (nodeId, attrs) => {
|
|
7709
|
+
var _a;
|
|
7710
|
+
const isExpanded = state.expandedIds.has(nodeId);
|
|
7711
|
+
if (isExpanded) {
|
|
7712
|
+
state.expandedIds.delete(nodeId);
|
|
7713
|
+
}
|
|
7714
|
+
else {
|
|
7715
|
+
state.expandedIds.add(nodeId);
|
|
7716
|
+
}
|
|
7717
|
+
(_a = attrs.onexpand) === null || _a === void 0 ? void 0 : _a.call(attrs, { nodeId, expanded: !isExpanded });
|
|
7718
|
+
};
|
|
7719
|
+
const handleToggleSelect = (nodeId, attrs) => {
|
|
7720
|
+
var _a;
|
|
7721
|
+
const { selectionMode = 'single' } = attrs;
|
|
7722
|
+
if (selectionMode === 'single') {
|
|
7723
|
+
state.selectedIds.clear();
|
|
7724
|
+
state.selectedIds.add(nodeId);
|
|
7725
|
+
}
|
|
7726
|
+
else if (selectionMode === 'multiple') {
|
|
7727
|
+
if (state.selectedIds.has(nodeId)) {
|
|
7728
|
+
state.selectedIds.delete(nodeId);
|
|
7729
|
+
}
|
|
7730
|
+
else {
|
|
7731
|
+
state.selectedIds.add(nodeId);
|
|
7732
|
+
}
|
|
7733
|
+
}
|
|
7734
|
+
(_a = attrs.onselection) === null || _a === void 0 ? void 0 : _a.call(attrs, Array.from(state.selectedIds));
|
|
7735
|
+
};
|
|
7736
|
+
const handleFocus = (nodeId) => {
|
|
7737
|
+
state.focusedNodeId = nodeId;
|
|
7738
|
+
};
|
|
7739
|
+
const renderNodes = (nodes, attrs, level = 0, parentPath = []) => {
|
|
7740
|
+
return nodes.map((node, index) => {
|
|
7741
|
+
var _a, _b, _c;
|
|
7742
|
+
const isSelected = state.selectedIds.has(node.id);
|
|
7743
|
+
const isExpanded = state.expandedIds.has(node.id);
|
|
7744
|
+
const isFocused = state.focusedNodeId === node.id;
|
|
7745
|
+
const currentPath = [...parentPath, index];
|
|
7746
|
+
const isLastInBranch = isNodeLastInBranch(currentPath, attrs.data);
|
|
7747
|
+
return m(TreeNodeComponent, {
|
|
7748
|
+
key: node.id,
|
|
7749
|
+
node,
|
|
7750
|
+
level,
|
|
7751
|
+
isSelected,
|
|
7752
|
+
isExpanded,
|
|
7753
|
+
isFocused,
|
|
7754
|
+
showConnectors: (_a = attrs.showConnectors) !== null && _a !== void 0 ? _a : true,
|
|
7755
|
+
iconType: (_b = attrs.iconType) !== null && _b !== void 0 ? _b : 'caret',
|
|
7756
|
+
selectionMode: (_c = attrs.selectionMode) !== null && _c !== void 0 ? _c : 'single',
|
|
7757
|
+
onToggleExpand: (nodeId) => handleToggleExpand(nodeId, attrs),
|
|
7758
|
+
onToggleSelect: (nodeId) => handleToggleSelect(nodeId, attrs),
|
|
7759
|
+
onFocus: handleFocus,
|
|
7760
|
+
isLastInBranch,
|
|
7761
|
+
currentPath,
|
|
7762
|
+
// Pass state and attrs for recursive rendering
|
|
7763
|
+
treeState: state,
|
|
7764
|
+
treeAttrs: attrs,
|
|
7765
|
+
});
|
|
7766
|
+
});
|
|
7767
|
+
};
|
|
7768
|
+
return {
|
|
7769
|
+
oninit: ({ attrs }) => {
|
|
7770
|
+
// Build internal tree map for efficient lookups
|
|
7771
|
+
buildTreeMap(attrs.data, state.treeMap);
|
|
7772
|
+
// Initialize expanded nodes from data
|
|
7773
|
+
initializeExpandedNodes(attrs.data);
|
|
7774
|
+
// Initialize selected nodes from props
|
|
7775
|
+
if (attrs.selectedIds) {
|
|
7776
|
+
state.selectedIds = new Set(attrs.selectedIds);
|
|
7777
|
+
}
|
|
7778
|
+
},
|
|
7779
|
+
onupdate: ({ attrs }) => {
|
|
7780
|
+
// Sync selectedIds prop with internal state
|
|
7781
|
+
if (attrs.selectedIds) {
|
|
7782
|
+
const newSelection = new Set(attrs.selectedIds);
|
|
7783
|
+
if (newSelection.size !== state.selectedIds.size ||
|
|
7784
|
+
!Array.from(newSelection).every((id) => state.selectedIds.has(id))) {
|
|
7785
|
+
state.selectedIds = newSelection;
|
|
7786
|
+
}
|
|
7787
|
+
}
|
|
7788
|
+
},
|
|
7789
|
+
view: ({ attrs }) => {
|
|
7790
|
+
const { data, className, style, id, selectionMode = 'single', showConnectors = true } = attrs;
|
|
7791
|
+
return m('div.tree-view', {
|
|
7792
|
+
class: [
|
|
7793
|
+
className,
|
|
7794
|
+
showConnectors && 'show-connectors'
|
|
7795
|
+
].filter(Boolean).join(' ') || undefined,
|
|
7796
|
+
style,
|
|
7797
|
+
id,
|
|
7798
|
+
role: selectionMode === 'multiple' ? 'listbox' : 'tree',
|
|
7799
|
+
'aria-multiselectable': selectionMode === 'multiple' ? 'true' : 'false',
|
|
7800
|
+
}, [
|
|
7801
|
+
m('ul.tree-root', {
|
|
7802
|
+
role: 'group',
|
|
7803
|
+
}, renderNodes(data, attrs)),
|
|
7804
|
+
]);
|
|
7805
|
+
},
|
|
7806
|
+
};
|
|
7807
|
+
};
|
|
7808
|
+
|
|
7809
|
+
/**
|
|
7810
|
+
* Timeline Component
|
|
7811
|
+
* Displays a sequence of events in chronological order with connecting lines
|
|
7812
|
+
* Supports both vertical and horizontal orientations with Material Design styling
|
|
7813
|
+
*/
|
|
7814
|
+
const Timeline = () => {
|
|
7815
|
+
const formatTimestamp = (timestamp) => {
|
|
7816
|
+
if (!timestamp)
|
|
7817
|
+
return '';
|
|
7818
|
+
if (typeof timestamp === 'string')
|
|
7819
|
+
return timestamp;
|
|
7820
|
+
return timestamp.toLocaleDateString();
|
|
7821
|
+
};
|
|
7822
|
+
const getColorClass = (color) => {
|
|
7823
|
+
switch (color) {
|
|
7824
|
+
case 'primary':
|
|
7825
|
+
return 'timeline-primary';
|
|
7826
|
+
case 'secondary':
|
|
7827
|
+
return 'timeline-secondary';
|
|
7828
|
+
case 'success':
|
|
7829
|
+
return 'timeline-success';
|
|
7830
|
+
case 'warning':
|
|
7831
|
+
return 'timeline-warning';
|
|
7832
|
+
case 'error':
|
|
7833
|
+
return 'timeline-error';
|
|
7834
|
+
case 'info':
|
|
7835
|
+
return 'timeline-info';
|
|
7836
|
+
default:
|
|
7837
|
+
return 'timeline-default';
|
|
7838
|
+
}
|
|
7839
|
+
};
|
|
7840
|
+
return {
|
|
7841
|
+
view: ({ attrs }) => {
|
|
7842
|
+
const { items = [], orientation = 'vertical', position = 'right', showConnector = true, className = '', showTimestamps = true, compact = false, } = attrs;
|
|
7843
|
+
const timelineClasses = [
|
|
7844
|
+
'timeline',
|
|
7845
|
+
`timeline-${orientation}`,
|
|
7846
|
+
`timeline-${position}`,
|
|
7847
|
+
showConnector ? 'timeline-connector' : '',
|
|
7848
|
+
compact ? 'timeline-compact' : '',
|
|
7849
|
+
className,
|
|
7850
|
+
]
|
|
7851
|
+
.filter(Boolean)
|
|
7852
|
+
.join(' ') || undefined;
|
|
7853
|
+
return m('div', { className: timelineClasses }, [
|
|
7854
|
+
items.map((item, index) => {
|
|
7855
|
+
const isAlternate = position === 'alternate';
|
|
7856
|
+
const itemPosition = isAlternate ? (index % 2 === 0 ? 'right' : 'left') : position;
|
|
7857
|
+
const itemClasses = [
|
|
7858
|
+
'timeline-item',
|
|
7859
|
+
`timeline-item-${itemPosition}`,
|
|
7860
|
+
getColorClass(item.color),
|
|
7861
|
+
item.disabled ? 'timeline-item-disabled' : '',
|
|
7862
|
+
item.className || '',
|
|
7863
|
+
]
|
|
7864
|
+
.filter(Boolean)
|
|
7865
|
+
.join(' ') || undefined;
|
|
7866
|
+
const handleItemClick = (e) => {
|
|
7867
|
+
if (item.disabled)
|
|
7868
|
+
return;
|
|
7869
|
+
if (item.onClick) {
|
|
7870
|
+
e.preventDefault();
|
|
7871
|
+
item.onClick(item, e);
|
|
7872
|
+
}
|
|
7873
|
+
};
|
|
7874
|
+
const isVertical = orientation === 'vertical';
|
|
7875
|
+
return m('div', {
|
|
7876
|
+
key: item.id || index,
|
|
7877
|
+
className: itemClasses,
|
|
7878
|
+
onclick: item.onClick ? handleItemClick : undefined,
|
|
7879
|
+
role: item.onClick ? 'button' : undefined,
|
|
7880
|
+
tabindex: item.onClick && !item.disabled ? 0 : undefined,
|
|
7881
|
+
'aria-disabled': item.disabled ? 'true' : undefined,
|
|
7882
|
+
}, [
|
|
7883
|
+
// Timestamp (on opposite side of content from bullet)
|
|
7884
|
+
isVertical &&
|
|
7885
|
+
showTimestamps &&
|
|
7886
|
+
item.timestamp &&
|
|
7887
|
+
m(`.timeline-timestamp-separate${!item.icon ? '.timeline-dot-small' : ''}`, formatTimestamp(item.timestamp)),
|
|
7888
|
+
// Timeline separator containing dot and connector
|
|
7889
|
+
m('.timeline-separator', [
|
|
7890
|
+
// Timeline dot/icon
|
|
7891
|
+
m(`.timeline-dot${!item.icon ? '.timeline-dot-small' : ''}`, [
|
|
7892
|
+
item.icon ? m('i.material-icons.timeline-icon', item.icon) : m('.timeline-marker'),
|
|
7893
|
+
]),
|
|
7894
|
+
// Timeline connector (only show if not the last item and connectors are enabled)
|
|
7895
|
+
showConnector && index < items.length - 1 && m('.timeline-connector'),
|
|
7896
|
+
]),
|
|
7897
|
+
// Content container
|
|
7898
|
+
m('.timeline-content', [
|
|
7899
|
+
// Timestamp for horizontal layout or when not shown separately
|
|
7900
|
+
!isVertical &&
|
|
7901
|
+
showTimestamps &&
|
|
7902
|
+
item.timestamp &&
|
|
7903
|
+
m('.timeline-timestamp', formatTimestamp(item.timestamp)),
|
|
7904
|
+
// Main content
|
|
7905
|
+
item.content
|
|
7906
|
+
? item.content
|
|
7907
|
+
: m('.timeline-text', [
|
|
7908
|
+
item.label && m('.timeline-label', item.label),
|
|
7909
|
+
item.description && m('.timeline-description', item.description),
|
|
7910
|
+
]),
|
|
7911
|
+
]),
|
|
7912
|
+
]);
|
|
7913
|
+
}),
|
|
7914
|
+
]);
|
|
7915
|
+
},
|
|
7916
|
+
};
|
|
7917
|
+
};
|
|
7918
|
+
|
|
7919
|
+
const Masonry = () => {
|
|
7920
|
+
let containerRef = null;
|
|
7921
|
+
const itemHeights = []; // measured heights
|
|
7922
|
+
let resizeObserver = null;
|
|
7923
|
+
const defaultBreakpoints = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5 };
|
|
7924
|
+
const getColumnsCount = (columns = 3) => {
|
|
7925
|
+
if (typeof columns === 'number')
|
|
7926
|
+
return columns;
|
|
7927
|
+
const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), columns);
|
|
7928
|
+
const width = window.innerWidth;
|
|
7929
|
+
if (width >= 1200)
|
|
7930
|
+
return breakpoints.xl || 5;
|
|
7931
|
+
if (width >= 992)
|
|
7932
|
+
return breakpoints.lg || 4;
|
|
7933
|
+
if (width >= 768)
|
|
7934
|
+
return breakpoints.md || 3;
|
|
7935
|
+
if (width >= 576)
|
|
7936
|
+
return breakpoints.sm || 2;
|
|
7937
|
+
return breakpoints.xs || 1;
|
|
7938
|
+
};
|
|
7939
|
+
const setupResizeObserver = () => {
|
|
7940
|
+
if (resizeObserver)
|
|
7941
|
+
return;
|
|
7942
|
+
if (typeof ResizeObserver === 'undefined')
|
|
7943
|
+
return;
|
|
7944
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
7945
|
+
for (const entry of entries) {
|
|
7946
|
+
const el = entry.target;
|
|
7947
|
+
const index = Number(el.dataset.index);
|
|
7948
|
+
if (!isNaN(index)) {
|
|
7949
|
+
const h = el.offsetHeight;
|
|
7950
|
+
if (itemHeights[index] !== h) {
|
|
7951
|
+
itemHeights[index] = h;
|
|
7952
|
+
m.redraw();
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
});
|
|
7957
|
+
};
|
|
7958
|
+
const cleanup = () => {
|
|
7959
|
+
if (resizeObserver) {
|
|
7960
|
+
resizeObserver.disconnect();
|
|
7961
|
+
resizeObserver = null;
|
|
7962
|
+
}
|
|
7963
|
+
};
|
|
7964
|
+
return {
|
|
7965
|
+
onremove: cleanup,
|
|
7966
|
+
view: ({ attrs, children }) => {
|
|
7967
|
+
const { columns = 3, spacing = 16, className = '', onItemClick, cssOnly = false, animationDelay } = attrs;
|
|
7968
|
+
const gap = typeof spacing === 'number' ? spacing : parseInt(spacing, 10) || 16;
|
|
7969
|
+
const columnsCount = typeof columns === 'number' ? columns : getColumnsCount(columns);
|
|
7970
|
+
const containerClasses = [
|
|
7971
|
+
'masonry',
|
|
7972
|
+
cssOnly ? 'masonry-css' : 'masonry-js',
|
|
7973
|
+
animationDelay ? 'masonry-animated' : '',
|
|
7974
|
+
className,
|
|
7975
|
+
]
|
|
7976
|
+
.filter(Boolean)
|
|
7977
|
+
.join(' ');
|
|
7978
|
+
const containerStyle = { position: 'relative' };
|
|
7979
|
+
// --- CSS-only fallback ---
|
|
7980
|
+
if (cssOnly) {
|
|
7981
|
+
containerStyle.display = 'grid';
|
|
7982
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
7983
|
+
containerStyle.gap = `${gap}px`;
|
|
7984
|
+
return m('div', { className: containerClasses, style: containerStyle }, children);
|
|
7985
|
+
}
|
|
7986
|
+
// --- JS Masonry ---
|
|
7987
|
+
const containerWidth = (containerRef === null || containerRef === void 0 ? void 0 : containerRef.offsetWidth) || 800;
|
|
7988
|
+
const totalGapWidth = gap * (columnsCount - 1);
|
|
7989
|
+
const columnWidth = (containerWidth - totalGapWidth) / columnsCount;
|
|
7990
|
+
const columnHeights = new Array(columnsCount).fill(0);
|
|
7991
|
+
const positionedChildren = [];
|
|
7992
|
+
(Array.isArray(children) ? children : [children]).forEach((child, index) => {
|
|
7993
|
+
var _a;
|
|
7994
|
+
const shortestColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
|
|
7995
|
+
const x = shortestColumnIndex * (columnWidth + gap);
|
|
7996
|
+
const y = columnHeights[shortestColumnIndex];
|
|
7997
|
+
const itemHeight = (_a = itemHeights[index]) !== null && _a !== void 0 ? _a : 200; // fallback until measured
|
|
7998
|
+
columnHeights[shortestColumnIndex] += itemHeight + gap;
|
|
7999
|
+
const itemStyle = {
|
|
8000
|
+
position: 'absolute',
|
|
8001
|
+
left: `${x}px`,
|
|
8002
|
+
top: `${y}px`,
|
|
8003
|
+
width: `${columnWidth}px`,
|
|
8004
|
+
transition: 'all 0.3s ease',
|
|
8005
|
+
animationDelay: animationDelay ? `${index * animationDelay}ms` : undefined,
|
|
8006
|
+
};
|
|
8007
|
+
positionedChildren.push(m('div', {
|
|
8008
|
+
key: `masonry-item-${index}`,
|
|
8009
|
+
className: 'masonry-item',
|
|
8010
|
+
style: itemStyle,
|
|
8011
|
+
'data-index': index,
|
|
8012
|
+
onclick: onItemClick ? (e) => onItemClick(index, e) : undefined,
|
|
8013
|
+
oncreate: ({ dom }) => {
|
|
8014
|
+
const el = dom;
|
|
8015
|
+
setupResizeObserver();
|
|
8016
|
+
resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(el);
|
|
8017
|
+
const h = el.offsetHeight;
|
|
8018
|
+
if (itemHeights[index] !== h) {
|
|
8019
|
+
itemHeights[index] = h;
|
|
8020
|
+
m.redraw();
|
|
8021
|
+
}
|
|
8022
|
+
},
|
|
8023
|
+
onremove: ({ dom }) => {
|
|
8024
|
+
resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.unobserve(dom);
|
|
8025
|
+
},
|
|
8026
|
+
}, child));
|
|
8027
|
+
});
|
|
8028
|
+
containerStyle.height = `${Math.max(...columnHeights) - gap}px`;
|
|
8029
|
+
return m('div', {
|
|
8030
|
+
className: containerClasses,
|
|
8031
|
+
style: containerStyle,
|
|
8032
|
+
oncreate: ({ dom }) => {
|
|
8033
|
+
containerRef = dom;
|
|
8034
|
+
},
|
|
8035
|
+
}, positionedChildren);
|
|
8036
|
+
},
|
|
8037
|
+
};
|
|
8038
|
+
};
|
|
8039
|
+
|
|
8040
|
+
/**
|
|
8041
|
+
* ImageList Component
|
|
8042
|
+
* Displays a collection of images in various grid layouts
|
|
8043
|
+
* Supports standard grid, quilted (varied sizes), masonry, and woven patterns
|
|
8044
|
+
*/
|
|
8045
|
+
const ImageList = () => {
|
|
8046
|
+
const defaultBreakpoints = {
|
|
8047
|
+
xs: 1,
|
|
8048
|
+
sm: 2,
|
|
8049
|
+
md: 3,
|
|
8050
|
+
lg: 4,
|
|
8051
|
+
xl: 5
|
|
8052
|
+
};
|
|
8053
|
+
const getColumnsCount = (cols = 3) => {
|
|
8054
|
+
if (typeof cols === 'number')
|
|
8055
|
+
return cols;
|
|
8056
|
+
const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), cols);
|
|
8057
|
+
const width = typeof window !== 'undefined' ? window.innerWidth : 1024;
|
|
8058
|
+
if (width >= 1200)
|
|
8059
|
+
return breakpoints.xl || 5;
|
|
8060
|
+
if (width >= 992)
|
|
8061
|
+
return breakpoints.lg || 4;
|
|
8062
|
+
if (width >= 768)
|
|
8063
|
+
return breakpoints.md || 3;
|
|
8064
|
+
if (width >= 576)
|
|
8065
|
+
return breakpoints.sm || 2;
|
|
8066
|
+
return breakpoints.xs || 1;
|
|
8067
|
+
};
|
|
8068
|
+
const handleImageLoad = (e) => {
|
|
8069
|
+
const img = e.target;
|
|
8070
|
+
img.classList.add('loaded');
|
|
8071
|
+
};
|
|
8072
|
+
const handleImageError = (e) => {
|
|
8073
|
+
const img = e.target;
|
|
8074
|
+
img.classList.add('error');
|
|
8075
|
+
// Could set a placeholder image here
|
|
8076
|
+
img.alt = 'Failed to load image';
|
|
8077
|
+
};
|
|
8078
|
+
const renderImage = (item, index, options) => {
|
|
8079
|
+
const { src, alt = '', title, subtitle, onclick, actionButton, className = '', loading = options.loading || 'lazy', aspectRatio, cols = 1, rows = 1, featured = false } = item;
|
|
8080
|
+
const itemClasses = [
|
|
8081
|
+
'image-list-item',
|
|
8082
|
+
featured ? 'image-list-item-featured' : '',
|
|
8083
|
+
onclick ? 'image-list-item-clickable' : '',
|
|
8084
|
+
className
|
|
8085
|
+
].filter(Boolean).join(' ');
|
|
8086
|
+
const itemStyle = {};
|
|
8087
|
+
// Quilted layout with fixed alternating pattern
|
|
8088
|
+
if (options.variant === 'quilted') {
|
|
8089
|
+
if (featured || index % 7 === 0) {
|
|
8090
|
+
itemStyle.gridColumnEnd = 'span 2';
|
|
8091
|
+
itemStyle.gridRowEnd = 'span 2';
|
|
8092
|
+
}
|
|
8093
|
+
else if (index % 3 === 0) {
|
|
8094
|
+
itemStyle.gridColumnEnd = 'span 2';
|
|
8095
|
+
itemStyle.gridRowEnd = 'span 1';
|
|
8096
|
+
}
|
|
8097
|
+
else {
|
|
8098
|
+
itemStyle.gridColumnEnd = 'span 1';
|
|
8099
|
+
itemStyle.gridRowEnd = 'span 1';
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
8102
|
+
// Woven layout with varied sizes based on item properties
|
|
8103
|
+
if (options.variant === 'woven') {
|
|
8104
|
+
itemStyle.gridColumnEnd = `span ${cols}`;
|
|
8105
|
+
itemStyle.gridRowEnd = `span ${rows}`;
|
|
8106
|
+
}
|
|
8107
|
+
// Masonry layout - prevent break inside items
|
|
8108
|
+
if (options.variant === 'masonry') {
|
|
8109
|
+
itemStyle.breakInside = 'avoid';
|
|
8110
|
+
itemStyle.marginBottom = typeof options.gap === 'number' ? `${options.gap}px` : options.gap || '4px';
|
|
8111
|
+
itemStyle.display = 'inline-block';
|
|
8112
|
+
itemStyle.width = '100%';
|
|
8113
|
+
}
|
|
8114
|
+
// Custom aspect ratio
|
|
8115
|
+
if (aspectRatio && options.variant !== 'masonry') {
|
|
8116
|
+
itemStyle.aspectRatio = aspectRatio.toString();
|
|
8117
|
+
}
|
|
8118
|
+
const handleItemClick = (e) => {
|
|
8119
|
+
if (onclick) {
|
|
8120
|
+
e.preventDefault();
|
|
8121
|
+
onclick(item, e);
|
|
8122
|
+
}
|
|
8123
|
+
};
|
|
8124
|
+
const handleActionClick = (e) => {
|
|
8125
|
+
e.stopPropagation();
|
|
8126
|
+
if (actionButton === null || actionButton === void 0 ? void 0 : actionButton.onclick) {
|
|
8127
|
+
actionButton.onclick(item, e);
|
|
8128
|
+
}
|
|
8129
|
+
};
|
|
8130
|
+
return m(`.${itemClasses}`, {
|
|
8131
|
+
key: `image-${index}`,
|
|
8132
|
+
style: itemStyle,
|
|
8133
|
+
onclick: onclick ? handleItemClick : undefined,
|
|
8134
|
+
role: onclick ? 'button' : undefined,
|
|
8135
|
+
tabindex: onclick ? 0 : undefined,
|
|
8136
|
+
}, [
|
|
8137
|
+
m('.image-list-item-img', [
|
|
8138
|
+
m('img', {
|
|
8139
|
+
src,
|
|
8140
|
+
alt,
|
|
8141
|
+
loading,
|
|
8142
|
+
onload: handleImageLoad,
|
|
8143
|
+
onerror: handleImageError,
|
|
8144
|
+
draggable: false,
|
|
8145
|
+
}),
|
|
8146
|
+
// Loading placeholder
|
|
8147
|
+
m('.image-list-item-placeholder'),
|
|
8148
|
+
]),
|
|
8149
|
+
// Title overlay
|
|
8150
|
+
(options.showTitles && (title || subtitle)) &&
|
|
8151
|
+
m('.image-list-item-bar', [
|
|
8152
|
+
m('.image-list-item-title-wrap', [
|
|
8153
|
+
title && m('.image-list-item-title', title),
|
|
8154
|
+
subtitle && m('.image-list-item-subtitle', subtitle)
|
|
8155
|
+
])
|
|
8156
|
+
]),
|
|
8157
|
+
// Action button
|
|
8158
|
+
(options.showActions && actionButton) &&
|
|
8159
|
+
m('button.image-list-item-action', {
|
|
8160
|
+
class: `image-list-action-${actionButton.position || 'top-right'}`,
|
|
8161
|
+
onclick: handleActionClick,
|
|
8162
|
+
'aria-label': actionButton.ariaLabel || `Action for ${title || alt}`,
|
|
8163
|
+
}, [
|
|
8164
|
+
m('i.material-icons', actionButton.icon)
|
|
8165
|
+
])
|
|
8166
|
+
]);
|
|
8167
|
+
};
|
|
8168
|
+
return {
|
|
8169
|
+
view: ({ attrs }) => {
|
|
8170
|
+
const { items = [], variant = 'standard', cols = 3, gap = 4, rowHeight = 'auto', className = '', showTitles = false, showActions = false } = attrs;
|
|
8171
|
+
const columnsCount = getColumnsCount(cols);
|
|
8172
|
+
const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
|
|
8173
|
+
const containerClasses = [
|
|
8174
|
+
'image-list',
|
|
8175
|
+
`image-list-${variant}`,
|
|
8176
|
+
showTitles ? 'image-list-with-titles' : '',
|
|
8177
|
+
showActions ? 'image-list-with-actions' : '',
|
|
8178
|
+
className
|
|
8179
|
+
].filter(Boolean).join(' ');
|
|
8180
|
+
const containerStyle = {
|
|
8181
|
+
gap: gapValue
|
|
8182
|
+
};
|
|
8183
|
+
// Set up grid based on variant
|
|
8184
|
+
switch (variant) {
|
|
8185
|
+
case 'standard':
|
|
8186
|
+
containerStyle.display = 'grid';
|
|
8187
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
8188
|
+
if (rowHeight !== 'auto') {
|
|
8189
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight;
|
|
8190
|
+
}
|
|
8191
|
+
break;
|
|
8192
|
+
case 'quilted':
|
|
8193
|
+
// Fixed pattern like woven
|
|
8194
|
+
containerStyle.display = 'grid';
|
|
8195
|
+
containerStyle.gridTemplateColumns = `repeat(${Math.max(columnsCount * 2, 4)}, 1fr)`;
|
|
8196
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '150px';
|
|
8197
|
+
containerStyle.gridAutoFlow = 'dense';
|
|
8198
|
+
break;
|
|
8199
|
+
case 'masonry':
|
|
8200
|
+
// Use CSS columns for masonry effect
|
|
8201
|
+
containerStyle.display = 'block';
|
|
8202
|
+
containerStyle.columnCount = columnsCount;
|
|
8203
|
+
containerStyle.columnGap = gapValue;
|
|
8204
|
+
containerStyle.columnFill = 'balance';
|
|
8205
|
+
containerStyle.orphans = 1;
|
|
8206
|
+
containerStyle.widows = 1;
|
|
8207
|
+
break;
|
|
8208
|
+
case 'woven':
|
|
8209
|
+
// Varied sizes based on item cols/rows
|
|
8210
|
+
containerStyle.display = 'grid';
|
|
8211
|
+
containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
|
|
8212
|
+
containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '200px';
|
|
8213
|
+
containerStyle.gridAutoFlow = 'dense';
|
|
8214
|
+
break;
|
|
8215
|
+
}
|
|
8216
|
+
return m(`.${containerClasses}`, {
|
|
8217
|
+
style: containerStyle
|
|
8218
|
+
}, [
|
|
8219
|
+
items.map((item, index) => renderImage(item, index, attrs))
|
|
8220
|
+
]);
|
|
8221
|
+
}
|
|
8222
|
+
};
|
|
8223
|
+
};
|
|
8224
|
+
|
|
8225
|
+
/**
|
|
8226
|
+
* @fileoverview Core TypeScript utility types for mithril-materialized library
|
|
8227
|
+
* These types improve type safety and developer experience across all components
|
|
8228
|
+
*/
|
|
8229
|
+
/**
|
|
8230
|
+
* Type guard to check if validation result indicates success
|
|
8231
|
+
* @param result - The validation result to check
|
|
8232
|
+
* @returns True if validation passed
|
|
8233
|
+
*/
|
|
8234
|
+
const isValidationSuccess = (result) => result === true || result === '';
|
|
8235
|
+
/**
|
|
8236
|
+
* Type guard to check if validation result indicates an error
|
|
8237
|
+
* @param result - The validation result to check
|
|
8238
|
+
* @returns True if validation failed
|
|
8239
|
+
*/
|
|
8240
|
+
const isValidationError = (result) => !isValidationSuccess(result);
|
|
8241
|
+
// ============================================================================
|
|
8242
|
+
// EXPORTS
|
|
8243
|
+
// ============================================================================
|
|
8244
|
+
// All types are already exported via individual export declarations above
|
|
8245
|
+
|
|
8246
|
+
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 };
|