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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) 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/forms.css +344 -13
  9. package/dist/icon.d.ts +2 -2
  10. package/dist/image-list.d.ts +70 -0
  11. package/dist/index.css +1940 -20
  12. package/dist/index.d.ts +8 -0
  13. package/dist/index.esm.js +2688 -630
  14. package/dist/index.js +2698 -629
  15. package/dist/index.min.css +2 -2
  16. package/dist/index.umd.js +2698 -629
  17. package/dist/input-options.d.ts +18 -4
  18. package/dist/input.d.ts +0 -1
  19. package/dist/masonry.d.ts +17 -0
  20. package/dist/material-icon.d.ts +3 -0
  21. package/dist/pickers.css +45 -0
  22. package/dist/range-slider.d.ts +42 -0
  23. package/dist/timeline.d.ts +43 -0
  24. package/dist/treeview.d.ts +39 -0
  25. package/dist/types.d.ts +226 -0
  26. package/dist/utilities.css +16 -9
  27. package/package.json +12 -9
  28. package/sass/components/_cards.scss +10 -3
  29. package/sass/components/_datatable.scss +417 -0
  30. package/sass/components/_datepicker.scss +57 -0
  31. package/sass/components/_global.scss +6 -6
  32. package/sass/components/_image-list.scss +421 -0
  33. package/sass/components/_masonry.scss +241 -0
  34. package/sass/components/_timeline.scss +452 -0
  35. package/sass/components/_treeview.scss +353 -0
  36. package/sass/components/forms/_forms.scss +1 -1
  37. package/sass/components/forms/_range-enhanced.scss +406 -0
  38. package/sass/components/forms/_range.scss +5 -5
  39. package/sass/components/forms/_select.scss +1 -1
  40. 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),
@@ -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,82 +3165,1009 @@ 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;
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;
2742
4171
  font-size: 16px;
2743
4172
  text-align: center;
2744
4173
  opacity: 0;
@@ -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
@@ -2973,135 +4406,37 @@ const ModalPanel = () => {
2973
4406
  minWidth: 'auto',
2974
4407
  lineHeight: 1,
2975
4408
  },
2976
- onclick: () => closeModal(attrs),
2977
- 'aria-label': 'Close modal',
2978
- }, '×'),
2979
- // Modal content
2980
- m('.modal-content', {
2981
- style: { padding: '24px', paddingTop: showCloseButton ? '48px' : '24px' },
2982
- }, [
2983
- m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
2984
- description &&
2985
- m('div', Object.assign({ id: `${id}-desc` }, (richContent && typeof description === 'string' ? { innerHTML: description } : {})), richContent && typeof description === 'string' ? undefined : description),
2986
- ]),
2987
- // Modal footer with buttons
2988
- buttons &&
2989
- buttons.length > 0 &&
2990
- m('.modal-footer', {
2991
- 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'),
4409
+ onclick: () => closeModal(attrs),
4410
+ 'aria-label': 'Close modal',
4411
+ }, '×'),
4412
+ // Modal content
4413
+ m('.modal-content', {
4414
+ style: {
4415
+ padding: '24px',
4416
+ paddingTop: showCloseButton ? '48px' : '24px',
4417
+ minHeight: 'auto',
4418
+ flex: '1 1 auto',
4419
+ },
4420
+ }, [
4421
+ m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
4422
+ description &&
4423
+ m('div', Object.assign({ id: `${id}-desc` }, (richContent && typeof description === 'string' ? { innerHTML: description } : {})), richContent && typeof description === 'string' ? undefined : description),
3102
4424
  ]),
3103
- description && m(HelperText, { helperText: description }),
3104
- m('form', { action: '#' }, optionsContent),
4425
+ // Modal footer with buttons
4426
+ buttons &&
4427
+ buttons.length > 0 &&
4428
+ m('.modal-footer', {
4429
+ style: {
4430
+ padding: '4px 6px',
4431
+ borderTop: '1px solid var(--mm-border-color, rgba(160,160,160,0.2))',
4432
+ textAlign: 'right',
4433
+ },
4434
+ }, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
4435
+ if (buttonProps.onclick)
4436
+ buttonProps.onclick(e);
4437
+ closeModal(attrs);
4438
+ } })))),
4439
+ ]),
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();
@@ -5489,8 +6839,8 @@ const FileUpload = () => {
5489
6839
  }
5490
6840
  // Check file type
