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

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