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