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