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