5491
6841
  if (attrs.accept) {
5492
- const acceptedTypes = attrs.accept.split(',').map(type => type.trim());
5493
- const isAccepted = acceptedTypes.some(acceptedType => {
6842
+ const acceptedTypes = attrs.accept.split(',').map((type) => type.trim());
6843
+ const isAccepted = acceptedTypes.some((acceptedType) => {
5494
6844
  if (acceptedType.startsWith('.')) {
5495
6845
  // Extension check
5496
6846
  return file.name.toLowerCase().endsWith(acceptedType.toLowerCase());
@@ -5550,11 +6900,11 @@ const FileUpload = () => {
5550
6900
  }
5551
6901
  // Notify parent component
5552
6902
  if (attrs.onFilesSelected) {
5553
- attrs.onFilesSelected(state.files.filter(f => !f.uploadError));
6903
+ attrs.onFilesSelected(state.files.filter((f) => !f.uploadError));
5554
6904
  }
5555
6905
  };
5556
6906
  const removeFile = (fileToRemove, attrs) => {
5557
- state.files = state.files.filter(file => file !== fileToRemove);
6907
+ state.files = state.files.filter((file) => file !== fileToRemove);
5558
6908
  if (attrs.onFileRemoved) {
5559
6909
  attrs.onFileRemoved(fileToRemove);
5560
6910
  }
@@ -5573,11 +6923,11 @@ const FileUpload = () => {
5573
6923
  id: uniqueId(),
5574
6924
  files: [],
5575
6925
  isDragOver: false,
5576
- isUploading: false
6926
+ isUploading: false,
5577
6927
  };
5578
6928
  },
5579
6929
  view: ({ attrs }) => {
5580
- const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error } = attrs;
6930
+ const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error, } = attrs;
5581
6931
  return m('.file-upload-container', { class: className }, [
5582
6932
  // Upload area
5583
6933
  m('.file-upload-area', {
@@ -5585,8 +6935,10 @@ const FileUpload = () => {
5585
6935
  state.isDragOver ? 'drag-over' : '',
5586
6936
  disabled ? 'disabled' : '',
5587
6937
  error ? 'error' : '',
5588
- state.files.length > 0 ? 'has-files' : ''
5589
- ].filter(Boolean).join(' '),
6938
+ state.files.length > 0 ? 'has-files' : '',
6939
+ ]
6940
+ .filter(Boolean)
6941
+ .join(' ') || undefined,
5590
6942
  ondragover: (e) => {
5591
6943
  if (disabled)
5592
6944
  return;
@@ -5617,7 +6969,7 @@ const FileUpload = () => {
5617
6969
  return;
5618
6970
  const input = document.getElementById(state.id);
5619
6971
  input === null || input === void 0 ? void 0 : input.click();
5620
- }
6972
+ },
5621
6973
  }, [
5622
6974
  m('input[type="file"]', {
5623
6975
  id: state.id,
@@ -5630,57 +6982,55 @@ const FileUpload = () => {
5630
6982
  if (target.files) {
5631
6983
  handleFiles(target.files, attrs);
5632
6984
  }
5633
- }
6985
+ },
5634
6986
  }),
5635
6987
  m('.file-upload-content', [
5636
6988
  m('i.material-icons.file-upload-icon', 'cloud_upload'),
5637
6989
  m('p.file-upload-label', label),
5638
6990
  helperText && m('p.file-upload-helper', helperText),
5639
- accept && m('p.file-upload-types', `Accepted: ${accept}`)
5640
- ])
6991
+ accept && m('p.file-upload-types', `Accepted: ${accept}`),
6992
+ ]),
5641
6993
  ]),
5642
6994
  // Error message
5643
6995
  error && m('.file-upload-error', error),
5644
6996
  // 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
- ])
6997
+ state.files.length > 0 &&
6998
+ m('.file-upload-list', [
6999
+ m('h6', 'Selected Files:'),
7000
+ state.files.map((file) => m('.file-upload-item', { key: file.name + file.size }, [
7001
+ // Preview thumbnail
7002
+ showPreview && file.preview && m('.file-preview', [m('img', { src: file.preview, alt: file.name })]),
7003
+ // File info
7004
+ m('.file-info', [
7005
+ m('.file-name', file.name),
7006
+ m('.file-details', [
7007
+ m('span.file-size', formatFileSize(file.size)),
7008
+ file.type && m('span.file-type', file.type),
7009
+ ]),
7010
+ // Progress bar (if uploading)
7011
+ file.uploadProgress !== undefined &&
7012
+ m('.file-progress', [
7013
+ m('.progress', [
7014
+ m('.determinate', {
7015
+ style: { width: `${file.uploadProgress}%` },
7016
+ }),
7017
+ ]),
7018
+ ]),
7019
+ // Error message
7020
+ file.uploadError && m('.file-error', file.uploadError),
5666
7021
  ]),
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
- ])
7022
+ // Remove button
7023
+ m('button.btn-flat.file-remove', {
7024
+ onclick: (e) => {
7025
+ e.stopPropagation();
7026
+ removeFile(file, attrs);
7027
+ },
7028
+ title: 'Remove file',
7029
+ }, [m('i.material-icons', 'close')]),
7030
+ ])),
7031
+ ]),
5682
7032
  ]);
5683
- }
7033
+ },
5684
7034
  };
5685
7035
  };
5686
7036
 
@@ -5710,7 +7060,7 @@ const Sidenav = () => {
5710
7060
  state = {
5711
7061
  id: attrs.id || uniqueId(),
5712
7062
  isOpen: attrs.isOpen || false,
5713
- isAnimating: false
7063
+ isAnimating: false,
5714
7064
  };
5715
7065
  // Set up keyboard listener
5716
7066
  if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
@@ -5739,34 +7089,33 @@ const Sidenav = () => {
5739
7089
  }
5740
7090
  },
5741
7091
  view: ({ attrs, children }) => {
5742
- const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false } = attrs;
7092
+ const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, } = attrs;
5743
7093
  const isOpen = state.isOpen;
5744
7094
  return [
5745
7095
  // 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
- }),
7096
+ showBackdrop &&
7097
+ mode === 'overlay' &&
7098
+ m('.sidenav-overlay', {
7099
+ style: {
7100
+ display: isOpen ? 'block' : 'none',
7101
+ opacity: isOpen ? '1' : '0',
7102
+ },
7103
+ onclick: () => handleBackdropClick(attrs),
7104
+ }),
5753
7105
  // Sidenav (using existing materialize structure)
5754
7106
  m('ul.sidenav', {
5755
7107
  id: state.id,
5756
- class: [
5757
- position === 'right' ? 'right-aligned' : '',
5758
- fixed ? 'sidenav-fixed' : '',
5759
- className
5760
- ].filter(Boolean).join(' '),
7108
+ class: [position === 'right' ? 'right-aligned' : '', fixed ? 'sidenav-fixed' : '', className]
7109
+ .filter(Boolean)
7110
+ .join(' ') || undefined,
5761
7111
  style: {
5762
7112
  width: `${width}px`,
5763
- transform: isOpen ? 'translateX(0)' :
5764
- position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
5765
- 'transition-duration': `${animationDuration}ms`
5766
- }
5767
- }, children)
7113
+ transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
7114
+ 'transition-duration': `${animationDuration}ms`,
7115
+ },
7116
+ }, children),
5768
7117
  ];
5769
- }
7118
+ },
5770
7119
  };
5771
7120
  };
