mithril-materialized 2.0.0-beta.10 → 2.0.0-beta.12

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.
package/dist/index.js CHANGED
@@ -418,12 +418,18 @@ const ButtonFactory = (element, defaultClassNames, type = '') => {
418
418
  return () => {
419
419
  return {
420
420
  view: ({ attrs }) => {
421
- const { modalId, tooltip, tooltipPostion, iconName, iconClass, label, className, attr } = attrs, params = __rest(attrs, ["modalId", "tooltip", "tooltipPostion", "iconName", "iconClass", "label", "className", "attr"]);
422
- const cn = [modalId ? 'modal-trigger' : '', tooltip ? 'tooltipped' : '', defaultClassNames, className]
421
+ const { modalId, tooltip, tooltipPosition, tooltipPostion, // Keep for backwards compatibility
422
+ iconName, iconClass, label, className, attr, variant } = attrs, params = __rest(attrs, ["modalId", "tooltip", "tooltipPosition", "tooltipPostion", "iconName", "iconClass", "label", "className", "attr", "variant"]);
423
+ // Handle both new variant prop and legacy modalId/type
424
+ const buttonType = (variant === null || variant === void 0 ? void 0 : variant.type) || (modalId ? 'modal' : type || 'button');
425
+ const modalTarget = (variant === null || variant === void 0 ? void 0 : variant.type) === 'modal' ? variant.modalId : modalId;
426
+ const cn = [modalTarget ? 'modal-trigger' : '', tooltip ? 'tooltipped' : '', defaultClassNames, className]
423
427
  .filter(Boolean)
424
428
  .join(' ')
425
429
  .trim();
426
- return m(element, Object.assign(Object.assign(Object.assign({}, params), attr), { className: cn, href: modalId ? `#${modalId}` : undefined, 'data-position': tooltip ? tooltipPostion || 'top' : undefined, 'data-tooltip': tooltip || undefined, type }),
430
+ // Use tooltipPosition if available, fallback to legacy tooltipPostion
431
+ const position = tooltipPosition || tooltipPostion || 'top';
432
+ return m(element, Object.assign(Object.assign(Object.assign({}, params), attr), { className: cn, href: modalTarget ? `#${modalTarget}` : undefined, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType === 'modal' ? 'button' : buttonType }),
427
433
  // `${dca}${modalId ? `.modal-trigger[href=#${modalId}]` : ''}${
428
434
  // tooltip ? `.tooltipped[data-position=${tooltipPostion || 'top'}][data-tooltip=${tooltip}]` : ''
429
435
  // }${toAttributeString(attr)}`, {}
@@ -832,6 +838,18 @@ const iconPaths = {
832
838
  'M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7A1 1 0 0 0 5.7 7.11L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z',
833
839
  'M0 0h24v24H0z',
834
840
  ],
841
+ chevron: [
842
+ 'M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z', // chevron down
843
+ 'M0 0h24v24H0z', // background
844
+ ],
845
+ expand: [
846
+ 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z', // plus
847
+ 'M0 0h24v24H0z', // background
848
+ ],
849
+ collapse: [
850
+ 'M19 13H5v-2h14v2z', // minus
851
+ 'M0 0h24v24H0z', // background
852
+ ],
835
853
  };
836
854
  const MaterialIcon = () => {
837
855
  return {
@@ -841,8 +859,8 @@ const MaterialIcon = () => {
841
859
  const rotationMap = {
842
860
  down: 0,
843
861
  up: 180,
844
- left: -90,
845
- right: 90,
862
+ left: 90,
863
+ right: -90,
846
864
  };
847
865
  const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
848
866
  const transform = rotation ? `rotate(${rotation}deg)` : undefined;
@@ -1151,7 +1169,7 @@ const CodeBlock = () => ({
1151
1169
  const lang = language || 'lang-TypeScript';
1152
1170
  const label = lang.replace('lang-', '');
1153
1171
  const cb = code instanceof Array ? code.join('\n') : code;
1154
- const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim();
1172
+ const cn = [newRow ? 'clear' : '', lang, className].filter(Boolean).join(' ').trim() || undefined;
1155
1173
  return m(`pre.codeblock${newRow ? '.clear' : ''}`, attrs, [
1156
1174
  m('div', m('label', label)),
1157
1175
  m('code', Object.assign(Object.assign({}, params), { className: cn }), cb),
@@ -2037,248 +2055,6 @@ const DatePicker = () => {
2037
2055
  };
2038
2056
  };
2039
2057
 
2040
- /** Pure TypeScript Dropdown component - no Materialize dependencies */
2041
- const Dropdown = () => {
2042
- const state = {
2043
- isOpen: false,
2044
- initialValue: undefined,
2045
- id: '',
2046
- focusedIndex: -1,
2047
- inputRef: null,
2048
- dropdownRef: null,
2049
- };
2050
- const handleKeyDown = (e, items, onchange) => {
2051
- const availableItems = items.filter((item) => !item.divider && !item.disabled);
2052
- switch (e.key) {
2053
- case 'ArrowDown':
2054
- e.preventDefault();
2055
- if (!state.isOpen) {
2056
- state.isOpen = true;
2057
- state.focusedIndex = 0;
2058
- }
2059
- else {
2060
- state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
2061
- }
2062
- break;
2063
- case 'ArrowUp':
2064
- e.preventDefault();
2065
- if (state.isOpen) {
2066
- state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
2067
- }
2068
- break;
2069
- case 'Enter':
2070
- case ' ':
2071
- e.preventDefault();
2072
- if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
2073
- const selectedItem = availableItems[state.focusedIndex];
2074
- const value = (selectedItem.id || selectedItem.label);
2075
- state.initialValue = value;
2076
- state.isOpen = false;
2077
- state.focusedIndex = -1;
2078
- if (onchange)
2079
- onchange(value);
2080
- }
2081
- else if (!state.isOpen) {
2082
- state.isOpen = true;
2083
- state.focusedIndex = 0;
2084
- }
2085
- break;
2086
- case 'Escape':
2087
- e.preventDefault();
2088
- state.isOpen = false;
2089
- state.focusedIndex = -1;
2090
- break;
2091
- }
2092
- };
2093
- return {
2094
- oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
2095
- state.id = id;
2096
- state.initialValue = initialValue || checkedId;
2097
- // Mithril will handle click events through the component structure
2098
- },
2099
- view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
2100
- const { initialValue } = state;
2101
- const selectedItem = initialValue
2102
- ? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
2103
- : undefined;
2104
- const title = selectedItem ? selectedItem.label : label || 'Select';
2105
- const availableItems = items.filter((item) => !item.divider && !item.disabled);
2106
- return m('.dropdown-wrapper.input-field', { className, key, style }, [
2107
- iconName ? m('i.material-icons.prefix', iconName) : undefined,
2108
- m(HelperText, { helperText }),
2109
- m('.select-wrapper', {
2110
- onclick: disabled
2111
- ? undefined
2112
- : () => {
2113
- state.isOpen = !state.isOpen;
2114
- state.focusedIndex = state.isOpen ? 0 : -1;
2115
- },
2116
- onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
2117
- tabindex: disabled ? -1 : 0,
2118
- 'aria-expanded': state.isOpen ? 'true' : 'false',
2119
- 'aria-haspopup': 'listbox',
2120
- role: 'combobox',
2121
- }, [
2122
- m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
2123
- id: state.id,
2124
- value: title,
2125
- oncreate: ({ dom }) => {
2126
- state.inputRef = dom;
2127
- },
2128
- onclick: (e) => {
2129
- e.preventDefault();
2130
- e.stopPropagation();
2131
- if (!disabled) {
2132
- state.isOpen = !state.isOpen;
2133
- state.focusedIndex = state.isOpen ? 0 : -1;
2134
- }
2135
- },
2136
- }),
2137
- // Dropdown Menu using Select component's positioning logic
2138
- state.isOpen &&
2139
- m('ul.dropdown-content.select-dropdown', {
2140
- tabindex: 0,
2141
- role: 'listbox',
2142
- 'aria-labelledby': state.id,
2143
- oncreate: ({ dom }) => {
2144
- state.dropdownRef = dom;
2145
- },
2146
- onremove: () => {
2147
- state.dropdownRef = null;
2148
- },
2149
- style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
2150
- // Convert dropdown items to format expected by getDropdownStyles
2151
- group: undefined }))), true),
2152
- }, items.map((item, index) => {
2153
- if (item.divider) {
2154
- return m('li.divider', {
2155
- key: `divider-${index}`,
2156
- });
2157
- }
2158
- const itemIndex = availableItems.indexOf(item);
2159
- const isFocused = itemIndex === state.focusedIndex;
2160
- return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
2161
- item.disabled ? 'disabled' : '',
2162
- isFocused ? 'focused' : '',
2163
- (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
2164
- ]
2165
- .filter(Boolean)
2166
- .join(' ') }, (item.disabled
2167
- ? {}
2168
- : {
2169
- onclick: (e) => {
2170
- e.stopPropagation();
2171
- const value = (item.id || item.label);
2172
- state.initialValue = value;
2173
- state.isOpen = false;
2174
- state.focusedIndex = -1;
2175
- if (onchange)
2176
- onchange(value);
2177
- },
2178
- })), m('span', {
2179
- style: {
2180
- display: 'flex',
2181
- alignItems: 'center',
2182
- padding: '14px 16px',
2183
- },
2184
- }, [
2185
- item.iconName
2186
- ? m('i.material-icons', {
2187
- style: { marginRight: '32px' },
2188
- }, item.iconName)
2189
- : undefined,
2190
- item.label,
2191
- ]));
2192
- })),
2193
- m(MaterialIcon, {
2194
- name: 'caret',
2195
- direction: 'down',
2196
- class: 'caret',
2197
- }),
2198
- ]),
2199
- ]);
2200
- },
2201
- };
2202
- };
2203
-
2204
- /**
2205
- * Floating Action Button
2206
- */
2207
- const FloatingActionButton = () => {
2208
- const state = {
2209
- isOpen: false,
2210
- };
2211
- const handleClickOutside = (e) => {
2212
- const target = e.target;
2213
- if (!target.closest('.fixed-action-btn')) {
2214
- state.isOpen = false;
2215
- }
2216
- };
2217
- return {
2218
- oncreate: () => {
2219
- document.addEventListener('click', handleClickOutside);
2220
- },
2221
- onremove: () => {
2222
- document.removeEventListener('click', handleClickOutside);
2223
- },
2224
- view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
2225
- ? 'position: absolute; display: inline-block; left: 24px;'
2226
- : position === 'right' || position === 'inline-right'
2227
- ? 'position: absolute; display: inline-block; right: 24px;'
2228
- : undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
2229
- const fabClasses = [
2230
- 'fixed-action-btn',
2231
- direction ? `direction-${direction}` : '',
2232
- state.isOpen ? 'active' : '',
2233
- // hoverEnabled ? 'hover-enabled' : '',
2234
- ]
2235
- .filter(Boolean)
2236
- .join(' ');
2237
- return m('div', {
2238
- style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
2239
- }, m(`.${fabClasses}`, {
2240
- style,
2241
- onclick: (e) => {
2242
- e.stopPropagation();
2243
- if (buttons && buttons.length > 0) {
2244
- state.isOpen = !state.isOpen;
2245
- }
2246
- },
2247
- onmouseover: hoverEnabled
2248
- ? () => {
2249
- if (buttons && buttons.length > 0) {
2250
- state.isOpen = true;
2251
- }
2252
- }
2253
- : undefined,
2254
- onmouseleave: hoverEnabled
2255
- ? () => {
2256
- state.isOpen = false;
2257
- }
2258
- : undefined,
2259
- }, [
2260
- m('a.btn-floating.btn-large', {
2261
- className,
2262
- }, m('i.material-icons', { className: iconClass }, iconName)),
2263
- buttons &&
2264
- buttons.length > 0 &&
2265
- m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
2266
- style: {
2267
- opacity: state.isOpen ? '1' : '0',
2268
- transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
2269
- transition: `all 0.3s ease ${index * 40}ms`,
2270
- },
2271
- onclick: (e) => {
2272
- e.stopPropagation();
2273
- if (button.onClick)
2274
- button.onClick(e);
2275
- },
2276
- }, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
2277
- ]));
2278
- },
2279
- };
2280
- };
2281
-
2282
2058
  /** Character counter component that tracks text length against maxLength */
