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/README.md +41 -20
- package/dist/advanced.css +6 -6
- package/dist/button.d.ts +56 -11
- package/dist/components.css +601 -6
- package/dist/core.css +13 -13
- package/dist/datatable.d.ts +291 -0
- package/dist/forms.css +13 -13
- package/dist/icon.d.ts +2 -2
- package/dist/index.css +622 -16
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +1435 -518
- package/dist/index.js +1440 -517
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +1440 -517
- package/dist/input.d.ts +0 -1
- package/dist/material-icon.d.ts +3 -0
- package/dist/treeview.d.ts +39 -0
- package/dist/types.d.ts +226 -0
- package/dist/utilities.css +16 -9
- package/package.json +6 -3
- package/sass/components/_cards.scss +10 -3
- package/sass/components/_datatable.scss +417 -0
- package/sass/components/_global.scss +6 -6
- package/sass/components/_treeview.scss +353 -0
- package/sass/components/forms/_range.scss +5 -5
- package/sass/components/forms/_select.scss +1 -1
- package/sass/materialize.scss +2 -0
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,
|
|
420
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
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: {
|
|
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
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
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
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
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
|
-
]
|
|
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 &&
|
|
5646
|
-
m('
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
m('img', { src: file.preview, alt: file.name })
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
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
|
-
//
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
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 &&
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
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
|
-
|
|
5758
|
-
|
|
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
|
-
|
|
5765
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5884
|
-
|
|
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
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
// Text item (active or disabled)
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
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 };
|