mithril-materialized 2.0.0-beta.9 → 2.0.0-rc.2

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