2283
2059
  const CharacterCounter = () => {
2284
2060
  return {
@@ -2446,13 +2222,14 @@ const InputField = (type, defaultClass = '') => () => {
2446
2222
  var _a;
2447
2223
  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"]);
2448
2224
  // const attributes = toAttrs(params);
2449
- const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim();
2225
+ const cn = [newRow ? 'clear' : '', defaultClass, className].filter(Boolean).join(' ').trim() || undefined;
2450
2226
  const isActive = state.active || ((_a = state.inputElement) === null || _a === void 0 ? void 0 : _a.value) || placeholder || type === 'color' || type === 'range'
2451
2227
  ? true
2452
2228
  : false;
2453
2229
  return m('.input-field', { className: cn, style }, [
2454
2230
  iconName ? m('i.material-icons.prefix', iconName) : undefined,
2455
- m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
2231
+ m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
2232
+ placeholder,
2456
2233
  // attributes,
2457
2234
  oncreate: ({ dom }) => {
2458
2235
  const input = (state.inputElement = dom);
@@ -2656,11 +2433,938 @@ const FileInput = () => {
2656
2433
  i.value = '';
2657
2434
  onchange && onchange({});
2658
2435
  },
2659
- }, m(MaterialIcon, {
2660
- name: 'close',
2661
- className: 'close',
2662
- })),
2663
- ]);
2436
+ }, m(MaterialIcon, {
2437
+ name: 'close',
2438
+ className: 'close',
2439
+ })),
2440
+ ]);
2441
+ },
2442
+ };
2443
+ };
2444
+
2445
+ /** Component to show a check box */
2446
+ const InputCheckbox = () => {
2447
+ return {
2448
+ view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
2449
+ const checkboxId = inputId || uniqueId();
2450
+ return m(`p`, { className, style }, m('label', { for: checkboxId }, [
2451
+ m('input[type=checkbox][tabindex=0]', {
2452
+ id: checkboxId,
2453
+ checked,
2454
+ disabled,
2455
+ onclick: onchange
2456
+ ? (e) => {
2457
+ if (e.target && typeof e.target.checked !== 'undefined') {
2458
+ onchange(e.target.checked);
2459
+ }
2460
+ }
2461
+ : undefined,
2462
+ }),
2463
+ label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
2464
+ ]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
2465
+ },
2466
+ };
2467
+ };
2468
+ /** A list of checkboxes */
2469
+ const Options = () => {
2470
+ const state = {};
2471
+ const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
2472
+ const selectAll = (options, callback) => {
2473
+ const allIds = options.map((option) => option.id);
2474
+ state.checkedIds = [...allIds];
2475
+ if (callback)
2476
+ callback(allIds);
2477
+ };
2478
+ const selectNone = (callback) => {
2479
+ state.checkedIds = [];
2480
+ if (callback)
2481
+ callback([]);
2482
+ };
2483
+ return {
2484
+ oninit: ({ attrs: { initialValue, checkedId, id } }) => {
2485
+ const iv = checkedId || initialValue;
2486
+ state.checkedId = checkedId;
2487
+ state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
2488
+ state.componentId = id || uniqueId();
2489
+ },
2490
+ view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
2491
+ const onchange = callback
2492
+ ? (propId, checked) => {
2493
+ const checkedIds = state.checkedIds.filter((i) => i !== propId);
2494
+ if (checked) {
2495
+ checkedIds.push(propId);
2496
+ }
2497
+ state.checkedIds = checkedIds;
2498
+ callback(checkedIds);
2499
+ }
2500
+ : undefined;
2501
+ const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
2502
+ const optionsContent = layout === 'horizontal'
2503
+ ? m('div.grid-container', options.map((option) => m(InputCheckbox, {
2504
+ disabled: disabled || option.disabled,
2505
+ label: option.label,
2506
+ onchange: onchange ? (v) => onchange(option.id, v) : undefined,
2507
+ className: option.className || checkboxClass,
2508
+ checked: isChecked(option.id),
2509
+ description: option.description,
2510
+ inputId: `${state.componentId}-${option.id}`,
2511
+ })))
2512
+ : options.map((option) => m(InputCheckbox, {
2513
+ disabled: disabled || option.disabled,
2514
+ label: option.label,
2515
+ onchange: onchange ? (v) => onchange(option.id, v) : undefined,
2516
+ className: option.className || checkboxClass,
2517
+ checked: isChecked(option.id),
2518
+ description: option.description,
2519
+ inputId: `${state.componentId}-${option.id}`,
2520
+ }));
2521
+ return m('div', { id: state.componentId, className: cn, style }, [
2522
+ label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
2523
+ showSelectAll &&
2524
+ m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
2525
+ m('a', {
2526
+ href: '#',
2527
+ onclick: (e) => {
2528
+ e.preventDefault();
2529
+ selectAll(options, callback);
2530
+ },
2531
+ style: 'margin-right: 15px;',
2532
+ }, 'Select All'),
2533
+ m('a', {
2534
+ href: '#',
2535
+ onclick: (e) => {
2536
+ e.preventDefault();
2537
+ selectNone(callback);
2538
+ },
2539
+ }, 'Select None'),
2540
+ ]),
2541
+ description && m(HelperText, { helperText: description }),
2542
+ m('form', { action: '#' }, optionsContent),
2543
+ ]);
2544
+ },
2545
+ };
2546
+ };
2547
+
2548
+ const GlobalSearch = () => {
2549
+ return {
2550
+ view: ({ attrs }) => {
2551
+ const { searchPlaceholder, onSearch, i18n } = attrs;
2552
+ return m('.datatable-global-search', m(TextInput, {
2553
+ className: 'datatable-search',
2554
+ label: (i18n === null || i18n === void 0 ? void 0 : i18n.search) || 'Search',
2555
+ placeholder: searchPlaceholder || (i18n === null || i18n === void 0 ? void 0 : i18n.searchPlaceholder) || 'Search table...',
2556
+ oninput: onSearch,
2557
+ }));
2558
+ },
2559
+ };
2560
+ };
2561
+ const TableHeader = () => {
2562
+ return {
2563
+ view: ({ attrs }) => {
2564
+ const { columns, selection, sort, allSelected, helpers } = attrs;
2565
+ return m('thead', m('tr', [
2566
+ // Selection column header
2567
+ ...(selection && selection.mode !== 'none'
2568
+ ? [
2569
+ m('th.selection-checkbox', [
2570
+ selection.mode === 'multiple' &&
2571
+ m(InputCheckbox, {
2572
+ checked: allSelected,
2573
+ onchange: helpers.handleSelectAll,
2574
+ className: '',
2575
+ }),
2576
+ ]),
2577
+ ]
2578
+ : []),
2579
+ // Regular columns
2580
+ ...columns.map((column) => {
2581
+ const isSorted = (sort === null || sort === void 0 ? void 0 : sort.column) === column.key;
2582
+ const sortDirection = isSorted ? sort.direction : null;
2583
+ return m('th', {
2584
+ class: [
2585
+ column.headerClassName,
2586
+ column.sortable ? 'sortable' : '',
2587
+ isSorted ? `sorted-${sortDirection}` : '',
2588
+ ]
2589
+ .filter(Boolean)
2590
+ .join(' '),
2591
+ style: column.width ? { width: column.width } : undefined,
2592
+ onclick: column.sortable ? () => helpers.handleSort(column.key) : undefined,
2593
+ }, [
2594
+ column.title,
2595
+ column.sortable &&
2596
+ m('.sort-indicators', [
2597
+ m('span.sort-icon.sort-asc', {
2598
+ className: isSorted && sortDirection === 'asc' ? 'active' : '',
2599
+ }, '▲'),
2600
+ m('span.sort-icon.sort-desc', {
2601
+ className: isSorted && sortDirection === 'desc' ? 'active' : '',
2602
+ }, '▼'),
2603
+ ]),
2604
+ ]);
2605
+ }),
2606
+ ]));
2607
+ },
2608
+ };
2609
+ };
2610
+ const TableRow = () => {
2611
+ return {
2612
+ view: ({ attrs }) => {
2613
+ const { row, index, columns, selection, onRowClick, onRowDoubleClick, getRowClassName, helpers, data } = attrs;
2614
+ // Calculate the original data index for the row key
2615
+ const originalIndex = data.findIndex((originalRow) => originalRow === row);
2616
+ const rowKey = (selection === null || selection === void 0 ? void 0 : selection.getRowKey(row, originalIndex)) || String(originalIndex);
2617
+ const isSelected = (selection === null || selection === void 0 ? void 0 : selection.selectedKeys.includes(rowKey)) || false;
2618
+ return m('tr', {
2619
+ class: [getRowClassName ? getRowClassName(row, index) : '', isSelected ? 'selected' : '']
2620
+ .filter(Boolean)
2621
+ .join(' ') || undefined,
2622
+ onclick: onRowClick ? (e) => onRowClick(row, index, e) : undefined,
2623
+ ondblclick: onRowDoubleClick ? (e) => onRowDoubleClick(row, index, e) : undefined,
2624
+ }, [
2625
+ // Selection column
2626
+ selection &&
2627
+ selection.mode !== 'none' &&
2628
+ m('td.selection-checkbox', [
2629
+ m(InputCheckbox, {
2630
+ checked: isSelected,
2631
+ onchange: (checked) => helpers.handleSelectionChange(rowKey, checked),
2632
+ className: '',
2633
+ }),
2634
+ ]),
2635
+ columns.map((column) => {
2636
+ const value = helpers.getCellValue(row, column);
2637
+ let cellContent;
2638
+ if (column.cellRenderer) {
2639
+ cellContent = m(column.cellRenderer, {
2640
+ value,
2641
+ row,
2642
+ index,
2643
+ column,
2644
+ });
2645
+ }
2646
+ else if (column.render) {
2647
+ // Backward compatibility with deprecated render function
2648
+ cellContent = column.render(value, row, index);
2649
+ }
2650
+ else {
2651
+ cellContent = String(value || '');
2652
+ }
2653
+ return m('td', {
2654
+ class: [column.className, column.align ? `align-${column.align}` : ''].filter(Boolean).join(' ') ||
2655
+ undefined,
2656
+ }, cellContent);
2657
+ }),
2658
+ ]);
2659
+ },
2660
+ };
2661
+ };
2662
+ /**
2663
+ * Standalone Pagination Controls component
2664
+ *
2665
+ * Provides navigation controls for paginated data with customizable text labels.
2666
+ * Includes first page, previous page, next page, last page buttons and page info display.
2667
+ * Can be used independently of DataTable for any paginated content.
2668
+ *
2669
+ * @example
2670
+ * ```typescript
2671
+ * m(PaginationControls, {
2672
+ * pagination: { page: 0, pageSize: 10, total: 100 },
2673
+ * onPaginationChange: (newPagination) => console.log('Page changed:', newPagination),
2674
+ * i18n: { showing: 'Showing', to: 'to', of: 'of', entries: 'entries', page: 'Page' }
2675
+ * })
2676
+ * ```
2677
+ */
2678
+ const PaginationControls = () => {
2679
+ return {
2680
+ view: ({ attrs }) => {
2681
+ const { pagination, onPaginationChange, i18n } = attrs;
2682
+ if (!pagination)
2683
+ return null;
2684
+ const { page, pageSize, total } = pagination;
2685
+ const totalPages = Math.ceil(total / pageSize);
2686
+ const startItem = page * pageSize + 1;
2687
+ const endItem = Math.min((page + 1) * pageSize, total);
2688
+ const showingText = (i18n === null || i18n === void 0 ? void 0 : i18n.showing) || 'Showing';
2689
+ const toText = (i18n === null || i18n === void 0 ? void 0 : i18n.to) || 'to';
2690
+ const ofText = (i18n === null || i18n === void 0 ? void 0 : i18n.of) || 'of';
2691
+ const entriesText = (i18n === null || i18n === void 0 ? void 0 : i18n.entries) || 'entries';
2692
+ const pageText = (i18n === null || i18n === void 0 ? void 0 : i18n.page) || 'Page';
2693
+ return m('.datatable-pagination', [
2694
+ m('.pagination-info', `${showingText} ${startItem} ${toText} ${endItem} ${ofText} ${total} ${entriesText}`),
2695
+ m('.pagination-controls', [
2696
+ m('button.btn-flat', {
2697
+ disabled: page === 0,
2698
+ onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: 0 })),
2699
+ }, '⏮'),
2700
+ m('button.btn-flat', {
2701
+ disabled: page === 0,
2702
+ onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: page - 1 })),
2703
+ }, '◀'),
2704
+ m('span.page-info', `${pageText} ${page + 1} ${ofText} ${totalPages}`),
2705
+ m('button.btn-flat', {
2706
+ disabled: page >= totalPages - 1,
2707
+ onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: page + 1 })),
2708
+ }, '▶'),
2709
+ m('button.btn-flat', {
2710
+ disabled: page >= totalPages - 1,
2711
+ onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: totalPages - 1 })),
2712
+ }, '⏭'),
2713
+ ]),
2714
+ ]);
2715
+ },
2716
+ };
2717
+ };
2718
+ const TableContent = () => {
2719
+ return {
2720
+ view: ({ attrs: contentAttrs }) => {
2721
+ const { processedData, tableClasses, columns, selection, internalSort, allSelected, someSelected, helpers, onRowClick, onRowDoubleClick, getRowClassName, data, } = contentAttrs;
2722
+ return m('table', {
2723
+ class: tableClasses,
2724
+ }, m(TableHeader(), {
2725
+ columns,
2726
+ selection,
2727
+ sort: internalSort,
2728
+ allSelected,
2729
+ someSelected,
2730
+ helpers,
2731
+ }), m('tbody', processedData.map((row, index) => m(TableRow(), {
2732
+ key: (selection === null || selection === void 0 ? void 0 : selection.getRowKey(row, data.findIndex((originalRow) => originalRow === row))) || index,
2733
+ row,
2734
+ index,
2735
+ columns,
2736
+ selection,
2737
+ onRowClick,
2738
+ onRowDoubleClick,
2739
+ getRowClassName,
2740
+ helpers,
2741
+ data,
2742
+ }))));
2743
+ },
2744
+ };
2745
+ };
2746
+ /**
2747
+ * A comprehensive data table component with sorting, filtering, pagination, and selection capabilities.
2748
+ *
2749
+ * @template T The type of data objects displayed in each row
2750
+ *
2751
+ * @description
2752
+ * The DataTable component provides a feature-rich interface for displaying and interacting with tabular data.
2753
+ * It supports both controlled and uncontrolled modes for all interactive features.
2754
+ *
2755
+ * **Key Features:**
2756
+ * - Sorting: Click column headers to sort data ascending/descending
2757
+ * - Filtering: Global search across filterable columns
2758
+ * - Pagination: Navigate through large datasets with customizable page sizes
2759
+ * - Selection: Single or multiple row selection with callbacks
2760
+ * - Custom rendering: Use cellRenderer for complex cell content
2761
+ * - Responsive: Adapts to different screen sizes
2762
+ * - Internationalization: Customize all UI text
2763
+ * - Accessibility: Proper ARIA attributes and keyboard navigation
2764
+ *
2765
+ * @example Basic usage
2766
+ * ```typescript
2767
+ * interface User { id: number; name: string; email: string; }
2768
+ * const users: User[] = [...];
2769
+ * const columns: DataTableColumn<User>[] = [
2770
+ * { key: 'name', title: 'Name', field: 'name', sortable: true, filterable: true },
2771
+ * { key: 'email', title: 'Email', field: 'email', sortable: true, filterable: true }
2772
+ * ];
2773
+ *
2774
+ * return m(DataTable<User>, { data: users, columns });
2775
+ * ```
2776
+ *
2777
+ * @example Advanced usage with all features
2778
+ * ```typescript
2779
+ * return m(DataTable<User>, {
2780
+ * data: users,
2781
+ * columns,
2782
+ * title: 'User Management',
2783
+ * striped: true,
2784
+ * hoverable: true,
2785
+ * height: 400,
2786
+ *
2787
+ * // Pagination
2788
+ * pagination: { page: 0, pageSize: 10, total: users.length },
2789
+ * onPaginationChange: (pagination) => console.log('Page changed:', pagination),
2790
+ *
2791
+ * // Selection
2792
+ * selection: {
2793
+ * mode: 'multiple',
2794
+ * selectedKeys: [],
2795
+ * getRowKey: (user) => String(user.id),
2796
+ * onSelectionChange: (keys, selectedUsers) => console.log('Selection:', selectedUsers)
2797
+ * },
2798
+ *
2799
+ * // Search
2800
+ * enableGlobalSearch: true,
2801
+ * searchPlaceholder: 'Search users...',
2802
+ *
2803
+ * // Events
2804
+ * onRowClick: (user, index, event) => console.log('Clicked:', user),
2805
+ * onRowDoubleClick: (user) => editUser(user),
2806
+ *
2807
+ * // Styling
2808
+ * getRowClassName: (user) => user.active ? '' : 'inactive-row'
2809
+ * });
2810
+ * ```
2811
+ *
2812
+ * @returns A Mithril component that renders the data table
2813
+ */
2814
+ const DataTable = () => {
2815
+ const state = {
2816
+ internalSort: undefined,
2817
+ internalFilter: undefined,
2818
+ internalPagination: undefined,
2819
+ processedData: [],
2820
+ tableId: '',
2821
+ // Performance optimization caches
2822
+ lastProcessedHash: ''};
2823
+ // Helper functions
2824
+ const quickDataHash = (data) => {
2825
+ if (data.length === 0)
2826
+ return '0';
2827
+ if (data.length === 1)
2828
+ return '1';
2829
+ // Sample first, middle, and last items for quick hash
2830
+ const first = JSON.stringify(data[0]);
2831
+ const middle = data.length > 2 ? JSON.stringify(data[Math.floor(data.length / 2)]) : '';
2832
+ const last = JSON.stringify(data[data.length - 1]);
2833
+ return `${data.length}-${first.length}-${middle.length}-${last.length}`;
2834
+ };
2835
+ const getDataHash = (attrs) => {
2836
+ const { data, sort, filter, pagination } = attrs;
2837
+ const { internalSort, internalFilter, internalPagination } = state;
2838
+ const hashInputs = {
2839
+ dataLength: data.length,
2840
+ dataHash: quickDataHash(data),
2841
+ sort: sort || internalSort,
2842
+ filter: filter || internalFilter,
2843
+ pagination: pagination || internalPagination,
2844
+ };
2845
+ return JSON.stringify(hashInputs);
2846
+ };
2847
+ const getCellValue = (row, column) => {
2848
+ if (column.field) {
2849
+ return row[column.field];
2850
+ }
2851
+ return row;
2852
+ };
2853
+ const applyFiltering = (data, filter, columns) => {
2854
+ var _a;
2855
+ if (!filter.searchTerm && !filter.columnFilters)
2856
+ return data;
2857
+ const filterableColumns = columns.filter((col) => col.filterable);
2858
+ if (filterableColumns.length === 0 && !filter.searchTerm)
2859
+ return data;
2860
+ const searchTerm = (_a = filter.searchTerm) === null || _a === void 0 ? void 0 : _a.toLowerCase();
2861
+ const hasColumnFilters = filter.columnFilters &&
2862
+ Object.keys(filter.columnFilters).some((key) => {
2863
+ const value = filter.columnFilters[key];
2864
+ return value !== null && value !== undefined && value !== '';
2865
+ });
2866
+ return data.filter((row) => {
2867
+ // Global search
2868
+ if (searchTerm) {
2869
+ const matchesGlobal = filterableColumns.some((column) => {
2870
+ const value = getCellValue(row, column);
2871
+ if (value == null)
2872
+ return false;
2873
+ return String(value).toLowerCase().includes(searchTerm);
2874
+ });
2875
+ if (!matchesGlobal)
2876
+ return false;
2877
+ }
2878
+ // Column-specific filters
2879
+ if (hasColumnFilters) {
2880
+ const matchesColumnFilters = Object.entries(filter.columnFilters).every(([columnKey, filterValue]) => {
2881
+ if (filterValue === null || filterValue === undefined || filterValue === '')
2882
+ return true;
2883
+ const column = columns.find((col) => col.key === columnKey);
2884
+ if (!column)
2885
+ return true;
2886
+ const value = getCellValue(row, column);
2887
+ if (value == null)
2888
+ return false;
2889
+ return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
2890
+ });
2891
+ if (!matchesColumnFilters)
2892
+ return false;
2893
+ }
2894
+ return true;
2895
+ });
2896
+ };
2897
+ const applySorting = (data, sort, columns) => {
2898
+ const column = columns.find((col) => col.key === sort.column);
2899
+ if (!column || !column.sortable)
2900
+ return data;
2901
+ const multiplier = sort.direction === 'asc' ? 1 : -1;
2902
+ return [...data].sort((a, b) => {
2903
+ const aValue = getCellValue(a, column);
2904
+ const bValue = getCellValue(b, column);
2905
+ // Handle null/undefined values
2906
+ if (aValue == null && bValue == null)
2907
+ return 0;
2908
+ if (aValue == null)
2909
+ return -1 * multiplier;
2910
+ if (bValue == null)
2911
+ return 1 * multiplier;
2912
+ // Type-specific comparisons
2913
+ const aType = typeof aValue;
2914
+ const bType = typeof bValue;
2915
+ if (aType === bType) {
2916
+ if (aType === 'number') {
2917
+ return (aValue - bValue) * multiplier;
2918
+ }
2919
+ if (aType === 'boolean') {
2920
+ return (aValue === bValue ? 0 : aValue ? 1 : -1) * multiplier;
2921
+ }
2922
+ if (aValue instanceof Date && bValue instanceof Date) {
2923
+ return (aValue.getTime() - bValue.getTime()) * multiplier;
2924
+ }
2925
+ }
2926
+ // Fallback to string comparison
2927
+ return String(aValue).localeCompare(String(bValue)) * multiplier;
2928
+ });
2929
+ };
2930
+ const processData = (attrs) => {
2931
+ const { data } = attrs;
2932
+ const { internalSort, internalFilter, internalPagination } = state;
2933
+ let processedData = [...data];
2934
+ // Apply filtering
2935
+ if (internalFilter) {
2936
+ processedData = applyFiltering(processedData, internalFilter, attrs.columns);
2937
+ }
2938
+ // Apply sorting
2939
+ if (internalSort) {
2940
+ processedData = applySorting(processedData, internalSort, attrs.columns);
2941
+ }
2942
+ // Update total count for pagination
2943
+ if (internalPagination) {
2944
+ state.internalPagination = Object.assign(Object.assign({}, internalPagination), { total: processedData.length });
2945
+ }
2946
+ // Apply pagination
2947
+ if (internalPagination) {
2948
+ const { page, pageSize } = internalPagination;
2949
+ const start = page * pageSize;
2950
+ const end = start + pageSize;
2951
+ processedData = processedData.slice(start, end);
2952
+ }
2953
+ state.processedData = processedData;
2954
+ };
2955
+ // Create stable helper functions that don't get recreated on every render
2956
+ const createHelpers = (attrs) => ({
2957
+ getCellValue,
2958
+ handleSort: (columnKey) => {
2959
+ var _a;
2960
+ const column = attrs.columns.find((col) => col.key === columnKey);
2961
+ if (!column || !column.sortable)
2962
+ return;
2963
+ const currentSort = state.internalSort;
2964
+ let newSort;
2965
+ if ((currentSort === null || currentSort === void 0 ? void 0 : currentSort.column) === columnKey) {
2966
+ // Toggle direction
2967
+ if (currentSort.direction === 'asc') {
2968
+ newSort = { column: columnKey, direction: 'desc' };
2969
+ }
2970
+ else {
2971
+ newSort = { column: columnKey, direction: 'asc' };
2972
+ }
2973
+ }
2974
+ else {
2975
+ // New column sort
2976
+ newSort = { column: columnKey, direction: 'asc' };
2977
+ }
2978
+ state.internalSort = newSort;
2979
+ (_a = attrs.onSortChange) === null || _a === void 0 ? void 0 : _a.call(attrs, newSort);
2980
+ },
2981
+ handleGlobalSearch: (searchTerm) => {
2982
+ var _a;
2983
+ const newFilter = Object.assign(Object.assign({}, state.internalFilter), { searchTerm });
2984
+ state.internalFilter = newFilter;
2985
+ // Reset pagination to first page when filtering
2986
+ if (state.internalPagination) {
2987
+ state.internalPagination = Object.assign(Object.assign({}, state.internalPagination), { page: 0 });
2988
+ }
2989
+ (_a = attrs.onFilterChange) === null || _a === void 0 ? void 0 : _a.call(attrs, newFilter);
2990
+ },
2991
+ handleSelectionChange: (rowKey, selected) => {
2992
+ var _a, _b;
2993
+ if (!attrs.selection)
2994
+ return;
2995
+ let newSelectedKeys;
2996
+ if (attrs.selection.mode === 'single') {
2997
+ newSelectedKeys = selected ? [rowKey] : [];
2998
+ }
2999
+ else if (attrs.selection.mode === 'multiple') {
3000
+ if (selected) {
3001
+ newSelectedKeys = [...attrs.selection.selectedKeys, rowKey];
3002
+ }
3003
+ else {
3004
+ newSelectedKeys = attrs.selection.selectedKeys.filter((key) => key !== rowKey);
3005
+ }
3006
+ }
3007
+ else {
3008
+ return; // No selection mode
3009
+ }
3010
+ // Get selected rows
3011
+ const selectedRows = attrs.data.filter((row, index) => {
3012
+ const key = attrs.selection.getRowKey(row, index);
3013
+ return newSelectedKeys.includes(key);
3014
+ });
3015
+ (_b = (_a = attrs.selection).onSelectionChange) === null || _b === void 0 ? void 0 : _b.call(_a, newSelectedKeys, selectedRows);
3016
+ },
3017
+ handleSelectAll: (selected) => {
3018
+ var _a, _b;
3019
+ if (!attrs.selection || attrs.selection.mode !== 'multiple')
3020
+ return;
3021
+ let newSelectedKeys;
3022
+ if (selected) {
3023
+ // Select all visible rows
3024
+ newSelectedKeys = state.processedData.map((row) => {
3025
+ const originalIndex = attrs.data.findIndex((originalRow) => originalRow === row);
3026
+ return attrs.selection.getRowKey(row, originalIndex);
3027
+ });
3028
+ }
3029
+ else {
3030
+ newSelectedKeys = [];
3031
+ }
3032
+ const selectedRows = attrs.data.filter((row, index) => {
3033
+ const key = attrs.selection.getRowKey(row, index);
3034
+ return newSelectedKeys.includes(key);
3035
+ });
3036
+ (_b = (_a = attrs.selection).onSelectionChange) === null || _b === void 0 ? void 0 : _b.call(_a, newSelectedKeys, selectedRows);
3037
+ },
3038
+ });
3039
+ return {
3040
+ oninit(vnodeInit) {
3041
+ const { sort, filter, pagination } = vnodeInit.attrs;
3042
+ state.tableId = uniqueId();
3043
+ state.internalSort = sort || undefined;
3044
+ state.internalFilter = filter || { searchTerm: '', columnFilters: {} };
3045
+ state.internalPagination = pagination || undefined;
3046
+ processData(vnodeInit.attrs);
3047
+ },
3048
+ onbeforeupdate(vnodeUpdate) {
3049
+ // Only reprocess data if inputs have changed
3050
+ const currentHash = getDataHash(vnodeUpdate.attrs);
3051
+ if (currentHash !== state.lastProcessedHash) {
3052
+ processData(vnodeUpdate.attrs);
3053
+ state.lastProcessedHash = currentHash;
3054
+ }
3055
+ },
3056
+ view(vnodeView) {
3057
+ const attrs = vnodeView.attrs;
3058
+ const { loading, emptyMessage, striped, hoverable, responsive, centered, className, id, title, height, enableGlobalSearch, searchPlaceholder, selection, columns, onRowClick, onRowDoubleClick, getRowClassName, data, onPaginationChange, i18n, } = attrs;
3059
+ const { processedData, tableId, internalSort, internalPagination } = state;
3060
+ if (loading) {
3061
+ return m('.datatable-loading', [
3062
+ m('.preloader-wrapper.small.active', m('.spinner-layer.spinner-blue-only', m('.circle-clipper.left', m('.circle')))),
3063
+ m('p', (i18n === null || i18n === void 0 ? void 0 : i18n.loading) || 'Loading...'),
3064
+ ]);
3065
+ }
3066
+ // Create stable helpers object using the factory function
3067
+ const helpers = createHelpers(attrs);
3068
+ // Calculate selection state for "select all" checkbox
3069
+ let allSelected = false;
3070
+ let someSelected = false;
3071
+ if (selection && selection.mode === 'multiple') {
3072
+ const visibleRowKeys = processedData.map((row) => {
3073
+ const originalIndex = data.findIndex((originalRow) => originalRow === row);
3074
+ return selection.getRowKey(row, originalIndex);
3075
+ });
3076
+ const selectedVisibleKeys = visibleRowKeys.filter((key) => selection.selectedKeys.includes(key));
3077
+ allSelected = visibleRowKeys.length > 0 && selectedVisibleKeys.length === visibleRowKeys.length;
3078
+ someSelected = selectedVisibleKeys.length > 0 && selectedVisibleKeys.length < visibleRowKeys.length;
3079
+ }
3080
+ const tableClasses = [
3081
+ 'datatable',
3082
+ striped ? 'striped' : '',
3083
+ hoverable ? 'highlight' : '',
3084
+ responsive ? 'responsive-table' : '',
3085
+ centered ? 'centered' : '',
3086
+ className || '',
3087
+ ]
3088
+ .filter(Boolean)
3089
+ .join(' ');
3090
+ return m('.datatable-container', {
3091
+ id: id || tableId,
3092
+ }, title && m('h5.datatable-title', title), enableGlobalSearch &&
3093
+ m(GlobalSearch, {
3094
+ searchPlaceholder,
3095
+ onSearch: helpers.handleGlobalSearch,
3096
+ i18n,
3097
+ }), m('.datatable-wrapper', {
3098
+ style: {
3099
+ maxHeight: height ? `${height}px` : undefined,
3100
+ overflowY: height ? 'auto' : undefined,
3101
+ },
3102
+ }, processedData.length === 0
3103
+ ? m('.datatable-empty', emptyMessage || (i18n === null || i18n === void 0 ? void 0 : i18n.noDataAvailable) || 'No data available')
3104
+ : m(TableContent(), {
3105
+ processedData,
3106
+ height,
3107
+ tableClasses,
3108
+ columns,
3109
+ selection,
3110
+ internalSort,
3111
+ allSelected,
3112
+ someSelected,
3113
+ helpers,
3114
+ onRowClick,
3115
+ onRowDoubleClick,
3116
+ getRowClassName,
3117
+ data,
3118
+ })), m(PaginationControls, {
3119
+ pagination: internalPagination,
3120
+ onPaginationChange: (pagination) => {
3121
+ state.internalPagination = pagination;
3122
+ onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(pagination);
3123
+ },
3124
+ i18n,
3125
+ }));
3126
+ },
3127
+ };
3128
+ };
3129
+
3130
+ /** Pure TypeScript Dropdown component - no Materialize dependencies */
3131
+ const Dropdown = () => {
3132
+ const state = {
3133
+ isOpen: false,
3134
+ initialValue: undefined,
3135
+ id: '',
3136
+ focusedIndex: -1,
3137
+ inputRef: null,
3138
+ dropdownRef: null,
3139
+ };
3140
+ const handleKeyDown = (e, items, onchange) => {
3141
+ const availableItems = items.filter((item) => !item.divider && !item.disabled);
3142
+ switch (e.key) {
3143
+ case 'ArrowDown':
3144
+ e.preventDefault();
3145
+ if (!state.isOpen) {
3146
+ state.isOpen = true;
3147
+ state.focusedIndex = 0;
3148
+ }
3149
+ else {
3150
+ state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
3151
+ }
3152
+ break;
3153
+ case 'ArrowUp':
3154
+ e.preventDefault();
3155
+ if (state.isOpen) {
3156
+ state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
3157
+ }
3158
+ break;
3159
+ case 'Enter':
3160
+ case ' ':
3161
+ e.preventDefault();
3162
+ if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
3163
+ const selectedItem = availableItems[state.focusedIndex];
3164
+ const value = (selectedItem.id || selectedItem.label);
3165
+ state.initialValue = value;
3166
+ state.isOpen = false;
3167
+ state.focusedIndex = -1;
3168
+ if (onchange)
3169
+ onchange(value);
3170
+ }
3171
+ else if (!state.isOpen) {
3172
+ state.isOpen = true;
3173
+ state.focusedIndex = 0;
3174
+ }
3175
+ break;
3176
+ case 'Escape':
3177
+ e.preventDefault();
3178
+ state.isOpen = false;
3179
+ state.focusedIndex = -1;
3180
+ break;
3181
+ }
3182
+ };
3183
+ return {
3184
+ oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
3185
+ state.id = id;
3186
+ state.initialValue = initialValue || checkedId;
3187
+ // Mithril will handle click events through the component structure
3188
+ },
3189
+ view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
3190
+ const { initialValue } = state;
3191
+ const selectedItem = initialValue
3192
+ ? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
3193
+ : undefined;
3194
+ const title = selectedItem ? selectedItem.label : label || 'Select';
3195
+ const availableItems = items.filter((item) => !item.divider && !item.disabled);
3196
+ return m('.dropdown-wrapper.input-field', { className, key, style }, [
3197
+ iconName ? m('i.material-icons.prefix', iconName) : undefined,
3198
+ m(HelperText, { helperText }),
3199
+ m('.select-wrapper', {
3200
+ onclick: disabled
3201
+ ? undefined
3202
+ : () => {
3203
+ state.isOpen = !state.isOpen;
3204
+ state.focusedIndex = state.isOpen ? 0 : -1;
3205
+ },
3206
+ onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
3207
+ tabindex: disabled ? -1 : 0,
3208
+ 'aria-expanded': state.isOpen ? 'true' : 'false',
3209
+ 'aria-haspopup': 'listbox',
3210
+ role: 'combobox',
3211
+ }, [
3212
+ m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
3213
+ id: state.id,
3214
+ value: title,
3215
+ oncreate: ({ dom }) => {
3216
+ state.inputRef = dom;
3217
+ },
3218
+ onclick: (e) => {
3219
+ e.preventDefault();
3220
+ e.stopPropagation();
3221
+ if (!disabled) {
3222
+ state.isOpen = !state.isOpen;
3223
+ state.focusedIndex = state.isOpen ? 0 : -1;
3224
+ }
3225
+ },
3226
+ }),
3227
+ // Dropdown Menu using Select component's positioning logic
3228
+ state.isOpen &&
3229
+ m('ul.dropdown-content.select-dropdown', {
3230
+ tabindex: 0,
3231
+ role: 'listbox',
3232
+ 'aria-labelledby': state.id,
3233
+ oncreate: ({ dom }) => {
3234
+ state.dropdownRef = dom;
3235
+ },
3236
+ onremove: () => {
3237
+ state.dropdownRef = null;
3238
+ },
3239
+ style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
3240
+ // Convert dropdown items to format expected by getDropdownStyles
3241
+ group: undefined }))), true),
3242
+ }, items.map((item, index) => {
3243
+ if (item.divider) {
3244
+ return m('li.divider', {
3245
+ key: `divider-${index}`,
3246
+ });
3247
+ }
3248
+ const itemIndex = availableItems.indexOf(item);
3249
+ const isFocused = itemIndex === state.focusedIndex;
3250
+ return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
3251
+ item.disabled ? 'disabled' : '',
3252
+ isFocused ? 'focused' : '',
3253
+ (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
3254
+ ]
3255
+ .filter(Boolean)
3256
+ .join(' ') }, (item.disabled
3257
+ ? {}
3258
+ : {
3259
+ onclick: (e) => {
3260
+ e.stopPropagation();
3261
+ const value = (item.id || item.label);
3262
+ state.initialValue = value;
3263
+ state.isOpen = false;
3264
+ state.focusedIndex = -1;
3265
+ if (onchange)
3266
+ onchange(value);
3267
+ },
3268
+ })), m('span', {
3269
+ style: {
3270
+ display: 'flex',
3271
+ alignItems: 'center',
3272
+ padding: '14px 16px',
3273
+ },
3274
+ }, [
3275
+ item.iconName
3276
+ ? m('i.material-icons', {
3277
+ style: { marginRight: '32px' },
3278
+ }, item.iconName)
3279
+ : undefined,
3280
+ item.label,
3281
+ ]));
3282
+ })),
3283
+ m(MaterialIcon, {
3284
+ name: 'caret',
3285
+ direction: 'down',
3286
+ class: 'caret',
3287
+ }),
3288
+ ]),
3289
+ ]);
3290
+ },
3291
+ };
3292
+ };
3293
+
3294
+ /**
3295
+ * Floating Action Button
3296
+ */
3297
+ const FloatingActionButton = () => {
3298
+ const state = {
3299
+ isOpen: false,
3300
+ };
3301
+ const handleClickOutside = (e) => {
3302
+ const target = e.target;
3303
+ if (!target.closest('.fixed-action-btn')) {
3304
+ state.isOpen = false;
3305
+ }
3306
+ };
3307
+ return {
3308
+ oncreate: () => {
3309
+ document.addEventListener('click', handleClickOutside);
3310
+ },
3311
+ onremove: () => {
3312
+ document.removeEventListener('click', handleClickOutside);
3313
+ },
3314
+ view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
3315
+ ? 'position: absolute; display: inline-block; left: 24px;'
3316
+ : position === 'right' || position === 'inline-right'
3317
+ ? 'position: absolute; display: inline-block; right: 24px;'
3318
+ : undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
3319
+ const fabClasses = [
3320
+ 'fixed-action-btn',
3321
+ direction ? `direction-${direction}` : '',
3322
+ state.isOpen ? 'active' : '',
3323
+ // hoverEnabled ? 'hover-enabled' : '',
3324
+ ]
3325
+ .filter(Boolean)
3326
+ .join(' ');
3327
+ return m('div', {
3328
+ style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
3329
+ }, m(`.${fabClasses}`, {
3330
+ style,
3331
+ onclick: (e) => {
3332
+ e.stopPropagation();
3333
+ if (buttons && buttons.length > 0) {
3334
+ state.isOpen = !state.isOpen;
3335
+ }
3336
+ },
3337
+ onmouseover: hoverEnabled
3338
+ ? () => {
3339
+ if (buttons && buttons.length > 0) {
3340
+ state.isOpen = true;
3341
+ }
3342
+ }
3343
+ : undefined,
3344
+ onmouseleave: hoverEnabled
3345
+ ? () => {
3346
+ state.isOpen = false;
3347
+ }
3348
+ : undefined,
3349
+ }, [
3350
+ m('a.btn-floating.btn-large', {
3351
+ className,
3352
+ }, m('i.material-icons', { className: iconClass }, iconName)),
3353
+ buttons &&
3354
+ buttons.length > 0 &&
3355
+ m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
3356
+ style: {
3357
+ opacity: state.isOpen ? '1' : '0',
3358
+ transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
3359
+ transition: `all 0.3s ease ${index * 40}ms`,
3360
+ },
3361
+ onclick: (e) => {
3362
+ e.stopPropagation();
3363
+ if (button.onClick)
3364
+ button.onClick(e);
3365
+ },
3366
+ }, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
3367
+ ]));
2664
3368
  },
