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