5772
7121
  /**
@@ -5776,37 +7125,30 @@ const Sidenav = () => {
5776
7125
  const SidenavItem = () => {
5777
7126
  return {
5778
7127
  view: ({ attrs, children }) => {
5779
- const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false } = attrs;
7128
+ const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, } = attrs;
5780
7129
  if (divider) {
5781
7130
  return m('li.divider');
5782
7131
  }
5783
7132
  if (subheader) {
5784
7133
  return m('li.subheader', text || children);
5785
7134
  }
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
- ];
7135
+ const itemClasses = [active ? 'active' : '', disabled ? 'disabled' : '', className].filter(Boolean).join(' ') || undefined;
7136
+ const content = [icon && m('i.material-icons', icon), text || children];
5795
7137
  if (href && !disabled) {
5796
7138
  return m('li', { class: itemClasses }, [
5797
7139
  m('a', {
5798
7140
  href,
5799
- onclick: disabled ? undefined : onclick
5800
- }, content)
7141
+ onclick: disabled ? undefined : onclick,
7142
+ }, content),
5801
7143
  ]);
5802
7144
  }
5803
7145
  return m('li', { class: itemClasses }, [
5804
7146
  m('a', {
5805
7147
  onclick: disabled ? undefined : onclick,
5806
- href: '#!'
5807
- }, content)
7148
+ href: '#!',
7149
+ }, content),
5808
7150
  ]);
5809
- }
7151
+ },
5810
7152
  };
5811
7153
  };
5812
7154
  /**
@@ -5857,7 +7199,7 @@ class SidenavManager {
5857
7199
  const Breadcrumb = () => {
5858
7200
  return {
5859
7201
  view: ({ attrs }) => {
5860
- const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false } = attrs;
7202
+ const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false, } = attrs;
5861
7203
  if (items.length === 0) {
5862
7204
  return null;
5863
7205
  }
@@ -5866,52 +7208,46 @@ const Breadcrumb = () => {
5866
7208
  if (maxItems && items.length > maxItems) {
5867
7209
  const firstItem = items[0];
5868
7210
  const lastItems = items.slice(-(maxItems - 2));
5869
- displayItems = [
5870
- firstItem,
5871
- { text: '...', disabled: true, className: 'breadcrumb-ellipsis' },
5872
- ...lastItems
5873
- ];
7211
+ displayItems = [firstItem, { text: '...', disabled: true, className: 'breadcrumb-ellipsis' }, ...lastItems];
5874
7212
  }
5875
7213
  return m('nav.breadcrumb', { class: className }, [
5876
- m('ol.breadcrumb-list', displayItems.map((item, index) => {
7214
+ m('ol.breadcrumb-list', displayItems
7215
+ .map((item, index) => {
5877
7216
  const isLast = index === displayItems.length - 1;
5878
7217
  const isFirst = index === 0;
5879
7218
  return [
5880
7219
  // Breadcrumb item
5881
7220
  m('li.breadcrumb-item', {
5882
- class: [
5883
- item.active || isLast ? 'active' : '',
5884
- item.disabled ? 'disabled' : '',
5885
- item.className || ''
5886
- ].filter(Boolean).join(' ')
7221
+ class: [item.active || isLast ? 'active' : '', item.disabled ? 'disabled' : '', item.className || '']
7222
+ .filter(Boolean)
7223
+ .join(' ') || undefined,
5887
7224
  }, [
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
- ])
7225
+ item.href && !item.disabled && !isLast
7226
+ ? // Link item
7227
+ m('a.breadcrumb-link', {
7228
+ href: item.href,
7229
+ onclick: item.onclick,
7230
+ }, [
7231
+ showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
7232
+ showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
7233
+ m('span.breadcrumb-text', item.text),
7234
+ ])
7235
+ : // Text item (active or disabled)
7236
+ m('span.breadcrumb-text', {
7237
+ onclick: item.disabled ? undefined : item.onclick,
7238
+ }, [
7239
+ showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
7240
+ showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
7241
+ item.text,
7242
+ ]),
5906
7243
  ]),
5907
7244
  // Separator (except for last item)
5908
- !isLast && m('li.breadcrumb-separator', [
5909
- m('i.material-icons', separator)
5910
- ])
7245
+ !isLast && m('li.breadcrumb-separator', [m('i.material-icons', separator)]),
5911
7246
  ];
5912
- }).reduce((acc, val) => acc.concat(val), []))
7247
+ })
7248
+ .reduce((acc, val) => acc.concat(val), [])),
5913
7249
  ]);
5914
- }
7250
+ },
5915
7251
  };
5916
7252
  };
5917
7253
  /**
@@ -5924,7 +7260,7 @@ const createBreadcrumb = (path, basePath = '/') => {
5924
7260
  items.push({
5925
7261
  text: 'Home',
5926
7262
  href: basePath,
5927
- icon: 'home'
7263
+ icon: 'home',
5928
7264
  });
5929
7265
  // Add path segments
5930
7266
  let currentPath = basePath;
@@ -5934,7 +7270,7 @@ const createBreadcrumb = (path, basePath = '/') => {
5934
7270
  items.push({
5935
7271
  text: segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' '),
5936
7272
  href: isLast ? undefined : currentPath,
5937
- active: isLast
7273
+ active: isLast,
5938
7274
  });
5939
7275
  });
5940
7276
  return items;
@@ -5953,19 +7289,18 @@ class BreadcrumbManager {
5953
7289
  items.push({
5954
7290
  text: 'Home',
5955
7291
  href: '/',
5956
- icon: 'home'
7292
+ icon: 'home',
5957
7293
  });
5958
7294
  let currentPath = '';
5959
7295
  segments.forEach((segment, index) => {
5960
7296
  currentPath += '/' + segment;
5961
7297
  const isLast = index === segments.length - 1;
5962
7298
  // Use custom text from config or format segment
5963
- const text = routeConfig[currentPath] ||
5964
- segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
7299
+ const text = routeConfig[currentPath] || segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
5965
7300
  items.push({
5966
7301
  text,
5967
7302
  href: isLast ? undefined : currentPath,
5968
- active: isLast
7303
+ active: isLast,
5969
7304
  });
5970
7305
  });
5971
7306
  return items;
@@ -5977,7 +7312,7 @@ class BreadcrumbManager {
5977
7312
  return hierarchy.map((item, index) => ({
5978
7313
  text: item[textKey],
5979
7314
  href: index === hierarchy.length - 1 ? undefined : item[pathKey],
5980
- active: index === hierarchy.length - 1
7315
+ active: index === hierarchy.length - 1,
5981
7316
  }));
5982
7317
  }
5983
7318
  }
@@ -6111,7 +7446,7 @@ const Wizard = () => {
6111
7446
  hasError ? 'error' : '',
6112
7447
  step.disabled ? 'disabled' : '',
6113
7448
  step.optional ? 'optional' : ''
6114
- ].filter(Boolean).join(' '),
7449
+ ].filter(Boolean).join(' ') || undefined,
6115
7450
  onclick: allowHeaderNavigation && !step.disabled ?
6116
7451
  () => goToStep(index, attrs) : undefined
6117
7452
  }, [
@@ -6185,4 +7520,727 @@ const Stepper = () => {
6185
7520
  };
6186
7521
  };
6187
7522
 
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 };
7523
+ // Utility function to check if a node is the last in its branch
7524
+ const isNodeLastInBranch = (nodePath, rootNodes) => {
7525
+ // Navigate to the node's position and check if it's the last child at every level
7526
+ let currentNodes = rootNodes;
7527
+ for (let i = 0; i < nodePath.length; i++) {
7528
+ const index = nodePath[i];
7529
+ const isLastAtThisLevel = index === currentNodes.length - 1;
7530
+ // If this is not the last child at this level, then this node is not last in branch
7531
+ if (!isLastAtThisLevel) {
7532
+ return false;
7533
+ }
7534
+ // Move to the next level if it exists
7535
+ if (i < nodePath.length - 1) {
7536
+ const currentNode = currentNodes[index];
7537
+ if (currentNode.children) {
7538
+ currentNodes = currentNode.children;
7539
+ }
7540
+ }
7541
+ }
7542
+ return true;
7543
+ };
7544
+ const TreeNodeComponent = () => {
7545
+ return {
7546
+ view: ({ attrs }) => {
7547
+ const { node, level, isSelected, isExpanded, isFocused, showConnectors, iconType, selectionMode, onToggleExpand, onToggleSelect, onFocus, } = attrs;
7548
+ const hasChildren = node.children && node.children.length > 0;
7549
+ const indentLevel = level * 24; // 24px per level
7550
+ return m('li.tree-node', {
7551
+ class: [
7552
+ isSelected && 'selected',
7553
+ isFocused && 'focused',
7554
+ node.disabled && 'disabled',
7555
+ hasChildren && 'has-children',
7556
+ attrs.isLastInBranch && 'tree-last-in-branch',
7557
+ ]
7558
+ .filter(Boolean)
7559
+ .join(' ') || undefined,
7560
+ 'data-node-id': node.id,
7561
+ 'data-level': level,
7562
+ }, [
7563
+ // Node content
7564
+ m('.tree-node-content', {
7565
+ style: {
7566
+ paddingLeft: `${indentLevel}px`,
7567
+ },
7568
+ onclick: node.disabled
7569
+ ? undefined
7570
+ : () => {
7571
+ if (selectionMode !== 'none') {
7572
+ onToggleSelect(node.id);
7573
+ }
7574
+ onFocus(node.id);
7575
+ },
7576
+ onkeydown: (e) => {
7577
+ if (e.key === 'Enter' || e.key === ' ') {
7578
+ e.preventDefault();
7579
+ if (!node.disabled && selectionMode !== 'none') {
7580
+ onToggleSelect(node.id);
7581
+ }
7582
+ }
7583
+ },
7584
+ tabindex: node.disabled ? -1 : 0,
7585
+ role: selectionMode === 'multiple' ? 'option' : 'treeitem',
7586
+ 'aria-selected': selectionMode !== 'none' ? isSelected.toString() : undefined,
7587
+ 'aria-expanded': hasChildren ? isExpanded.toString() : undefined,
7588
+ 'aria-disabled': node.disabled ? 'true' : undefined,
7589
+ }, [
7590
+ // Connector lines
7591
+ showConnectors &&
7592
+ level > 0 &&
7593
+ m('.tree-connectors', Array.from({ length: level }, (_, i) => m('.tree-connector', {
7594
+ key: i,
7595
+ style: { left: `${i * 24 + 12}px` },
7596
+ }))),
7597
+ // Expand/collapse icon or spacer
7598
+ hasChildren
7599
+ ? m('.tree-expand-icon', {
7600
+ onclick: (e) => {
7601
+ e.stopPropagation();
7602
+ if (!node.disabled) {
7603
+ onToggleExpand(node.id);
7604
+ }
7605
+ },
7606
+ class: iconType,
7607
+ }, [
7608
+ iconType === 'plus-minus'
7609
+ ? m('span.tree-plus-minus', isExpanded ? '−' : '+')
7610
+ : iconType === 'triangle'
7611
+ ? m('span.tree-triangle', { class: isExpanded ? 'expanded' : undefined }, '▶')
7612
+ : iconType === 'chevron'
7613
+ ? m(MaterialIcon, {
7614
+ name: 'chevron',
7615
+ direction: isExpanded ? 'down' : 'right',
7616
+ class: 'tree-chevron-icon',
7617
+ })
7618
+ : m(MaterialIcon, {
7619
+ name: 'caret',
7620
+ direction: isExpanded ? 'down' : 'right',
7621
+ class: 'tree-caret-icon',
7622
+ }),
7623
+ ])
7624
+ : m('.tree-expand-spacer'), // Spacer for alignment
7625
+ // Selection indicator for multiple selection
7626
+ selectionMode === 'multiple' &&
7627
+ m('.tree-selection-indicator', [
7628
+ m('input[type=checkbox]', {
7629
+ checked: isSelected,
7630
+ disabled: node.disabled,
7631
+ onchange: () => {
7632
+ if (!node.disabled) {
7633
+ onToggleSelect(node.id);
7634
+ }
7635
+ },
7636
+ onclick: (e) => e.stopPropagation(),
7637
+ }),
7638
+ ]),
7639
+ // Node icon (optional)
7640
+ node.icon && m('i.tree-node-icon.material-icons', node.icon),
7641
+ // Node label
7642
+ m('span.tree-node-label', node.label),
7643
+ ]),
7644
+ // Children (recursive)
7645
+ hasChildren &&
7646
+ isExpanded &&
7647
+ m('ul.tree-children', {
7648
+ role: 'group',
7649
+ 'aria-expanded': 'true',
7650
+ }, node.children.map((child, childIndex) => {
7651
+ var _a, _b, _c, _d, _e, _f;
7652
+ // Calculate state for each child using treeState
7653
+ const childIsSelected = (_b = (_a = attrs.treeState) === null || _a === void 0 ? void 0 : _a.selectedIds.has(child.id)) !== null && _b !== void 0 ? _b : false;
7654
+ const childIsExpanded = (_d = (_c = attrs.treeState) === null || _c === void 0 ? void 0 : _c.expandedIds.has(child.id)) !== null && _d !== void 0 ? _d : false;
7655
+ const childIsFocused = ((_e = attrs.treeState) === null || _e === void 0 ? void 0 : _e.focusedNodeId) === child.id;
7656
+ // Calculate if this child is last in branch
7657
+ const childPath = [...(attrs.currentPath || []), childIndex];
7658
+ const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data) ?
7659
+ isNodeLastInBranch(childPath, attrs.treeAttrs.data) : false;
7660
+ return m(TreeNodeComponent, {
7661
+ key: child.id,
7662
+ node: child,
7663
+ level: level + 1,
7664
+ isSelected: childIsSelected,
7665
+ isExpanded: childIsExpanded,
7666
+ isFocused: childIsFocused,
7667
+ showConnectors,
7668
+ iconType,
7669
+ selectionMode,
7670
+ onToggleExpand,
7671
+ onToggleSelect,
7672
+ onFocus,
7673
+ isLastInBranch: childIsLastInBranch,
7674
+ currentPath: childPath,
7675
+ treeState: attrs.treeState,
7676
+ treeAttrs: attrs.treeAttrs,
7677
+ });
7678
+ })),
7679
+ ]);
7680
+ },
7681
+ };
7682
+ };
7683
+ const TreeView = () => {
7684
+ const state = {
7685
+ selectedIds: new Set(),
7686
+ expandedIds: new Set(),
7687
+ focusedNodeId: null,
7688
+ treeMap: new Map(),
7689
+ };
7690
+ const buildTreeMap = (nodes, map) => {
7691
+ nodes.forEach((node) => {
7692
+ map.set(node.id, node);
7693
+ if (node.children) {
7694
+ buildTreeMap(node.children, map);
7695
+ }
7696
+ });
7697
+ };
7698
+ const initializeExpandedNodes = (nodes) => {
7699
+ nodes.forEach((node) => {
7700
+ if (node.expanded) {
7701
+ state.expandedIds.add(node.id);
7702
+ }
7703
+ if (node.children) {
7704
+ initializeExpandedNodes(node.children);
7705
+ }
7706
+ });
7707
+ };
7708
+ const handleToggleExpand = (nodeId, attrs) => {
7709
+ var _a;
7710
+ const isExpanded = state.expandedIds.has(nodeId);
7711
+ if (isExpanded) {
7712
+ state.expandedIds.delete(nodeId);
7713
+ }
7714
+ else {
7715
+ state.expandedIds.add(nodeId);
7716
+ }
7717
+ (_a = attrs.onexpand) === null || _a === void 0 ? void 0 : _a.call(attrs, { nodeId, expanded: !isExpanded });
7718
+ };
7719
+ const handleToggleSelect = (nodeId, attrs) => {
7720
+ var _a;
7721
+ const { selectionMode = 'single' } = attrs;
7722
+ if (selectionMode === 'single') {
7723
+ state.selectedIds.clear();
7724
+ state.selectedIds.add(nodeId);
7725
+ }
7726
+ else if (selectionMode === 'multiple') {
7727
+ if (state.selectedIds.has(nodeId)) {
7728
+ state.selectedIds.delete(nodeId);
7729
+ }
7730
+ else {
7731
+ state.selectedIds.add(nodeId);
7732
+ }
7733
+ }
7734
+ (_a = attrs.onselection) === null || _a === void 0 ? void 0 : _a.call(attrs, Array.from(state.selectedIds));
7735
+ };
7736
+ const handleFocus = (nodeId) => {
7737
+ state.focusedNodeId = nodeId;
7738
+ };
7739
+ const renderNodes = (nodes, attrs, level = 0, parentPath = []) => {
7740
+ return nodes.map((node, index) => {
7741
+ var _a, _b, _c;
7742
+ const isSelected = state.selectedIds.has(node.id);
7743
+ const isExpanded = state.expandedIds.has(node.id);
7744
+ const isFocused = state.focusedNodeId === node.id;
7745
+ const currentPath = [...parentPath, index];
7746
+ const isLastInBranch = isNodeLastInBranch(currentPath, attrs.data);
7747
+ return m(TreeNodeComponent, {
7748
+ key: node.id,
7749
+ node,
7750
+ level,
7751
+ isSelected,
7752
+ isExpanded,
7753
+ isFocused,
7754
+ showConnectors: (_a = attrs.showConnectors) !== null && _a !== void 0 ? _a : true,
7755
+ iconType: (_b = attrs.iconType) !== null && _b !== void 0 ? _b : 'caret',
7756
+ selectionMode: (_c = attrs.selectionMode) !== null && _c !== void 0 ? _c : 'single',
7757
+ onToggleExpand: (nodeId) => handleToggleExpand(nodeId, attrs),
7758
+ onToggleSelect: (nodeId) => handleToggleSelect(nodeId, attrs),
7759
+ onFocus: handleFocus,
7760
+ isLastInBranch,
7761
+ currentPath,
7762
+ // Pass state and attrs for recursive rendering
7763
+ treeState: state,
7764
+ treeAttrs: attrs,
7765
+ });
7766
+ });
7767
+ };
7768
+ return {
7769
+ oninit: ({ attrs }) => {
7770
+ // Build internal tree map for efficient lookups
7771
+ buildTreeMap(attrs.data, state.treeMap);
7772
+ // Initialize expanded nodes from data
7773
+ initializeExpandedNodes(attrs.data);
7774
+ // Initialize selected nodes from props
7775
+ if (attrs.selectedIds) {
7776
+ state.selectedIds = new Set(attrs.selectedIds);
7777
+ }
7778
+ },
7779
+ onupdate: ({ attrs }) => {
7780
+ // Sync selectedIds prop with internal state
7781
+ if (attrs.selectedIds) {
7782
+ const newSelection = new Set(attrs.selectedIds);
7783
+ if (newSelection.size !== state.selectedIds.size ||
7784
+ !Array.from(newSelection).every((id) => state.selectedIds.has(id))) {
7785
+ state.selectedIds = newSelection;
7786
+ }
7787
+ }
7788
+ },
7789
+ view: ({ attrs }) => {
7790
+ const { data, className, style, id, selectionMode = 'single', showConnectors = true } = attrs;
7791
+ return m('div.tree-view', {
7792
+ class: [
7793
+ className,
7794
+ showConnectors && 'show-connectors'
7795
+ ].filter(Boolean).join(' ') || undefined,
7796
+ style,
7797
+ id,
7798
+ role: selectionMode === 'multiple' ? 'listbox' : 'tree',
7799
+ 'aria-multiselectable': selectionMode === 'multiple' ? 'true' : 'false',
7800
+ }, [
7801
+ m('ul.tree-root', {
7802
+ role: 'group',
7803
+ }, renderNodes(data, attrs)),
7804
+ ]);
7805
+ },
7806
+ };
7807
+ };
7808
+
7809
+ /**
7810
+ * Timeline Component
7811
+ * Displays a sequence of events in chronological order with connecting lines
7812
+ * Supports both vertical and horizontal orientations with Material Design styling
7813
+ */
7814
+ const Timeline = () => {
7815
+ const formatTimestamp = (timestamp) => {
7816
+ if (!timestamp)
7817
+ return '';
7818
+ if (typeof timestamp === 'string')
7819
+ return timestamp;
7820
+ return timestamp.toLocaleDateString();
7821
+ };
7822
+ const getColorClass = (color) => {
7823
+ switch (color) {
7824
+ case 'primary':
7825
+ return 'timeline-primary';
7826
+ case 'secondary':
7827
+ return 'timeline-secondary';
7828
+ case 'success':
7829
+ return 'timeline-success';
7830
+ case 'warning':
7831
+ return 'timeline-warning';
7832
+ case 'error':
7833
+ return 'timeline-error';
7834
+ case 'info':
7835
+ return 'timeline-info';
7836
+ default:
7837
+ return 'timeline-default';
7838
+ }
7839
+ };
7840
+ return {
7841
+ view: ({ attrs }) => {
7842
+ const { items = [], orientation = 'vertical', position = 'right', showConnector = true, className = '', showTimestamps = true, compact = false, } = attrs;
7843
+ const timelineClasses = [
7844
+ 'timeline',
7845
+ `timeline-${orientation}`,
7846
+ `timeline-${position}`,
7847
+ showConnector ? 'timeline-connector' : '',
7848
+ compact ? 'timeline-compact' : '',
7849
+ className,
7850
+ ]
7851
+ .filter(Boolean)
7852
+ .join(' ') || undefined;
7853
+ return m('div', { className: timelineClasses }, [
7854
+ items.map((item, index) => {
7855
+ const isAlternate = position === 'alternate';
7856
+ const itemPosition = isAlternate ? (index % 2 === 0 ? 'right' : 'left') : position;
7857
+ const itemClasses = [
7858
+ 'timeline-item',
7859
+ `timeline-item-${itemPosition}`,
7860
+ getColorClass(item.color),
7861
+ item.disabled ? 'timeline-item-disabled' : '',
7862
+ item.className || '',
7863
+ ]
7864
+ .filter(Boolean)
7865
+ .join(' ') || undefined;
7866
+ const handleItemClick = (e) => {
7867
+ if (item.disabled)
7868
+ return;
7869
+ if (item.onClick) {
7870
+ e.preventDefault();
7871
+ item.onClick(item, e);
7872
+ }
7873
+ };
7874
+ const isVertical = orientation === 'vertical';
7875
+ return m('div', {
7876
+ key: item.id || index,
7877
+ className: itemClasses,
7878
+ onclick: item.onClick ? handleItemClick : undefined,
7879
+ role: item.onClick ? 'button' : undefined,
7880
+ tabindex: item.onClick && !item.disabled ? 0 : undefined,
7881
+ 'aria-disabled': item.disabled ? 'true' : undefined,
7882
+ }, [
7883
+ // Timestamp (on opposite side of content from bullet)
7884
+ isVertical &&
7885
+ showTimestamps &&
7886
+ item.timestamp &&
7887
+ m(`.timeline-timestamp-separate${!item.icon ? '.timeline-dot-small' : ''}`, formatTimestamp(item.timestamp)),
7888
+ // Timeline separator containing dot and connector
7889
+ m('.timeline-separator', [
7890
+ // Timeline dot/icon
7891
+ m(`.timeline-dot${!item.icon ? '.timeline-dot-small' : ''}`, [
7892
+ item.icon ? m('i.material-icons.timeline-icon', item.icon) : m('.timeline-marker'),
7893
+ ]),
7894
+ // Timeline connector (only show if not the last item and connectors are enabled)
7895
+ showConnector && index < items.length - 1 && m('.timeline-connector'),
7896
+ ]),
7897
+ // Content container
7898
+ m('.timeline-content', [
7899
+ // Timestamp for horizontal layout or when not shown separately
7900
+ !isVertical &&
7901
+ showTimestamps &&
7902
+ item.timestamp &&
7903
+ m('.timeline-timestamp', formatTimestamp(item.timestamp)),
7904
+ // Main content
7905
+ item.content
7906
+ ? item.content
7907
+ : m('.timeline-text', [
7908
+ item.label && m('.timeline-label', item.label),
7909
+ item.description && m('.timeline-description', item.description),
7910
+ ]),
7911
+ ]),
7912
+ ]);
7913
+ }),
7914
+ ]);
7915
+ },
7916
+ };
7917
+ };
7918
+
7919
+ const Masonry = () => {
7920
+ let containerRef = null;
7921
+ const itemHeights = []; // measured heights
7922
+ let resizeObserver = null;
7923
+ const defaultBreakpoints = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5 };
7924
+ const getColumnsCount = (columns = 3) => {
7925
+ if (typeof columns === 'number')
7926
+ return columns;
7927
+ const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), columns);
7928
+ const width = window.innerWidth;
7929
+ if (width >= 1200)
7930
+ return breakpoints.xl || 5;
7931
+ if (width >= 992)
7932
+ return breakpoints.lg || 4;
7933
+ if (width >= 768)
7934
+ return breakpoints.md || 3;
7935
+ if (width >= 576)
7936
+ return breakpoints.sm || 2;
7937
+ return breakpoints.xs || 1;
7938
+ };
7939
+ const setupResizeObserver = () => {
7940
+ if (resizeObserver)
7941
+ return;
7942
+ if (typeof ResizeObserver === 'undefined')
7943
+ return;
7944
+ resizeObserver = new ResizeObserver((entries) => {
7945
+ for (const entry of entries) {
7946
+ const el = entry.target;
7947
+ const index = Number(el.dataset.index);
7948
+ if (!isNaN(index)) {
7949
+ const h = el.offsetHeight;
7950
+ if (itemHeights[index] !== h) {
7951
+ itemHeights[index] = h;
7952
+ m.redraw();
7953
+ }
7954
+ }
7955
+ }
7956
+ });
7957
+ };
7958
+ const cleanup = () => {
7959
+ if (resizeObserver) {
7960
+ resizeObserver.disconnect();
7961
+ resizeObserver = null;
7962
+ }
7963
+ };
7964
+ return {
7965
+ onremove: cleanup,
7966
+ view: ({ attrs, children }) => {
7967
+ const { columns = 3, spacing = 16, className = '', onItemClick, cssOnly = false, animationDelay } = attrs;
7968
+ const gap = typeof spacing === 'number' ? spacing : parseInt(spacing, 10) || 16;
7969
+ const columnsCount = typeof columns === 'number' ? columns : getColumnsCount(columns);
7970
+ const containerClasses = [
7971
+ 'masonry',
7972
+ cssOnly ? 'masonry-css' : 'masonry-js',
7973
+ animationDelay ? 'masonry-animated' : '',
7974
+ className,
7975
+ ]
7976
+ .filter(Boolean)
7977
+ .join(' ');
7978
+ const containerStyle = { position: 'relative' };
7979
+ // --- CSS-only fallback ---
7980
+ if (cssOnly) {
7981
+ containerStyle.display = 'grid';
7982
+ containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
7983
+ containerStyle.gap = `${gap}px`;
7984
+ return m('div', { className: containerClasses, style: containerStyle }, children);
7985
+ }
7986
+ // --- JS Masonry ---
7987
+ const containerWidth = (containerRef === null || containerRef === void 0 ? void 0 : containerRef.offsetWidth) || 800;
7988
+ const totalGapWidth = gap * (columnsCount - 1);
7989
+ const columnWidth = (containerWidth - totalGapWidth) / columnsCount;
7990
+ const columnHeights = new Array(columnsCount).fill(0);
7991
+ const positionedChildren = [];
7992
+ (Array.isArray(children) ? children : [children]).forEach((child, index) => {
7993
+ var _a;
7994
+ const shortestColumnIndex = columnHeights.indexOf(Math.min(...columnHeights));
7995
+ const x = shortestColumnIndex * (columnWidth + gap);
7996
+ const y = columnHeights[shortestColumnIndex];
7997
+ const itemHeight = (_a = itemHeights[index]) !== null && _a !== void 0 ? _a : 200; // fallback until measured
7998
+ columnHeights[shortestColumnIndex] += itemHeight + gap;
7999
+ const itemStyle = {
8000
+ position: 'absolute',
8001
+ left: `${x}px`,
8002
+ top: `${y}px`,
8003
+ width: `${columnWidth}px`,
8004
+ transition: 'all 0.3s ease',
8005
+ animationDelay: animationDelay ? `${index * animationDelay}ms` : undefined,
8006
+ };
8007
+ positionedChildren.push(m('div', {
8008
+ key: `masonry-item-${index}`,
8009
+ className: 'masonry-item',
8010
+ style: itemStyle,
8011
+ 'data-index': index,
8012
+ onclick: onItemClick ? (e) => onItemClick(index, e) : undefined,
8013
+ oncreate: ({ dom }) => {
8014
+ const el = dom;
8015
+ setupResizeObserver();
8016
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(el);
8017
+ const h = el.offsetHeight;
8018
+ if (itemHeights[index] !== h) {
8019
+ itemHeights[index] = h;
8020
+ m.redraw();
8021
+ }
8022
+ },
8023
+ onremove: ({ dom }) => {
8024
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.unobserve(dom);
8025
+ },
8026
+ }, child));
8027
+ });
8028
+ containerStyle.height = `${Math.max(...columnHeights) - gap}px`;
8029
+ return m('div', {
8030
+ className: containerClasses,
8031
+ style: containerStyle,
8032
+ oncreate: ({ dom }) => {
8033
+ containerRef = dom;
8034
+ },
8035
+ }, positionedChildren);
8036
+ },
8037
+ };
8038
+ };
8039
+
8040
+ /**
8041
+ * ImageList Component
8042
+ * Displays a collection of images in various grid layouts
8043
+ * Supports standard grid, quilted (varied sizes), masonry, and woven patterns
8044
+ */
8045
+ const ImageList = () => {
8046
+ const defaultBreakpoints = {
8047
+ xs: 1,
8048
+ sm: 2,
8049
+ md: 3,
8050
+ lg: 4,
8051
+ xl: 5
8052
+ };
8053
+ const getColumnsCount = (cols = 3) => {
8054
+ if (typeof cols === 'number')
8055
+ return cols;
8056
+ const breakpoints = Object.assign(Object.assign({}, defaultBreakpoints), cols);
8057
+ const width = typeof window !== 'undefined' ? window.innerWidth : 1024;
8058
+ if (width >= 1200)
8059
+ return breakpoints.xl || 5;
8060
+ if (width >= 992)
8061
+ return breakpoints.lg || 4;
8062
+ if (width >= 768)
8063
+ return breakpoints.md || 3;
8064
+ if (width >= 576)
8065
+ return breakpoints.sm || 2;
8066
+ return breakpoints.xs || 1;
8067
+ };
8068
+ const handleImageLoad = (e) => {
8069
+ const img = e.target;
8070
+ img.classList.add('loaded');
8071
+ };
8072
+ const handleImageError = (e) => {
8073
+ const img = e.target;
8074
+ img.classList.add('error');
8075
+ // Could set a placeholder image here
8076
+ img.alt = 'Failed to load image';
8077
+ };
8078
+ const renderImage = (item, index, options) => {
8079
+ const { src, alt = '', title, subtitle, onclick, actionButton, className = '', loading = options.loading || 'lazy', aspectRatio, cols = 1, rows = 1, featured = false } = item;
8080
+ const itemClasses = [
8081
+ 'image-list-item',
8082
+ featured ? 'image-list-item-featured' : '',
8083
+ onclick ? 'image-list-item-clickable' : '',
8084
+ className
8085
+ ].filter(Boolean).join(' ');
8086
+ const itemStyle = {};
8087
+ // Quilted layout with fixed alternating pattern
8088
+ if (options.variant === 'quilted') {
8089
+ if (featured || index % 7 === 0) {
8090
+ itemStyle.gridColumnEnd = 'span 2';
8091
+ itemStyle.gridRowEnd = 'span 2';
8092
+ }
8093
+ else if (index % 3 === 0) {
8094
+ itemStyle.gridColumnEnd = 'span 2';
8095
+ itemStyle.gridRowEnd = 'span 1';
8096
+ }
8097
+ else {
8098
+ itemStyle.gridColumnEnd = 'span 1';
8099
+ itemStyle.gridRowEnd = 'span 1';
8100
+ }
8101
+ }
8102
+ // Woven layout with varied sizes based on item properties
8103
+ if (options.variant === 'woven') {
8104
+ itemStyle.gridColumnEnd = `span ${cols}`;
8105
+ itemStyle.gridRowEnd = `span ${rows}`;
8106
+ }
8107
+ // Masonry layout - prevent break inside items
8108
+ if (options.variant === 'masonry') {
8109
+ itemStyle.breakInside = 'avoid';
8110
+ itemStyle.marginBottom = typeof options.gap === 'number' ? `${options.gap}px` : options.gap || '4px';
8111
+ itemStyle.display = 'inline-block';
8112
+ itemStyle.width = '100%';
8113
+ }
8114
+ // Custom aspect ratio
8115
+ if (aspectRatio && options.variant !== 'masonry') {
8116
+ itemStyle.aspectRatio = aspectRatio.toString();
8117
+ }
8118
+ const handleItemClick = (e) => {
8119
+ if (onclick) {
8120
+ e.preventDefault();
8121
+ onclick(item, e);
8122
+ }
8123
+ };
8124
+ const handleActionClick = (e) => {
8125
+ e.stopPropagation();
8126
+ if (actionButton === null || actionButton === void 0 ? void 0 : actionButton.onclick) {
8127
+ actionButton.onclick(item, e);
8128
+ }
8129
+ };
8130
+ return m(`.${itemClasses}`, {
8131
+ key: `image-${index}`,
8132
+ style: itemStyle,
8133
+ onclick: onclick ? handleItemClick : undefined,
8134
+ role: onclick ? 'button' : undefined,
8135
+ tabindex: onclick ? 0 : undefined,
8136
+ }, [
8137
+ m('.image-list-item-img', [
8138
+ m('img', {
8139
+ src,
8140
+ alt,
8141
+ loading,
8142
+ onload: handleImageLoad,
8143
+ onerror: handleImageError,
8144
+ draggable: false,
8145
+ }),
8146
+ // Loading placeholder
8147
+ m('.image-list-item-placeholder'),
8148
+ ]),
8149
+ // Title overlay
8150
+ (options.showTitles && (title || subtitle)) &&
8151
+ m('.image-list-item-bar', [
8152
+ m('.image-list-item-title-wrap', [
8153
+ title && m('.image-list-item-title', title),
8154
+ subtitle && m('.image-list-item-subtitle', subtitle)
8155
+ ])
8156
+ ]),
8157
+ // Action button
8158
+ (options.showActions && actionButton) &&
8159
+ m('button.image-list-item-action', {
8160
+ class: `image-list-action-${actionButton.position || 'top-right'}`,
8161
+ onclick: handleActionClick,
8162
+ 'aria-label': actionButton.ariaLabel || `Action for ${title || alt}`,
8163
+ }, [
8164
+ m('i.material-icons', actionButton.icon)
8165
+ ])
8166
+ ]);
8167
+ };
8168
+ return {
8169
+ view: ({ attrs }) => {
8170
+ const { items = [], variant = 'standard', cols = 3, gap = 4, rowHeight = 'auto', className = '', showTitles = false, showActions = false } = attrs;
8171
+ const columnsCount = getColumnsCount(cols);
8172
+ const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
8173
+ const containerClasses = [
8174
+ 'image-list',
8175
+ `image-list-${variant}`,
8176
+ showTitles ? 'image-list-with-titles' : '',
8177
+ showActions ? 'image-list-with-actions' : '',
8178
+ className
8179
+ ].filter(Boolean).join(' ');
8180
+ const containerStyle = {
8181
+ gap: gapValue
8182
+ };
8183
+ // Set up grid based on variant
8184
+ switch (variant) {
8185
+ case 'standard':
8186
+ containerStyle.display = 'grid';
8187
+ containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
8188
+ if (rowHeight !== 'auto') {
8189
+ containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight;
8190
+ }
8191
+ break;
8192
+ case 'quilted':
8193
+ // Fixed pattern like woven
8194
+ containerStyle.display = 'grid';
8195
+ containerStyle.gridTemplateColumns = `repeat(${Math.max(columnsCount * 2, 4)}, 1fr)`;
8196
+ containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '150px';
8197
+ containerStyle.gridAutoFlow = 'dense';
8198
+ break;
8199
+ case 'masonry':
8200
+ // Use CSS columns for masonry effect
8201
+ containerStyle.display = 'block';
8202
+ containerStyle.columnCount = columnsCount;
8203
+ containerStyle.columnGap = gapValue;
8204
+ containerStyle.columnFill = 'balance';
8205
+ containerStyle.orphans = 1;
8206
+ containerStyle.widows = 1;
8207
+ break;
8208
+ case 'woven':
8209
+ // Varied sizes based on item cols/rows
8210
+ containerStyle.display = 'grid';
8211
+ containerStyle.gridTemplateColumns = `repeat(${columnsCount}, 1fr)`;
8212
+ containerStyle.gridAutoRows = typeof rowHeight === 'number' ? `${rowHeight}px` : '200px';
8213
+ containerStyle.gridAutoFlow = 'dense';
8214
+ break;
8215
+ }
8216
+ return m(`.${containerClasses}`, {
8217
+ style: containerStyle
8218
+ }, [
8219
+ items.map((item, index) => renderImage(item, index, attrs))
8220
+ ]);
8221
+ }
8222
+ };
8223
+ };
8224
+
8225
+ /**
8226
+ * @fileoverview Core TypeScript utility types for mithril-materialized library
8227
+ * These types improve type safety and developer experience across all components
8228
+ */
8229
+ /**
8230
+ * Type guard to check if validation result indicates success
8231
+ * @param result - The validation result to check
8232
+ * @returns True if validation passed
8233
+ */
8234
+ const isValidationSuccess = (result) => result === true || result === '';
8235
+ /**
8236
+ * Type guard to check if validation result indicates an error
8237
+ * @param result - The validation result to check
8238
+ * @returns True if validation failed
8239
+ */
8240
+ const isValidationError = (result) => !isValidationSuccess(result);
8241
+ // ============================================================================
8242
+ // EXPORTS
8243
+ // ============================================================================
8244
+ // All types are already exported via individual export declarations above
8245
+
8246
+ export { AnchorItem, Autocomplete, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, DataTable, DatePicker, DoubleRangeSlider, Dropdown, EmailInput, FileInput, FileUpload, FlatButton, FloatingActionButton, HelperText, Icon, ImageList, InputCheckbox, Label, LargeButton, ListItem, Mandatory, Masonry, MaterialBox, MaterialIcon, ModalPanel, NumberInput, Options, Pagination, PaginationControls, Parallax, PasswordInput, Pushpin, PushpinComponent, RadioButton, RadioButtons, RangeInput, RoundIconButton, SearchSelect, SecondaryContent, Select, Sidenav, SidenavItem, SidenavManager, SingleRangeSlider, SmallButton, Stepper, SubmitButton, Switch, Tabs, TextArea, TextInput, ThemeManager, ThemeSwitcher, ThemeToggle, TimePicker, Timeline, Toast, ToastComponent, Tooltip, TooltipComponent, TreeView, UrlInput, Wizard, createBreadcrumb, getDropdownStyles, initPushpins, initTooltips, isNumeric, isValidationError, isValidationSuccess, padLeft, range, toast, uniqueId, uuid4 };