2665
3369
  };
2666
3370
  };
@@ -2839,7 +3543,7 @@ const MaterialBox = () => {
2839
3543
  view: ({ attrs }) => {
2840
3544
  const { src, alt, width, height, caption, className, style } = attrs, otherAttrs = __rest(attrs, ["src", "alt", "width", "height", "caption", "className", "style"]);
2841
3545
  return m('img.materialboxed', Object.assign(Object.assign({}, otherAttrs), { src, alt: alt || '', width,
2842
- height, className: ['materialboxed', className].filter(Boolean).join(' '), style: Object.assign({ cursor: 'zoom-in', transition: 'opacity 200ms ease' }, style), onclick: (e) => {
3546
+ height, className: ['materialboxed', className].filter(Boolean).join(' ') || undefined, style: Object.assign({ cursor: 'zoom-in', transition: 'opacity 200ms ease' }, style), onclick: (e) => {
2843
3547
  e.preventDefault();
2844
3548
  openBox(e.target, attrs);
2845
3549
  } }));
@@ -2921,7 +3625,7 @@ const ModalPanel = () => {
2921
3625
  .filter(Boolean)
2922
3626
  .join(' ')
2923
3627
  .trim();
2924
- const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim();
3628
+ const overlayClasses = ['modal-overlay', state.isOpen ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined;
2925
3629
  return m('div', { className: 'modal-container' }, [
2926
3630
  // Modal overlay
2927
3631
  m('div', {
@@ -2946,21 +3650,25 @@ const ModalPanel = () => {
2946
3650
  role: 'dialog',
2947
3651
  'aria-labelledby': `${id}-title`,
2948
3652
  'aria-describedby': description ? `${id}-desc` : undefined,
2949
- style: {
2950
- display: state.isOpen ? 'block' : 'none',
2951
- position: 'fixed',
2952
- top: '50%',
2953
- left: '50%',
2954
- transform: 'translate(-50%, -50%)',
2955
- backgroundColor: '#fff',
2956
- borderRadius: '4px',
2957
- maxWidth: '75%',
2958
- maxHeight: '85%',
2959
- overflow: 'auto',
2960
- zIndex: '1003',
2961
- padding: '0',
2962
- boxShadow: '0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.20)',
2963
- },
3653
+ style: Object.assign(Object.assign({ display: state.isOpen ? 'flex' : 'none', position: 'fixed' }, (bottomSheet
3654
+ ? {
3655
+ // Bottom sheet positioning
3656
+ top: 'auto',
3657
+ bottom: '0',
3658
+ left: '0',
3659
+ right: '0',
3660
+ transform: 'none',
3661
+ maxWidth: '100%',
3662
+ borderRadius: '8px 8px 0 0',
3663
+ }
3664
+ : {
3665
+ // Regular modal positioning
3666
+ top: '50%',
3667
+ left: '50%',
3668
+ transform: 'translate(-50%, -50%)',
3669
+ maxWidth: '75%',
3670
+ borderRadius: '4px',
3671
+ })), { 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)' }),
2964
3672
  onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
2965
3673
  }, [
2966
3674
  // Close button
@@ -2980,7 +3688,12 @@ const ModalPanel = () => {
2980
3688
  }, '×'),
2981
3689
  // Modal content
2982
3690
  m('.modal-content', {
2983
- style: { padding: '24px', paddingTop: showCloseButton ? '48px' : '24px' },
3691
+ style: {
3692
+ padding: '24px',
3693
+ paddingTop: showCloseButton ? '48px' : '24px',
3694
+ minHeight: 'auto',
3695
+ flex: '1 1 auto',
3696
+ },
2984
3697
  }, [
2985
3698
  m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
2986
3699
  description &&
@@ -2992,7 +3705,7 @@ const ModalPanel = () => {
2992
3705
  m('.modal-footer', {
2993
3706
  style: {
2994
3707
  padding: '4px 6px',
2995
- borderTop: '1px solid rgba(160,160,160,0.2)',
3708
+ borderTop: '1px solid var(--mm-border-color, rgba(160,160,160,0.2))',
2996
3709
  textAlign: 'right',
2997
3710
  },
2998
3711
  }, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
@@ -3006,109 +3719,6 @@ const ModalPanel = () => {
3006
3719
  };
3007
3720
  };
3008
3721
 
3009
- /** Component to show a check box */
3010
- const InputCheckbox = () => {
3011
- return {
3012
- view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
3013
- const checkboxId = inputId || uniqueId();
3014
- return m(`p`, { className, style }, m('label', { for: checkboxId }, [
3015
- m('input[type=checkbox][tabindex=0]', {
3016
- id: checkboxId,
3017
- checked,
3018
- disabled,
3019
- onclick: onchange
3020
- ? (e) => {
3021
- if (e.target && typeof e.target.checked !== 'undefined') {
3022
- onchange(e.target.checked);
3023
- }
3024
- }
3025
- : undefined,
3026
- }),
3027
- label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
3028
- ]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
3029
- },
3030
- };
3031
- };
3032
- /** A list of checkboxes */
3033
- const Options = () => {
3034
- const state = {};
3035
- const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
3036
- const selectAll = (options, callback) => {
3037
- const allIds = options.map((option) => option.id);
3038
- state.checkedIds = [...allIds];
3039
- if (callback)
3040
- callback(allIds);
3041
- };
3042
- const selectNone = (callback) => {
3043
- state.checkedIds = [];
3044
- if (callback)
3045
- callback([]);
3046
- };
3047
- return {
3048
- oninit: ({ attrs: { initialValue, checkedId, id } }) => {
3049
- const iv = checkedId || initialValue;
3050
- state.checkedId = checkedId;
3051
- state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
3052
- state.componentId = id || uniqueId();
3053
- },
3054
- view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
3055
- const onchange = callback
3056
- ? (propId, checked) => {
3057
- const checkedIds = state.checkedIds.filter((i) => i !== propId);
3058
- if (checked) {
3059
- checkedIds.push(propId);
3060
- }
3061
- state.checkedIds = checkedIds;
3062
- callback(checkedIds);
3063
- }
3064
- : undefined;
3065
- const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
3066
- const optionsContent = layout === 'horizontal'
3067
- ? m('div.grid-container', options.map((option) => m(InputCheckbox, {
3068
- disabled: disabled || option.disabled,
3069
- label: option.label,
3070
- onchange: onchange ? (v) => onchange(option.id, v) : undefined,
3071
- className: option.className || checkboxClass,
3072
- checked: isChecked(option.id),
3073
- description: option.description,
3074
- inputId: `${state.componentId}-${option.id}`,
3075
- })))
3076
- : options.map((option) => m(InputCheckbox, {
3077
- disabled: disabled || option.disabled,
3078
- label: option.label,
3079
- onchange: onchange ? (v) => onchange(option.id, v) : undefined,
3080
- className: option.className || checkboxClass,
3081
- checked: isChecked(option.id),
3082
- description: option.description,
3083
- inputId: `${state.componentId}-${option.id}`,
3084
- }));
3085
- return m('div', { id: state.componentId, className: cn, style }, [
3086
- label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
3087
- showSelectAll &&
3088
- m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
3089
- m('a', {
3090
- href: '#',
3091
- onclick: (e) => {
3092
- e.preventDefault();
3093
- selectAll(options, callback);
3094
- },
3095
- style: 'margin-right: 15px;',
3096
- }, 'Select All'),
3097
- m('a', {
3098
- href: '#',
3099
- onclick: (e) => {
3100
- e.preventDefault();
3101
- selectNone(callback);
3102
- },
3103
- }, 'Select None'),
3104
- ]),
3105
- description && m(HelperText, { helperText: description }),
3106
- m('form', { action: '#' }, optionsContent),
3107
- ]);
3108
- },
3109
- };
3110
- };
3111
-
3112
3722
  const PaginationItem = () => ({
3113
3723
  view: ({ attrs: { title, href, active, disabled } }) => m('li', { className: active ? 'active' : disabled ? 'disabled' : 'waves-effect' }, typeof title === 'number' ? m(m.route.Link, { href }, title) : title),
3114
3724
  });
@@ -3373,12 +3983,15 @@ const TimePicker = () => {
3373
3983
  };
3374
3984
  const updateTimeFromInput = (inputValue) => {
3375
3985
  let value = ((inputValue || options.defaultTime || '') + '').split(':');
3986
+ let amPmWasProvided = false;
3376
3987
  if (options.twelveHour && value.length > 1) {
3377
3988
  if (value[1].toUpperCase().indexOf('AM') > -1) {
3378
3989
  state.amOrPm = 'AM';
3990
+ amPmWasProvided = true;
3379
3991
  }
3380
3992
  else if (value[1].toUpperCase().indexOf('PM') > -1) {
3381
3993
  state.amOrPm = 'PM';
3994
+ amPmWasProvided = true;
3382
3995
  }
3383
3996
  value[1] = value[1].replace('AM', '').replace('PM', '').trim();
3384
3997
  }
@@ -3387,21 +4000,33 @@ const TimePicker = () => {
3387
4000
  value = [now.getHours().toString(), now.getMinutes().toString()];
3388
4001
  if (options.twelveHour) {
3389
4002
  state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
4003
+ amPmWasProvided = false; // For 'now', we need to do conversion
3390
4004
  }
3391
4005
  }
3392
4006
  let hours = +value[0] || 0;
3393
4007
  let minutes = +value[1] || 0;
3394
- // Handle 24-hour to 12-hour conversion if needed
3395
- if (options.twelveHour && hours >= 12) {
3396
- state.amOrPm = 'PM';
3397
- if (hours > 12) {
3398
- hours = hours - 12;
4008
+ if (options.twelveHour) {
4009
+ if (!amPmWasProvided) {
4010
+ // No AM/PM was provided, assume this is 24-hour format input - convert it
4011
+ if (hours >= 12) {
4012
+ state.amOrPm = 'PM';
4013
+ if (hours > 12) {
4014
+ hours = hours - 12;
4015
+ }
4016
+ }
4017
+ else {
4018
+ state.amOrPm = 'AM';
4019
+ if (hours === 0) {
4020
+ hours = 12;
4021
+ }
4022
+ }
3399
4023
  }
3400
- }
3401
- else if (options.twelveHour && hours < 12) {
3402
- state.amOrPm = 'AM';
3403
- if (hours === 0) {
3404
- hours = 12;
4024
+ else {
4025
+ // AM/PM was provided, hours are already in 12-hour format
4026
+ // Just handle midnight/noon edge cases
4027
+ if (hours === 0 && state.amOrPm === 'AM') {
4028
+ hours = 12;
4029
+ }
3405
4030
  }
3406
4031
  }
3407
4032
  state.hours = hours;
@@ -4081,7 +4706,7 @@ const RadioButtons = () => {
4081
4706
  callback(propId);
4082
4707
  }
4083
4708
  };
4084
- const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
4709
+ const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
4085
4710
  const optionsContent = layout === 'horizontal'
4086
4711
  ? m('div.grid-container', options.map((r) => m(RadioButton, Object.assign(Object.assign({}, r), { onchange,
4087
4712
  groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === checkedId, inputId: `${componentId}-${r.id}` }))))
@@ -4354,7 +4979,7 @@ const Switch = () => {
4354
4979
  view: ({ attrs }) => {
4355
4980
  const id = attrs.id || state.id;
4356
4981
  const { label, left, right, disabled, newRow, onchange, isMandatory, className = 'col s12' } = attrs, params = __rest(attrs, ["label", "left", "right", "disabled", "newRow", "onchange", "isMandatory", "className"]);
4357
- const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
4982
+ const cn = ['input-field', newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
4358
4983
  return m('div', {
4359
4984
  className: cn,
4360
4985
  onclick: (e) => {
@@ -4505,7 +5130,7 @@ const Tabs = () => {
4505
5130
  },
4506
5131
  view: ({ attrs }) => {
4507
5132
  const { tabWidth, tabs, className, style, swipeable = false } = attrs;
4508
- const cn = [tabWidth === 'fill' ? 'tabs-fixed-width' : '', className].filter(Boolean).join(' ').trim();
5133
+ const cn = [tabWidth === 'fill' ? 'tabs-fixed-width' : '', className].filter(Boolean).join(' ').trim() || undefined;
4509
5134
  const anchoredTabs = tabs.map(toAnchored());
4510
5135
  const activeTab = setActiveTabId(anchoredTabs, attrs.selectedTabId);
4511
5136
  updateIndicator();
@@ -5491,8 +6116,8 @@ const FileUpload = () => {
5491
6116
  }
5492
6117
  // Check file type
5493
6118
  if (attrs.accept) {
5494
- const acceptedTypes = attrs.accept.split(',').map(type => type.trim());
5495
- const isAccepted = acceptedTypes.some(acceptedType => {
6119
+ const acceptedTypes = attrs.accept.split(',').map((type) => type.trim());
6120
+ const isAccepted = acceptedTypes.some((acceptedType) => {
5496
6121
  if (acceptedType.startsWith('.')) {
5497
6122
  // Extension check
5498
6123
  return file.name.toLowerCase().endsWith(acceptedType.toLowerCase());
@@ -5552,11 +6177,11 @@ const FileUpload = () => {
5552
6177
  }
5553
6178
  // Notify parent component
5554
6179
  if (attrs.onFilesSelected) {
5555
- attrs.onFilesSelected(state.files.filter(f => !f.uploadError));
6180
+ attrs.onFilesSelected(state.files.filter((f) => !f.uploadError));
5556
6181
  }
5557
6182
  };
5558
6183
  const removeFile = (fileToRemove, attrs) => {
5559
- state.files = state.files.filter(file => file !== fileToRemove);
6184
+ state.files = state.files.filter((file) => file !== fileToRemove);
5560
6185
  if (attrs.onFileRemoved) {
5561
6186
  attrs.onFileRemoved(fileToRemove);
5562
6187
  }
@@ -5575,11 +6200,11 @@ const FileUpload = () => {
5575
6200
  id: uniqueId(),
5576
6201
  files: [],
5577
6202
  isDragOver: false,
5578
- isUploading: false
6203
+ isUploading: false,
5579
6204
  };
5580
6205
  },
5581
6206
  view: ({ attrs }) => {
5582
- const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error } = attrs;
6207
+ const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error, } = attrs;
5583
6208
  return m('.file-upload-container', { class: className }, [
5584
6209
  // Upload area
5585
6210
  m('.file-upload-area', {
@@ -5587,8 +6212,10 @@ const FileUpload = () => {
5587
6212
  state.isDragOver ? 'drag-over' : '',
5588
6213
  disabled ? 'disabled' : '',
5589
6214
  error ? 'error' : '',
5590
- state.files.length > 0 ? 'has-files' : ''
5591
- ].filter(Boolean).join(' '),
6215
+ state.files.length > 0 ? 'has-files' : '',
6216
+ ]
6217
+ .filter(Boolean)
6218
+ .join(' ') || undefined,
5592
6219
  ondragover: (e) => {
5593
6220
  if (disabled)
5594
6221
  return;
@@ -5619,7 +6246,7 @@ const FileUpload = () => {
5619
6246
  return;
5620
6247
  const input = document.getElementById(state.id);
5621
6248
  input === null || input === void 0 ? void 0 : input.click();
5622
- }
6249
+ },
5623
6250
  }, [
5624
6251
  m('input[type="file"]', {
5625
6252
  id: state.id,
@@ -5632,57 +6259,55 @@ const FileUpload = () => {
5632
6259
  if (target.files) {
5633
6260
  handleFiles(target.files, attrs);
5634
6261
  }
5635
- }
6262
+ },
5636
6263
  }),
5637
6264
  m('.file-upload-content', [
5638
6265
  m('i.material-icons.file-upload-icon', 'cloud_upload'),
5639
6266
  m('p.file-upload-label', label),
5640
6267
  helperText && m('p.file-upload-helper', helperText),
5641
- accept && m('p.file-upload-types', `Accepted: ${accept}`)
5642
- ])
6268
+ accept && m('p.file-upload-types', `Accepted: ${accept}`),
6269
+ ]),
5643
6270
  ]),
5644
6271
  // Error message
5645
6272
  error && m('.file-upload-error', error),
5646
6273
  // File list
5647
- state.files.length > 0 && m('.file-upload-list', [
5648
- m('h6', 'Selected Files:'),
5649
- state.files.map(file => m('.file-upload-item', { key: file.name + file.size }, [
5650
- // Preview thumbnail
5651
- showPreview && file.preview && m('.file-preview', [
5652
- m('img', { src: file.preview, alt: file.name })
5653
- ]),
5654
- // File info
5655
- m('.file-info', [
5656
- m('.file-name', file.name),
5657
- m('.file-details', [
5658
- m('span.file-size', formatFileSize(file.size)),
5659
- file.type && m('span.file-type', file.type)
5660
- ]),
5661
- // Progress bar (if uploading)
5662
- file.uploadProgress !== undefined && m('.file-progress', [
5663
- m('.progress', [
5664
- m('.determinate', {
5665
- style: { width: `${file.uploadProgress}%` }
5666
- })
5667
- ])
6274
+ state.files.length > 0 &&
6275
+ m('.file-upload-list', [
6276
+ m('h6', 'Selected Files:'),
6277
+ state.files.map((file) => m('.file-upload-item', { key: file.name + file.size }, [
6278
+ // Preview thumbnail
6279
+ showPreview && file.preview && m('.file-preview', [m('img', { src: file.preview, alt: file.name })]),
6280
+ // File info
6281
+ m('.file-info', [
6282
+ m('.file-name', file.name),
6283
+ m('.file-details', [
6284
+ m('span.file-size', formatFileSize(file.size)),
6285
+ file.type && m('span.file-type', file.type),
6286
+ ]),
6287
+ // Progress bar (if uploading)
6288
+ file.uploadProgress !== undefined &&
6289
+ m('.file-progress', [
6290
+ m('.progress', [
6291
+ m('.determinate', {
6292
+ style: { width: `${file.uploadProgress}%` },
6293
+ }),
6294
+ ]),
6295
+ ]),
6296
+ // Error message
6297
+ file.uploadError && m('.file-error', file.uploadError),
5668
6298
  ]),
5669
- // Error message
5670
- file.uploadError && m('.file-error', file.uploadError)
5671
- ]),
5672
- // Remove button
5673
- m('button.btn-flat.file-remove', {
5674
- onclick: (e) => {
5675
- e.stopPropagation();
5676
- removeFile(file, attrs);
5677
- },
5678
- title: 'Remove file'
5679
- }, [
5680
- m('i.material-icons', 'close')
5681
- ])
5682
- ]))
5683
- ])
6299
+ // Remove button
6300
+ m('button.btn-flat.file-remove', {
6301
+ onclick: (e) => {
6302
+ e.stopPropagation();
6303
+ removeFile(file, attrs);
6304
+ },
6305
+ title: 'Remove file',
6306
+ }, [m('i.material-icons', 'close')]),
6307
+ ])),
6308
+ ]),
5684
6309
  ]);
5685
- }
6310
+ },
5686
6311
  };
5687
6312
  };
5688
6313
 
@@ -5712,7 +6337,7 @@ const Sidenav = () => {
5712
6337
  state = {
5713
6338
  id: attrs.id || uniqueId(),
5714
6339
  isOpen: attrs.isOpen || false,
5715
- isAnimating: false
6340
+ isAnimating: false,
5716
6341
  };
5717
6342
  // Set up keyboard listener
5718
6343
  if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
@@ -5741,34 +6366,33 @@ const Sidenav = () => {
5741
6366
  }
5742
6367
  },
5743
6368
  view: ({ attrs, children }) => {
5744
- const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false } = attrs;
6369
+ const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false, } = attrs;
5745
6370
  const isOpen = state.isOpen;
5746
6371
  return [
5747
6372
  // Backdrop (using existing materialize class)
5748
- showBackdrop && mode === 'overlay' && m('.sidenav-overlay', {
5749
- style: {
5750
- display: isOpen ? 'block' : 'none',
5751
- opacity: isOpen ? '1' : '0'
5752
- },
5753
- onclick: () => handleBackdropClick(attrs)
5754
- }),
6373
+ showBackdrop &&
6374
+ mode === 'overlay' &&
6375
+ m('.sidenav-overlay', {
6376
+ style: {
6377
+ display: isOpen ? 'block' : 'none',
6378
+ opacity: isOpen ? '1' : '0',
6379
+ },
6380
+ onclick: () => handleBackdropClick(attrs),
6381
+ }),
5755
6382
  // Sidenav (using existing materialize structure)
5756
6383
  m('ul.sidenav', {
5757
6384
  id: state.id,
5758
- class: [
5759
- position === 'right' ? 'right-aligned' : '',
5760
- fixed ? 'sidenav-fixed' : '',
5761
- className
5762
- ].filter(Boolean).join(' '),
6385
+ class: [position === 'right' ? 'right-aligned' : '', fixed ? 'sidenav-fixed' : '', className]
6386
+ .filter(Boolean)
6387
+ .join(' ') || undefined,
5763
6388
  style: {
5764
6389
  width: `${width}px`,
5765
- transform: isOpen ? 'translateX(0)' :
5766
- position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
5767
- 'transition-duration': `${animationDuration}ms`
5768
- }
5769
- }, children)
6390
+ transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
6391
+ 'transition-duration': `${animationDuration}ms`,
6392
+ },
6393
+ }, children),
5770
6394
  ];
5771
- }
6395
+ },
5772
6396
  };
5773
6397
  };
5774
6398
  /**
@@ -5778,37 +6402,30 @@ const Sidenav = () => {
5778
6402
  const SidenavItem = () => {
5779
6403
  return {
5780
6404
  view: ({ attrs, children }) => {
5781
- const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false } = attrs;
6405
+ const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, } = attrs;
5782
6406
  if (divider) {
5783
6407
  return m('li.divider');
5784
6408
  }
5785
6409
  if (subheader) {
5786
6410
  return m('li.subheader', text || children);
5787
6411
  }
5788
- const itemClasses = [
5789
- active ? 'active' : '',
5790
- disabled ? 'disabled' : '',
5791
- className
5792
- ].filter(Boolean).join(' ');
5793
- const content = [
5794
- icon && m('i.material-icons', icon),
5795
- text || children
5796
- ];
6412
+ const itemClasses = [active ? 'active' : '', disabled ? 'disabled' : '', className].filter(Boolean).join(' ') || undefined;
6413
+ const content = [icon && m('i.material-icons', icon), text || children];
5797
6414
  if (href && !disabled) {
5798
6415
  return m('li', { class: itemClasses }, [
5799
6416
  m('a', {
5800
6417
  href,
5801
- onclick: disabled ? undefined : onclick
5802
- }, content)
6418
+ onclick: disabled ? undefined : onclick,
6419
+ }, content),
5803
6420
  ]);
5804
6421
  }
5805
6422
  return m('li', { class: itemClasses }, [
5806
6423
  m('a', {
5807
6424
  onclick: disabled ? undefined : onclick,
5808
- href: '#!'
5809
- }, content)
6425
+ href: '#!',
6426
+ }, content),
5810
6427
  ]);
5811
- }
6428
+ },
5812
6429
  };
5813
6430
  };
5814
6431
  /**
@@ -5859,7 +6476,7 @@ class SidenavManager {
5859
6476
  const Breadcrumb = () => {
5860
6477
  return {
5861
6478
  view: ({ attrs }) => {
5862
- const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false } = attrs;
6479
+ const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false, } = attrs;
5863
6480
  if (items.length === 0) {
5864
6481
  return null;
5865
6482
  }
@@ -5868,52 +6485,46 @@ const Breadcrumb = () => {
5868
6485
  if (maxItems && items.length > maxItems) {
5869
6486
  const firstItem = items[0];
5870
6487
  const lastItems = items.slice(-(maxItems - 2));
5871
- displayItems = [
5872
- firstItem,
5873
- { text: '...', disabled: true, className: 'breadcrumb-ellipsis' },
5874
- ...lastItems
5875
- ];
6488
+ displayItems = [firstItem, { text: '...', disabled: true, className: 'breadcrumb-ellipsis' }, ...lastItems];
5876
6489
  }
5877
6490
  return m('nav.breadcrumb', { class: className }, [
5878
- m('ol.breadcrumb-list', displayItems.map((item, index) => {
6491
+ m('ol.breadcrumb-list', displayItems
6492
+ .map((item, index) => {
5879
6493
  const isLast = index === displayItems.length - 1;
5880
6494
  const isFirst = index === 0;
5881
6495
  return [
5882
6496
  // Breadcrumb item
5883
6497
  m('li.breadcrumb-item', {
5884
- class: [
5885
- item.active || isLast ? 'active' : '',
5886
- item.disabled ? 'disabled' : '',
5887
- item.className || ''
5888
- ].filter(Boolean).join(' ')
6498
+ class: [item.active || isLast ? 'active' : '', item.disabled ? 'disabled' : '', item.className || '']
6499
+ .filter(Boolean)
6500
+ .join(' ') || undefined,
5889
6501
  }, [
5890
- item.href && !item.disabled && !isLast ?
5891
- // Link item
5892
- m('a.breadcrumb-link', {
5893
- href: item.href,
5894
- onclick: item.onclick
5895
- }, [
5896
- (showIcons && item.icon) && m('i.material-icons.breadcrumb-icon', item.icon),
5897
- (showHome && isFirst && !item.icon) && m('i.material-icons.breadcrumb-icon', 'home'),
5898
- m('span.breadcrumb-text', item.text)
5899
- ]) :
5900
- // Text item (active or disabled)
5901
- m('span.breadcrumb-text', {
5902
- onclick: item.disabled ? undefined : item.onclick
5903
- }, [
5904
- (showIcons && item.icon) && m('i.material-icons.breadcrumb-icon', item.icon),
5905
- (showHome && isFirst && !item.icon) && m('i.material-icons.breadcrumb-icon', 'home'),
5906
- item.text
5907
- ])
6502
+ item.href && !item.disabled && !isLast
6503
+ ? // Link item
6504
+ m('a.breadcrumb-link', {
6505
+ href: item.href,
6506
+ onclick: item.onclick,
6507
+ }, [
6508
+ showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
6509
+ showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
6510
+ m('span.breadcrumb-text', item.text),
6511
+ ])
6512
+ : // Text item (active or disabled)
6513
+ m('span.breadcrumb-text', {
6514
+ onclick: item.disabled ? undefined : item.onclick,
6515
+ }, [
6516
+ showIcons && item.icon && m('i.material-icons.breadcrumb-icon', item.icon),
6517
+ showHome && isFirst && !item.icon && m('i.material-icons.breadcrumb-icon', 'home'),
6518
+ item.text,
6519
+ ]),
5908
6520
  ]),
5909
6521
  // Separator (except for last item)
5910
- !isLast && m('li.breadcrumb-separator', [
5911
- m('i.material-icons', separator)
5912
- ])
6522
+ !isLast && m('li.breadcrumb-separator', [m('i.material-icons', separator)]),
5913
6523
  ];
5914
- }).reduce((acc, val) => acc.concat(val), []))
6524
+ })
6525
+ .reduce((acc, val) => acc.concat(val), [])),
5915
6526
  ]);
5916
- }
6527
+ },
5917
6528
  };
5918
6529
  };
5919
6530
  /**
@@ -5926,7 +6537,7 @@ const createBreadcrumb = (path, basePath = '/') => {
5926
6537
  items.push({
5927
6538
  text: 'Home',
5928
6539
  href: basePath,
5929
- icon: 'home'
6540
+ icon: 'home',
5930
6541
  });
5931
6542
  // Add path segments
5932
6543
  let currentPath = basePath;
@@ -5936,7 +6547,7 @@ const createBreadcrumb = (path, basePath = '/') => {
5936
6547
  items.push({
5937
6548
  text: segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' '),
5938
6549
  href: isLast ? undefined : currentPath,
5939
- active: isLast
6550
+ active: isLast,
5940
6551
  });
5941
6552
  });
5942
6553
  return items;
@@ -5955,19 +6566,18 @@ class BreadcrumbManager {
5955
6566
  items.push({
5956
6567
  text: 'Home',
5957
6568
  href: '/',
5958
- icon: 'home'
6569
+ icon: 'home',
5959
6570
  });
5960
6571
  let currentPath = '';
5961
6572
  segments.forEach((segment, index) => {
5962
6573
  currentPath += '/' + segment;
5963
6574
  const isLast = index === segments.length - 1;
5964
6575
  // Use custom text from config or format segment
5965
- const text = routeConfig[currentPath] ||
5966
- segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
6576
+ const text = routeConfig[currentPath] || segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
5967
6577
  items.push({
5968
6578
  text,
5969
6579
  href: isLast ? undefined : currentPath,
5970
- active: isLast
6580
+ active: isLast,
5971
6581
  });
5972
6582
  });
5973
6583
  return items;
@@ -5979,7 +6589,7 @@ class BreadcrumbManager {
5979
6589
  return hierarchy.map((item, index) => ({
5980
6590
  text: item[textKey],
5981
6591
  href: index === hierarchy.length - 1 ? undefined : item[pathKey],
5982
- active: index === hierarchy.length - 1
6592
+ active: index === hierarchy.length - 1,
5983
6593
  }));
5984
6594
  }
5985
6595
  }
@@ -6113,7 +6723,7 @@ const Wizard = () => {
6113
6723
  hasError ? 'error' : '',
6114
6724
  step.disabled ? 'disabled' : '',
6115
6725
  step.optional ? 'optional' : ''
6116
- ].filter(Boolean).join(' '),
6726
+ ].filter(Boolean).join(' ') || undefined,
6117
6727
  onclick: allowHeaderNavigation && !step.disabled ?
6118
6728
  () => goToStep(index, attrs) : undefined
6119
6729
  }, [
@@ -6187,6 +6797,313 @@ const Stepper = () => {
6187
6797
  };
6188
6798
  };
6189
6799
 
6800
+ // Utility function to check if a node is the last in its branch
6801
+ const isNodeLastInBranch = (nodePath, rootNodes) => {
6802
+ // Navigate to the node's position and check if it's the last child at every level
6803
+ let currentNodes = rootNodes;
6804
+ for (let i = 0; i < nodePath.length; i++) {
6805
+ const index = nodePath[i];
6806
+ const isLastAtThisLevel = index === currentNodes.length - 1;
6807
+ // If this is not the last child at this level, then this node is not last in branch
6808
+ if (!isLastAtThisLevel) {
6809
+ return false;
6810
+ }
6811
+ // Move to the next level if it exists
6812
+ if (i < nodePath.length - 1) {
6813
+ const currentNode = currentNodes[index];
6814
+ if (currentNode.children) {
6815
+ currentNodes = currentNode.children;
6816
+ }
6817
+ }
6818
+ }
6819
+ return true;
6820
+ };
6821
+ const TreeNodeComponent = () => {
6822
+ return {
6823
+ view: ({ attrs }) => {
6824
+ const { node, level, isSelected, isExpanded, isFocused, showConnectors, iconType, selectionMode, onToggleExpand, onToggleSelect, onFocus, } = attrs;
6825
+ const hasChildren = node.children && node.children.length > 0;
6826
+ const indentLevel = level * 24; // 24px per level
6827
+ return m('li.tree-node', {
6828
+ class: [
6829
+ isSelected && 'selected',
6830
+ isFocused && 'focused',
6831
+ node.disabled && 'disabled',
6832
+ hasChildren && 'has-children',
6833
+ attrs.isLastInBranch && 'tree-last-in-branch',
6834
+ ]
6835
+ .filter(Boolean)
6836
+ .join(' ') || undefined,
6837
+ 'data-node-id': node.id,
6838
+ 'data-level': level,
6839
+ }, [
6840
+ // Node content
6841
+ m('.tree-node-content', {
6842
+ style: {
6843
+ paddingLeft: `${indentLevel}px`,
6844
+ },
6845
+ onclick: node.disabled
6846
+ ? undefined
6847
+ : () => {
6848
+ if (selectionMode !== 'none') {
6849
+ onToggleSelect(node.id);
6850
+ }
6851
+ onFocus(node.id);
6852
+ },
6853
+ onkeydown: (e) => {
6854
+ if (e.key === 'Enter' || e.key === ' ') {
6855
+ e.preventDefault();
6856
+ if (!node.disabled && selectionMode !== 'none') {
6857
+ onToggleSelect(node.id);
6858
+ }
6859
+ }
6860
+ },
6861
+ tabindex: node.disabled ? -1 : 0,
6862
+ role: selectionMode === 'multiple' ? 'option' : 'treeitem',
6863
+ 'aria-selected': selectionMode !== 'none' ? isSelected.toString() : undefined,
6864
+ 'aria-expanded': hasChildren ? isExpanded.toString() : undefined,
6865
+ 'aria-disabled': node.disabled ? 'true' : undefined,
6866
+ }, [
6867
+ // Connector lines
6868
+ showConnectors &&
6869
+ level > 0 &&
6870
+ m('.tree-connectors', Array.from({ length: level }, (_, i) => m('.tree-connector', {
6871
+ key: i,
6872
+ style: { left: `${i * 24 + 12}px` },
6873
+ }))),
6874
+ // Expand/collapse icon or spacer
6875
+ hasChildren
6876
+ ? m('.tree-expand-icon', {
6877
+ onclick: (e) => {
6878
+ e.stopPropagation();
6879
+ if (!node.disabled) {
6880
+ onToggleExpand(node.id);
6881
+ }
6882
+ },
6883
+ class: iconType,
6884
+ }, [
6885
+ iconType === 'plus-minus'
6886
+ ? m('span.tree-plus-minus', isExpanded ? '−' : '+')
6887
+ : iconType === 'triangle'
6888
+ ? m('span.tree-triangle', { class: isExpanded ? 'expanded' : undefined }, '▶')
6889
+ : iconType === 'chevron'
6890
+ ? m(MaterialIcon, {
6891
+ name: 'chevron',
6892
+ direction: isExpanded ? 'down' : 'right',
6893
+ class: 'tree-chevron-icon',
6894
+ })
6895
+ : m(MaterialIcon, {
6896
+ name: 'caret',
6897
+ direction: isExpanded ? 'down' : 'right',
6898
+ class: 'tree-caret-icon',
6899
+ }),
6900
+ ])
6901
+ : m('.tree-expand-spacer'), // Spacer for alignment
6902
+ // Selection indicator for multiple selection
6903
+ selectionMode === 'multiple' &&
6904
+ m('.tree-selection-indicator', [
6905
+ m('input[type=checkbox]', {
6906
+ checked: isSelected,
6907
+ disabled: node.disabled,
6908
+ onchange: () => {
6909
+ if (!node.disabled) {
6910
+ onToggleSelect(node.id);
6911
+ }
6912
+ },
6913
+ onclick: (e) => e.stopPropagation(),
6914
+ }),
6915
+ ]),
6916
+ // Node icon (optional)
6917
+ node.icon && m('i.tree-node-icon.material-icons', node.icon),
6918
+ // Node label
6919
+ m('span.tree-node-label', node.label),
6920
+ ]),
6921
+ // Children (recursive)
6922
+ hasChildren &&
6923
+ isExpanded &&
6924
+ m('ul.tree-children', {
6925
+ role: 'group',
6926
+ 'aria-expanded': 'true',
6927
+ }, node.children.map((child, childIndex) => {
6928
+ var _a, _b, _c, _d, _e, _f;
6929
+ // Calculate state for each child using treeState
6930
+ const childIsSelected = (_b = (_a = attrs.treeState) === null || _a === void 0 ? void 0 : _a.selectedIds.has(child.id)) !== null && _b !== void 0 ? _b : false;
6931
+ const childIsExpanded = (_d = (_c = attrs.treeState) === null || _c === void 0 ? void 0 : _c.expandedIds.has(child.id)) !== null && _d !== void 0 ? _d : false;
6932
+ const childIsFocused = ((_e = attrs.treeState) === null || _e === void 0 ? void 0 : _e.focusedNodeId) === child.id;
6933
+ // Calculate if this child is last in branch
6934
+ const childPath = [...(attrs.currentPath || []), childIndex];
6935
+ const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data) ?
6936
+ isNodeLastInBranch(childPath, attrs.treeAttrs.data) : false;
6937
+ return m(TreeNodeComponent, {
6938
+ key: child.id,
6939
+ node: child,
6940
+ level: level + 1,
6941
+ isSelected: childIsSelected,
6942
+ isExpanded: childIsExpanded,
6943
+ isFocused: childIsFocused,
6944
+ showConnectors,
6945
+ iconType,
6946
+ selectionMode,
6947
+ onToggleExpand,
6948
+ onToggleSelect,
6949
+ onFocus,
6950
+ isLastInBranch: childIsLastInBranch,
6951
+ currentPath: childPath,
6952
+ treeState: attrs.treeState,
6953
+ treeAttrs: attrs.treeAttrs,
6954
+ });
6955
+ })),
6956
+ ]);
6957
+ },
6958
+ };
6959
+ };
6960
+ const TreeView = () => {
6961
+ const state = {
6962
+ selectedIds: new Set(),
6963
+ expandedIds: new Set(),
6964
+ focusedNodeId: null,
6965
+ treeMap: new Map(),
6966
+ };
6967
+ const buildTreeMap = (nodes, map) => {
6968
+ nodes.forEach((node) => {
6969
+ map.set(node.id, node);
6970
+ if (node.children) {
6971
+ buildTreeMap(node.children, map);
6972
+ }
6973
+ });
6974
+ };
6975
+ const initializeExpandedNodes = (nodes) => {
6976
+ nodes.forEach((node) => {
6977
+ if (node.expanded) {
6978
+ state.expandedIds.add(node.id);
6979
+ }
6980
+ if (node.children) {
6981
+ initializeExpandedNodes(node.children);
6982
+ }
6983
+ });
6984
+ };
6985
+ const handleToggleExpand = (nodeId, attrs) => {
6986
+ var _a;
6987
+ const isExpanded = state.expandedIds.has(nodeId);
6988
+ if (isExpanded) {
6989
+ state.expandedIds.delete(nodeId);
6990
+ }
6991
+ else {
6992
+ state.expandedIds.add(nodeId);
6993
+ }
6994
+ (_a = attrs.onexpand) === null || _a === void 0 ? void 0 : _a.call(attrs, { nodeId, expanded: !isExpanded });
6995
+ };
6996
+ const handleToggleSelect = (nodeId, attrs) => {
6997
+ var _a;
6998
+ const { selectionMode = 'single' } = attrs;
6999
+ if (selectionMode === 'single') {
7000
+ state.selectedIds.clear();
7001
+ state.selectedIds.add(nodeId);
7002
+ }
7003
+ else if (selectionMode === 'multiple') {
7004
+ if (state.selectedIds.has(nodeId)) {
7005
+ state.selectedIds.delete(nodeId);
7006
+ }
7007
+ else {
7008
+ state.selectedIds.add(nodeId);
7009
+ }
7010
+ }
7011
+ (_a = attrs.onselection) === null || _a === void 0 ? void 0 : _a.call(attrs, Array.from(state.selectedIds));
7012
+ };
7013
+ const handleFocus = (nodeId) => {
7014
+ state.focusedNodeId = nodeId;
7015
+ };
7016
+ const renderNodes = (nodes, attrs, level = 0, parentPath = []) => {
7017
+ return nodes.map((node, index) => {
7018
+ var _a, _b, _c;
7019
+ const isSelected = state.selectedIds.has(node.id);
7020
+ const isExpanded = state.expandedIds.has(node.id);
7021
+ const isFocused = state.focusedNodeId === node.id;
7022
+ const currentPath = [...parentPath, index];
7023
+ const isLastInBranch = isNodeLastInBranch(currentPath, attrs.data);
7024
+ return m(TreeNodeComponent, {
7025
+ key: node.id,
7026
+ node,
7027
+ level,
7028
+ isSelected,
7029
+ isExpanded,
7030
+ isFocused,
7031
+ showConnectors: (_a = attrs.showConnectors) !== null && _a !== void 0 ? _a : true,
7032
+ iconType: (_b = attrs.iconType) !== null && _b !== void 0 ? _b : 'caret',
7033
+ selectionMode: (_c = attrs.selectionMode) !== null && _c !== void 0 ? _c : 'single',
7034
+ onToggleExpand: (nodeId) => handleToggleExpand(nodeId, attrs),
7035
+ onToggleSelect: (nodeId) => handleToggleSelect(nodeId, attrs),
7036
+ onFocus: handleFocus,
7037
+ isLastInBranch,
7038
+ currentPath,
7039
+ // Pass state and attrs for recursive rendering
7040
+ treeState: state,
7041
+ treeAttrs: attrs,
7042
+ });
7043
+ });
7044
+ };
7045
+ return {
7046
+ oninit: ({ attrs }) => {
7047
+ // Build internal tree map for efficient lookups
7048
+ buildTreeMap(attrs.data, state.treeMap);
7049
+ // Initialize expanded nodes from data
7050
+ initializeExpandedNodes(attrs.data);
7051
+ // Initialize selected nodes from props
7052
+ if (attrs.selectedIds) {
7053
+ state.selectedIds = new Set(attrs.selectedIds);
7054
+ }
7055
+ },
7056
+ onupdate: ({ attrs }) => {
7057
+ // Sync selectedIds prop with internal state
7058
+ if (attrs.selectedIds) {
7059
+ const newSelection = new Set(attrs.selectedIds);
7060
+ if (newSelection.size !== state.selectedIds.size ||
7061
+ !Array.from(newSelection).every((id) => state.selectedIds.has(id))) {
7062
+ state.selectedIds = newSelection;
7063
+ }
7064
+ }
7065
+ },
7066
+ view: ({ attrs }) => {
7067
+ const { data, className, style, id, selectionMode = 'single', showConnectors = true } = attrs;
7068
+ return m('div.tree-view', {
7069
+ class: [
7070
+ className,
7071
+ showConnectors && 'show-connectors'
7072
+ ].filter(Boolean).join(' ') || undefined,
7073
+ style,
7074
+ id,
7075
+ role: selectionMode === 'multiple' ? 'listbox' : 'tree',
7076
+ 'aria-multiselectable': selectionMode === 'multiple' ? 'true' : 'false',
7077
+ }, [
7078
+ m('ul.tree-root', {
7079
+ role: 'group',
7080
+ }, renderNodes(data, attrs)),
7081
+ ]);
7082
+ },
7083
+ };
7084
+ };
7085
+
7086
+ /**
7087
+ * @fileoverview Core TypeScript utility types for mithril-materialized library
7088
+ * These types improve type safety and developer experience across all components
7089
+ */
7090
+ /**
7091
+ * Type guard to check if validation result indicates success
7092
+ * @param result - The validation result to check
7093
+ * @returns True if validation passed
7094
+ */
7095
+ const isValidationSuccess = (result) => result === true || result === '';
7096
+ /**
7097
+ * Type guard to check if validation result indicates an error
7098
+ * @param result - The validation result to check
7099
+ * @returns True if validation failed
7100
+ */
7101
+ const isValidationError = (result) => !isValidationSuccess(result);
7102
+ // ============================================================================
7103
+ // EXPORTS
7104
+ // ============================================================================
7105
+ // All types are already exported via individual export declarations above
7106
+
6190
7107
  exports.AnchorItem = AnchorItem;
6191
7108
  exports.Autocomplete = Autocomplete;
6192
7109
  exports.Breadcrumb = Breadcrumb;
@@ -6201,6 +7118,7 @@ exports.Collapsible = Collapsible;
6201
7118
  exports.CollapsibleItem = CollapsibleItem;
6202
7119
  exports.Collection = Collection;
6203
7120
  exports.ColorInput = ColorInput;
7121
+ exports.DataTable = DataTable;
6204
7122
  exports.DatePicker = DatePicker;
6205
7123
  exports.Dropdown = Dropdown;
6206
7124
  exports.EmailInput = EmailInput;
@@ -6216,10 +7134,12 @@ exports.LargeButton = LargeButton;
6216
7134
  exports.ListItem = ListItem;
6217
7135
  exports.Mandatory = Mandatory;
6218
7136
  exports.MaterialBox = MaterialBox;
7137
+ exports.MaterialIcon = MaterialIcon;
6219
7138
  exports.ModalPanel = ModalPanel;
6220
7139
  exports.NumberInput = NumberInput;
6221
7140
  exports.Options = Options;
6222
7141
  exports.Pagination = Pagination;
7142
+ exports.PaginationControls = PaginationControls;
6223
7143
  exports.Parallax = Parallax;
6224
7144
  exports.PasswordInput = PasswordInput;
6225
7145
  exports.Pushpin = Pushpin;
@@ -6249,6 +7169,7 @@ exports.Toast = Toast;
6249
7169
  exports.ToastComponent = ToastComponent;
6250
7170
  exports.Tooltip = Tooltip;
6251
7171
  exports.TooltipComponent = TooltipComponent;
7172
+ exports.TreeView = TreeView;
6252
7173
  exports.UrlInput = UrlInput;
6253
7174
  exports.Wizard = Wizard;
6254
7175
  exports.createBreadcrumb = createBreadcrumb;
@@ -6256,6 +7177,8 @@ exports.getDropdownStyles = getDropdownStyles;
6256
7177
  exports.initPushpins = initPushpins;
6257
7178
  exports.initTooltips = initTooltips;
6258
7179
  exports.isNumeric = isNumeric;
7180
+ exports.isValidationError = isValidationError;
7181
+ exports.isValidationSuccess = isValidationSuccess;
6259
7182
  exports.padLeft = padLeft;
6260
7183
  exports.range = range;
6261
7184
  exports.toast = toast;