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