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