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