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