mithril-materialized 2.0.0-beta.10 → 2.0.0-beta.11
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 +18 -19
- package/dist/advanced.css +6 -6
- package/dist/button.d.ts +56 -11
- package/dist/components.css +6 -6
- package/dist/core.css +13 -13
- package/dist/datatable.d.ts +291 -0
- package/dist/forms.css +13 -13
- package/dist/index.css +341 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +1074 -442
- package/dist/index.js +1077 -441
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +1077 -441
- package/dist/input.d.ts +0 -1
- package/dist/types.d.ts +226 -0
- package/dist/utilities.css +16 -9
- package/package.json +5 -2
- package/sass/components/_cards.scss +10 -3
- package/sass/components/_datatable.scss +417 -0
- package/sass/components/_global.scss +6 -6
- package/sass/components/forms/_range.scss +5 -5
- package/sass/components/forms/_select.scss +1 -1
- package/sass/materialize.scss +1 -0
package/dist/index.umd.js
CHANGED
|
@@ -420,12 +420,18 @@
|
|
|
420
420
|
return () => {
|
|
421
421
|
return {
|
|
422
422
|
view: ({ attrs }) => {
|
|
423
|
-
const { modalId, tooltip,
|
|
424
|
-
|
|
423
|
+
const { modalId, tooltip, tooltipPosition, tooltipPostion, // Keep for backwards compatibility
|
|
424
|
+
iconName, iconClass, label, className, attr, variant } = attrs, params = __rest(attrs, ["modalId", "tooltip", "tooltipPosition", "tooltipPostion", "iconName", "iconClass", "label", "className", "attr", "variant"]);
|
|
425
|
+
// Handle both new variant prop and legacy modalId/type
|
|
426
|
+
const buttonType = (variant === null || variant === void 0 ? void 0 : variant.type) || (modalId ? 'modal' : type || 'button');
|
|
427
|
+
const modalTarget = (variant === null || variant === void 0 ? void 0 : variant.type) === 'modal' ? variant.modalId : modalId;
|
|
428
|
+
const cn = [modalTarget ? 'modal-trigger' : '', tooltip ? 'tooltipped' : '', defaultClassNames, className]
|
|
425
429
|
.filter(Boolean)
|
|
426
430
|
.join(' ')
|
|
427
431
|
.trim();
|
|
428
|
-
|
|
432
|
+
// Use tooltipPosition if available, fallback to legacy tooltipPostion
|
|
433
|
+
const position = tooltipPosition || tooltipPostion || 'top';
|
|
434
|
+
return m(element, Object.assign(Object.assign(Object.assign({}, params), attr), { className: cn, href: modalTarget ? `#${modalTarget}` : undefined, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined, type: buttonType === 'modal' ? 'button' : buttonType }),
|
|
429
435
|
// `${dca}${modalId ? `.modal-trigger[href=#${modalId}]` : ''}${
|
|
430
436
|
// tooltip ? `.tooltipped[data-position=${tooltipPostion || 'top'}][data-tooltip=${tooltip}]` : ''
|
|
431
437
|
// }${toAttributeString(attr)}`, {}
|
|
@@ -2039,248 +2045,6 @@
|
|
|
2039
2045
|
};
|
|
2040
2046
|
};
|
|
2041
2047
|
|
|
2042
|
-
/** Pure TypeScript Dropdown component - no Materialize dependencies */
|
|
2043
|
-
const Dropdown = () => {
|
|
2044
|
-
const state = {
|
|
2045
|
-
isOpen: false,
|
|
2046
|
-
initialValue: undefined,
|
|
2047
|
-
id: '',
|
|
2048
|
-
focusedIndex: -1,
|
|
2049
|
-
inputRef: null,
|
|
2050
|
-
dropdownRef: null,
|
|
2051
|
-
};
|
|
2052
|
-
const handleKeyDown = (e, items, onchange) => {
|
|
2053
|
-
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
2054
|
-
switch (e.key) {
|
|
2055
|
-
case 'ArrowDown':
|
|
2056
|
-
e.preventDefault();
|
|
2057
|
-
if (!state.isOpen) {
|
|
2058
|
-
state.isOpen = true;
|
|
2059
|
-
state.focusedIndex = 0;
|
|
2060
|
-
}
|
|
2061
|
-
else {
|
|
2062
|
-
state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
|
|
2063
|
-
}
|
|
2064
|
-
break;
|
|
2065
|
-
case 'ArrowUp':
|
|
2066
|
-
e.preventDefault();
|
|
2067
|
-
if (state.isOpen) {
|
|
2068
|
-
state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
|
|
2069
|
-
}
|
|
2070
|
-
break;
|
|
2071
|
-
case 'Enter':
|
|
2072
|
-
case ' ':
|
|
2073
|
-
e.preventDefault();
|
|
2074
|
-
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
2075
|
-
const selectedItem = availableItems[state.focusedIndex];
|
|
2076
|
-
const value = (selectedItem.id || selectedItem.label);
|
|
2077
|
-
state.initialValue = value;
|
|
2078
|
-
state.isOpen = false;
|
|
2079
|
-
state.focusedIndex = -1;
|
|
2080
|
-
if (onchange)
|
|
2081
|
-
onchange(value);
|
|
2082
|
-
}
|
|
2083
|
-
else if (!state.isOpen) {
|
|
2084
|
-
state.isOpen = true;
|
|
2085
|
-
state.focusedIndex = 0;
|
|
2086
|
-
}
|
|
2087
|
-
break;
|
|
2088
|
-
case 'Escape':
|
|
2089
|
-
e.preventDefault();
|
|
2090
|
-
state.isOpen = false;
|
|
2091
|
-
state.focusedIndex = -1;
|
|
2092
|
-
break;
|
|
2093
|
-
}
|
|
2094
|
-
};
|
|
2095
|
-
return {
|
|
2096
|
-
oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
|
|
2097
|
-
state.id = id;
|
|
2098
|
-
state.initialValue = initialValue || checkedId;
|
|
2099
|
-
// Mithril will handle click events through the component structure
|
|
2100
|
-
},
|
|
2101
|
-
view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
|
|
2102
|
-
const { initialValue } = state;
|
|
2103
|
-
const selectedItem = initialValue
|
|
2104
|
-
? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
|
|
2105
|
-
: undefined;
|
|
2106
|
-
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
2107
|
-
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
2108
|
-
return m('.dropdown-wrapper.input-field', { className, key, style }, [
|
|
2109
|
-
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2110
|
-
m(HelperText, { helperText }),
|
|
2111
|
-
m('.select-wrapper', {
|
|
2112
|
-
onclick: disabled
|
|
2113
|
-
? undefined
|
|
2114
|
-
: () => {
|
|
2115
|
-
state.isOpen = !state.isOpen;
|
|
2116
|
-
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
2117
|
-
},
|
|
2118
|
-
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
|
|
2119
|
-
tabindex: disabled ? -1 : 0,
|
|
2120
|
-
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
2121
|
-
'aria-haspopup': 'listbox',
|
|
2122
|
-
role: 'combobox',
|
|
2123
|
-
}, [
|
|
2124
|
-
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
2125
|
-
id: state.id,
|
|
2126
|
-
value: title,
|
|
2127
|
-
oncreate: ({ dom }) => {
|
|
2128
|
-
state.inputRef = dom;
|
|
2129
|
-
},
|
|
2130
|
-
onclick: (e) => {
|
|
2131
|
-
e.preventDefault();
|
|
2132
|
-
e.stopPropagation();
|
|
2133
|
-
if (!disabled) {
|
|
2134
|
-
state.isOpen = !state.isOpen;
|
|
2135
|
-
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
2136
|
-
}
|
|
2137
|
-
},
|
|
2138
|
-
}),
|
|
2139
|
-
// Dropdown Menu using Select component's positioning logic
|
|
2140
|
-
state.isOpen &&
|
|
2141
|
-
m('ul.dropdown-content.select-dropdown', {
|
|
2142
|
-
tabindex: 0,
|
|
2143
|
-
role: 'listbox',
|
|
2144
|
-
'aria-labelledby': state.id,
|
|
2145
|
-
oncreate: ({ dom }) => {
|
|
2146
|
-
state.dropdownRef = dom;
|
|
2147
|
-
},
|
|
2148
|
-
onremove: () => {
|
|
2149
|
-
state.dropdownRef = null;
|
|
2150
|
-
},
|
|
2151
|
-
style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
|
|
2152
|
-
// Convert dropdown items to format expected by getDropdownStyles
|
|
2153
|
-
group: undefined }))), true),
|
|
2154
|
-
}, items.map((item, index) => {
|
|
2155
|
-
if (item.divider) {
|
|
2156
|
-
return m('li.divider', {
|
|
2157
|
-
key: `divider-${index}`,
|
|
2158
|
-
});
|
|
2159
|
-
}
|
|
2160
|
-
const itemIndex = availableItems.indexOf(item);
|
|
2161
|
-
const isFocused = itemIndex === state.focusedIndex;
|
|
2162
|
-
return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
|
|
2163
|
-
item.disabled ? 'disabled' : '',
|
|
2164
|
-
isFocused ? 'focused' : '',
|
|
2165
|
-
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
2166
|
-
]
|
|
2167
|
-
.filter(Boolean)
|
|
2168
|
-
.join(' ') }, (item.disabled
|
|
2169
|
-
? {}
|
|
2170
|
-
: {
|
|
2171
|
-
onclick: (e) => {
|
|
2172
|
-
e.stopPropagation();
|
|
2173
|
-
const value = (item.id || item.label);
|
|
2174
|
-
state.initialValue = value;
|
|
2175
|
-
state.isOpen = false;
|
|
2176
|
-
state.focusedIndex = -1;
|
|
2177
|
-
if (onchange)
|
|
2178
|
-
onchange(value);
|
|
2179
|
-
},
|
|
2180
|
-
})), m('span', {
|
|
2181
|
-
style: {
|
|
2182
|
-
display: 'flex',
|
|
2183
|
-
alignItems: 'center',
|
|
2184
|
-
padding: '14px 16px',
|
|
2185
|
-
},
|
|
2186
|
-
}, [
|
|
2187
|
-
item.iconName
|
|
2188
|
-
? m('i.material-icons', {
|
|
2189
|
-
style: { marginRight: '32px' },
|
|
2190
|
-
}, item.iconName)
|
|
2191
|
-
: undefined,
|
|
2192
|
-
item.label,
|
|
2193
|
-
]));
|
|
2194
|
-
})),
|
|
2195
|
-
m(MaterialIcon, {
|
|
2196
|
-
name: 'caret',
|
|
2197
|
-
direction: 'down',
|
|
2198
|
-
class: 'caret',
|
|
2199
|
-
}),
|
|
2200
|
-
]),
|
|
2201
|
-
]);
|
|
2202
|
-
},
|
|
2203
|
-
};
|
|
2204
|
-
};
|
|
2205
|
-
|
|
2206
|
-
/**
|
|
2207
|
-
* Floating Action Button
|
|
2208
|
-
*/
|
|
2209
|
-
const FloatingActionButton = () => {
|
|
2210
|
-
const state = {
|
|
2211
|
-
isOpen: false,
|
|
2212
|
-
};
|
|
2213
|
-
const handleClickOutside = (e) => {
|
|
2214
|
-
const target = e.target;
|
|
2215
|
-
if (!target.closest('.fixed-action-btn')) {
|
|
2216
|
-
state.isOpen = false;
|
|
2217
|
-
}
|
|
2218
|
-
};
|
|
2219
|
-
return {
|
|
2220
|
-
oncreate: () => {
|
|
2221
|
-
document.addEventListener('click', handleClickOutside);
|
|
2222
|
-
},
|
|
2223
|
-
onremove: () => {
|
|
2224
|
-
document.removeEventListener('click', handleClickOutside);
|
|
2225
|
-
},
|
|
2226
|
-
view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
|
|
2227
|
-
? 'position: absolute; display: inline-block; left: 24px;'
|
|
2228
|
-
: position === 'right' || position === 'inline-right'
|
|
2229
|
-
? 'position: absolute; display: inline-block; right: 24px;'
|
|
2230
|
-
: undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
|
|
2231
|
-
const fabClasses = [
|
|
2232
|
-
'fixed-action-btn',
|
|
2233
|
-
direction ? `direction-${direction}` : '',
|
|
2234
|
-
state.isOpen ? 'active' : '',
|
|
2235
|
-
// hoverEnabled ? 'hover-enabled' : '',
|
|
2236
|
-
]
|
|
2237
|
-
.filter(Boolean)
|
|
2238
|
-
.join(' ');
|
|
2239
|
-
return m('div', {
|
|
2240
|
-
style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
|
|
2241
|
-
}, m(`.${fabClasses}`, {
|
|
2242
|
-
style,
|
|
2243
|
-
onclick: (e) => {
|
|
2244
|
-
e.stopPropagation();
|
|
2245
|
-
if (buttons && buttons.length > 0) {
|
|
2246
|
-
state.isOpen = !state.isOpen;
|
|
2247
|
-
}
|
|
2248
|
-
},
|
|
2249
|
-
onmouseover: hoverEnabled
|
|
2250
|
-
? () => {
|
|
2251
|
-
if (buttons && buttons.length > 0) {
|
|
2252
|
-
state.isOpen = true;
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
: undefined,
|
|
2256
|
-
onmouseleave: hoverEnabled
|
|
2257
|
-
? () => {
|
|
2258
|
-
state.isOpen = false;
|
|
2259
|
-
}
|
|
2260
|
-
: undefined,
|
|
2261
|
-
}, [
|
|
2262
|
-
m('a.btn-floating.btn-large', {
|
|
2263
|
-
className,
|
|
2264
|
-
}, m('i.material-icons', { className: iconClass }, iconName)),
|
|
2265
|
-
buttons &&
|
|
2266
|
-
buttons.length > 0 &&
|
|
2267
|
-
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
|
|
2268
|
-
style: {
|
|
2269
|
-
opacity: state.isOpen ? '1' : '0',
|
|
2270
|
-
transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
|
|
2271
|
-
transition: `all 0.3s ease ${index * 40}ms`,
|
|
2272
|
-
},
|
|
2273
|
-
onclick: (e) => {
|
|
2274
|
-
e.stopPropagation();
|
|
2275
|
-
if (button.onClick)
|
|
2276
|
-
button.onClick(e);
|
|
2277
|
-
},
|
|
2278
|
-
}, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
|
|
2279
|
-
]));
|
|
2280
|
-
},
|
|
2281
|
-
};
|
|
2282
|
-
};
|
|
2283
|
-
|
|
2284
2048
|
/** Character counter component that tracks text length against maxLength */
|
|
2285
2049
|
const CharacterCounter = () => {
|
|
2286
2050
|
return {
|
|
@@ -2454,7 +2218,8 @@
|
|
|
2454
2218
|
: false;
|
|
2455
2219
|
return m('.input-field', { className: cn, style }, [
|
|
2456
2220
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
2457
|
-
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2221
|
+
m('input.validate', Object.assign(Object.assign({}, params), { type, tabindex: 0, id,
|
|
2222
|
+
placeholder,
|
|
2458
2223
|
// attributes,
|
|
2459
2224
|
oncreate: ({ dom }) => {
|
|
2460
2225
|
const input = (state.inputElement = dom);
|
|
@@ -2589,80 +2354,1007 @@
|
|
|
2589
2354
|
]);
|
|
2590
2355
|
},
|
|
2591
2356
|
};
|
|
2592
|
-
};
|
|
2593
|
-
/** Component for entering some text */
|
|
2594
|
-
const TextInput = InputField('text');
|
|
2595
|
-
/** Component for entering a password */
|
|
2596
|
-
const PasswordInput = InputField('password');
|
|
2597
|
-
/** Component for entering a number */
|
|
2598
|
-
const NumberInput = InputField('number');
|
|
2599
|
-
/** Component for entering a URL */
|
|
2600
|
-
const UrlInput = InputField('url');
|
|
2601
|
-
/** Component for entering a color */
|
|
2602
|
-
const ColorInput = InputField('color');
|
|
2603
|
-
/** Component for entering a range */
|
|
2604
|
-
const RangeInput = InputField('range', '.range-field');
|
|
2605
|
-
/** Component for entering an email */
|
|
2606
|
-
const EmailInput = InputField('email');
|
|
2607
|
-
/** Component for uploading a file */
|
|
2608
|
-
const FileInput = () => {
|
|
2609
|
-
let canClear = false;
|
|
2610
|
-
let i;
|
|
2357
|
+
};
|
|
2358
|
+
/** Component for entering some text */
|
|
2359
|
+
const TextInput = InputField('text');
|
|
2360
|
+
/** Component for entering a password */
|
|
2361
|
+
const PasswordInput = InputField('password');
|
|
2362
|
+
/** Component for entering a number */
|
|
2363
|
+
const NumberInput = InputField('number');
|
|
2364
|
+
/** Component for entering a URL */
|
|
2365
|
+
const UrlInput = InputField('url');
|
|
2366
|
+
/** Component for entering a color */
|
|
2367
|
+
const ColorInput = InputField('color');
|
|
2368
|
+
/** Component for entering a range */
|
|
2369
|
+
const RangeInput = InputField('range', '.range-field');
|
|
2370
|
+
/** Component for entering an email */
|
|
2371
|
+
const EmailInput = InputField('email');
|
|
2372
|
+
/** Component for uploading a file */
|
|
2373
|
+
const FileInput = () => {
|
|
2374
|
+
let canClear = false;
|
|
2375
|
+
let i;
|
|
2376
|
+
return {
|
|
2377
|
+
view: ({ attrs }) => {
|
|
2378
|
+
const { multiple, disabled, initialValue, placeholder, onchange, className = 'col s12', accept: acceptedFiles, label = 'File', } = attrs;
|
|
2379
|
+
const accept = acceptedFiles
|
|
2380
|
+
? acceptedFiles instanceof Array
|
|
2381
|
+
? acceptedFiles.join(', ')
|
|
2382
|
+
: acceptedFiles
|
|
2383
|
+
: undefined;
|
|
2384
|
+
return m('.file-field.input-field', {
|
|
2385
|
+
className: attrs.class || className,
|
|
2386
|
+
}, [
|
|
2387
|
+
m('.btn', [
|
|
2388
|
+
m('span', label),
|
|
2389
|
+
m('input[type=file]', {
|
|
2390
|
+
title: label,
|
|
2391
|
+
accept,
|
|
2392
|
+
multiple,
|
|
2393
|
+
disabled,
|
|
2394
|
+
onchange: onchange
|
|
2395
|
+
? (e) => {
|
|
2396
|
+
const i = e.target;
|
|
2397
|
+
if (i && i.files && onchange) {
|
|
2398
|
+
canClear = true;
|
|
2399
|
+
onchange(i.files);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
: undefined,
|
|
2403
|
+
}),
|
|
2404
|
+
]),
|
|
2405
|
+
m('.file-path-wrapper', m('input.file-path.validate[type=text]', {
|
|
2406
|
+
placeholder,
|
|
2407
|
+
oncreate: ({ dom }) => {
|
|
2408
|
+
i = dom;
|
|
2409
|
+
if (initialValue)
|
|
2410
|
+
i.value = initialValue;
|
|
2411
|
+
},
|
|
2412
|
+
})),
|
|
2413
|
+
(canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
|
|
2414
|
+
m('a.waves-effect.waves-teal.btn-flat', {
|
|
2415
|
+
style: {
|
|
2416
|
+
float: 'right',
|
|
2417
|
+
position: 'relative',
|
|
2418
|
+
top: '-3rem',
|
|
2419
|
+
padding: 0,
|
|
2420
|
+
},
|
|
2421
|
+
onclick: () => {
|
|
2422
|
+
canClear = false;
|
|
2423
|
+
i.value = '';
|
|
2424
|
+
onchange && onchange({});
|
|
2425
|
+
},
|
|
2426
|
+
}, m(MaterialIcon, {
|
|
2427
|
+
name: 'close',
|
|
2428
|
+
className: 'close',
|
|
2429
|
+
})),
|
|
2430
|
+
]);
|
|
2431
|
+
},
|
|
2432
|
+
};
|
|
2433
|
+
};
|
|
2434
|
+
|
|
2435
|
+
/** Component to show a check box */
|
|
2436
|
+
const InputCheckbox = () => {
|
|
2437
|
+
return {
|
|
2438
|
+
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
2439
|
+
const checkboxId = inputId || uniqueId();
|
|
2440
|
+
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
2441
|
+
m('input[type=checkbox][tabindex=0]', {
|
|
2442
|
+
id: checkboxId,
|
|
2443
|
+
checked,
|
|
2444
|
+
disabled,
|
|
2445
|
+
onclick: onchange
|
|
2446
|
+
? (e) => {
|
|
2447
|
+
if (e.target && typeof e.target.checked !== 'undefined') {
|
|
2448
|
+
onchange(e.target.checked);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
: undefined,
|
|
2452
|
+
}),
|
|
2453
|
+
label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
|
|
2454
|
+
]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
|
|
2455
|
+
},
|
|
2456
|
+
};
|
|
2457
|
+
};
|
|
2458
|
+
/** A list of checkboxes */
|
|
2459
|
+
const Options = () => {
|
|
2460
|
+
const state = {};
|
|
2461
|
+
const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
|
|
2462
|
+
const selectAll = (options, callback) => {
|
|
2463
|
+
const allIds = options.map((option) => option.id);
|
|
2464
|
+
state.checkedIds = [...allIds];
|
|
2465
|
+
if (callback)
|
|
2466
|
+
callback(allIds);
|
|
2467
|
+
};
|
|
2468
|
+
const selectNone = (callback) => {
|
|
2469
|
+
state.checkedIds = [];
|
|
2470
|
+
if (callback)
|
|
2471
|
+
callback([]);
|
|
2472
|
+
};
|
|
2473
|
+
return {
|
|
2474
|
+
oninit: ({ attrs: { initialValue, checkedId, id } }) => {
|
|
2475
|
+
const iv = checkedId || initialValue;
|
|
2476
|
+
state.checkedId = checkedId;
|
|
2477
|
+
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
2478
|
+
state.componentId = id || uniqueId();
|
|
2479
|
+
},
|
|
2480
|
+
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
|
|
2481
|
+
const onchange = callback
|
|
2482
|
+
? (propId, checked) => {
|
|
2483
|
+
const checkedIds = state.checkedIds.filter((i) => i !== propId);
|
|
2484
|
+
if (checked) {
|
|
2485
|
+
checkedIds.push(propId);
|
|
2486
|
+
}
|
|
2487
|
+
state.checkedIds = checkedIds;
|
|
2488
|
+
callback(checkedIds);
|
|
2489
|
+
}
|
|
2490
|
+
: undefined;
|
|
2491
|
+
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
2492
|
+
const optionsContent = layout === 'horizontal'
|
|
2493
|
+
? m('div.grid-container', options.map((option) => m(InputCheckbox, {
|
|
2494
|
+
disabled: disabled || option.disabled,
|
|
2495
|
+
label: option.label,
|
|
2496
|
+
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
2497
|
+
className: option.className || checkboxClass,
|
|
2498
|
+
checked: isChecked(option.id),
|
|
2499
|
+
description: option.description,
|
|
2500
|
+
inputId: `${state.componentId}-${option.id}`,
|
|
2501
|
+
})))
|
|
2502
|
+
: options.map((option) => m(InputCheckbox, {
|
|
2503
|
+
disabled: disabled || option.disabled,
|
|
2504
|
+
label: option.label,
|
|
2505
|
+
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
2506
|
+
className: option.className || checkboxClass,
|
|
2507
|
+
checked: isChecked(option.id),
|
|
2508
|
+
description: option.description,
|
|
2509
|
+
inputId: `${state.componentId}-${option.id}`,
|
|
2510
|
+
}));
|
|
2511
|
+
return m('div', { id: state.componentId, className: cn, style }, [
|
|
2512
|
+
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
2513
|
+
showSelectAll &&
|
|
2514
|
+
m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
|
|
2515
|
+
m('a', {
|
|
2516
|
+
href: '#',
|
|
2517
|
+
onclick: (e) => {
|
|
2518
|
+
e.preventDefault();
|
|
2519
|
+
selectAll(options, callback);
|
|
2520
|
+
},
|
|
2521
|
+
style: 'margin-right: 15px;',
|
|
2522
|
+
}, 'Select All'),
|
|
2523
|
+
m('a', {
|
|
2524
|
+
href: '#',
|
|
2525
|
+
onclick: (e) => {
|
|
2526
|
+
e.preventDefault();
|
|
2527
|
+
selectNone(callback);
|
|
2528
|
+
},
|
|
2529
|
+
}, 'Select None'),
|
|
2530
|
+
]),
|
|
2531
|
+
description && m(HelperText, { helperText: description }),
|
|
2532
|
+
m('form', { action: '#' }, optionsContent),
|
|
2533
|
+
]);
|
|
2534
|
+
},
|
|
2535
|
+
};
|
|
2536
|
+
};
|
|
2537
|
+
|
|
2538
|
+
const GlobalSearch = () => {
|
|
2539
|
+
return {
|
|
2540
|
+
view: ({ attrs }) => {
|
|
2541
|
+
const { searchPlaceholder, onSearch, i18n } = attrs;
|
|
2542
|
+
return m('.datatable-global-search', m(TextInput, {
|
|
2543
|
+
className: 'datatable-search',
|
|
2544
|
+
label: (i18n === null || i18n === void 0 ? void 0 : i18n.search) || 'Search',
|
|
2545
|
+
placeholder: searchPlaceholder || (i18n === null || i18n === void 0 ? void 0 : i18n.searchPlaceholder) || 'Search table...',
|
|
2546
|
+
oninput: onSearch,
|
|
2547
|
+
}));
|
|
2548
|
+
},
|
|
2549
|
+
};
|
|
2550
|
+
};
|
|
2551
|
+
const TableHeader = () => {
|
|
2552
|
+
return {
|
|
2553
|
+
view: ({ attrs }) => {
|
|
2554
|
+
const { columns, selection, sort, allSelected, helpers } = attrs;
|
|
2555
|
+
return m('thead', m('tr', [
|
|
2556
|
+
// Selection column header
|
|
2557
|
+
...(selection && selection.mode !== 'none'
|
|
2558
|
+
? [
|
|
2559
|
+
m('th.selection-checkbox', [
|
|
2560
|
+
selection.mode === 'multiple' &&
|
|
2561
|
+
m(InputCheckbox, {
|
|
2562
|
+
checked: allSelected,
|
|
2563
|
+
onchange: helpers.handleSelectAll,
|
|
2564
|
+
className: '',
|
|
2565
|
+
}),
|
|
2566
|
+
]),
|
|
2567
|
+
]
|
|
2568
|
+
: []),
|
|
2569
|
+
// Regular columns
|
|
2570
|
+
...columns.map((column) => {
|
|
2571
|
+
const isSorted = (sort === null || sort === void 0 ? void 0 : sort.column) === column.key;
|
|
2572
|
+
const sortDirection = isSorted ? sort.direction : null;
|
|
2573
|
+
return m('th', {
|
|
2574
|
+
class: [
|
|
2575
|
+
column.headerClassName,
|
|
2576
|
+
column.sortable ? 'sortable' : '',
|
|
2577
|
+
isSorted ? `sorted-${sortDirection}` : '',
|
|
2578
|
+
]
|
|
2579
|
+
.filter(Boolean)
|
|
2580
|
+
.join(' '),
|
|
2581
|
+
style: column.width ? { width: column.width } : undefined,
|
|
2582
|
+
onclick: column.sortable ? () => helpers.handleSort(column.key) : undefined,
|
|
2583
|
+
}, [
|
|
2584
|
+
column.title,
|
|
2585
|
+
column.sortable &&
|
|
2586
|
+
m('.sort-indicators', [
|
|
2587
|
+
m('span.sort-icon.sort-asc', {
|
|
2588
|
+
className: isSorted && sortDirection === 'asc' ? 'active' : '',
|
|
2589
|
+
}, '▲'),
|
|
2590
|
+
m('span.sort-icon.sort-desc', {
|
|
2591
|
+
className: isSorted && sortDirection === 'desc' ? 'active' : '',
|
|
2592
|
+
}, '▼'),
|
|
2593
|
+
]),
|
|
2594
|
+
]);
|
|
2595
|
+
}),
|
|
2596
|
+
]));
|
|
2597
|
+
},
|
|
2598
|
+
};
|
|
2599
|
+
};
|
|
2600
|
+
const TableRow = () => {
|
|
2601
|
+
return {
|
|
2602
|
+
view: ({ attrs }) => {
|
|
2603
|
+
const { row, index, columns, selection, onRowClick, onRowDoubleClick, getRowClassName, helpers, data } = attrs;
|
|
2604
|
+
// Calculate the original data index for the row key
|
|
2605
|
+
const originalIndex = data.findIndex((originalRow) => originalRow === row);
|
|
2606
|
+
const rowKey = (selection === null || selection === void 0 ? void 0 : selection.getRowKey(row, originalIndex)) || String(originalIndex);
|
|
2607
|
+
const isSelected = (selection === null || selection === void 0 ? void 0 : selection.selectedKeys.includes(rowKey)) || false;
|
|
2608
|
+
return m('tr', {
|
|
2609
|
+
class: [getRowClassName ? getRowClassName(row, index) : '', isSelected ? 'selected' : '']
|
|
2610
|
+
.filter(Boolean)
|
|
2611
|
+
.join(' ') || undefined,
|
|
2612
|
+
onclick: onRowClick ? (e) => onRowClick(row, index, e) : undefined,
|
|
2613
|
+
ondblclick: onRowDoubleClick ? (e) => onRowDoubleClick(row, index, e) : undefined,
|
|
2614
|
+
}, [
|
|
2615
|
+
// Selection column
|
|
2616
|
+
selection &&
|
|
2617
|
+
selection.mode !== 'none' &&
|
|
2618
|
+
m('td.selection-checkbox', [
|
|
2619
|
+
m(InputCheckbox, {
|
|
2620
|
+
checked: isSelected,
|
|
2621
|
+
onchange: (checked) => helpers.handleSelectionChange(rowKey, checked),
|
|
2622
|
+
className: '',
|
|
2623
|
+
}),
|
|
2624
|
+
]),
|
|
2625
|
+
columns.map((column) => {
|
|
2626
|
+
const value = helpers.getCellValue(row, column);
|
|
2627
|
+
let cellContent;
|
|
2628
|
+
if (column.cellRenderer) {
|
|
2629
|
+
cellContent = m(column.cellRenderer, {
|
|
2630
|
+
value,
|
|
2631
|
+
row,
|
|
2632
|
+
index,
|
|
2633
|
+
column,
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
else if (column.render) {
|
|
2637
|
+
// Backward compatibility with deprecated render function
|
|
2638
|
+
cellContent = column.render(value, row, index);
|
|
2639
|
+
}
|
|
2640
|
+
else {
|
|
2641
|
+
cellContent = String(value || '');
|
|
2642
|
+
}
|
|
2643
|
+
return m('td', {
|
|
2644
|
+
class: [column.className, column.align ? `align-${column.align}` : ''].filter(Boolean).join(' ') ||
|
|
2645
|
+
undefined,
|
|
2646
|
+
}, cellContent);
|
|
2647
|
+
}),
|
|
2648
|
+
]);
|
|
2649
|
+
},
|
|
2650
|
+
};
|
|
2651
|
+
};
|
|
2652
|
+
/**
|
|
2653
|
+
* Standalone Pagination Controls component
|
|
2654
|
+
*
|
|
2655
|
+
* Provides navigation controls for paginated data with customizable text labels.
|
|
2656
|
+
* Includes first page, previous page, next page, last page buttons and page info display.
|
|
2657
|
+
* Can be used independently of DataTable for any paginated content.
|
|
2658
|
+
*
|
|
2659
|
+
* @example
|
|
2660
|
+
* ```typescript
|
|
2661
|
+
* m(PaginationControls, {
|
|
2662
|
+
* pagination: { page: 0, pageSize: 10, total: 100 },
|
|
2663
|
+
* onPaginationChange: (newPagination) => console.log('Page changed:', newPagination),
|
|
2664
|
+
* i18n: { showing: 'Showing', to: 'to', of: 'of', entries: 'entries', page: 'Page' }
|
|
2665
|
+
* })
|
|
2666
|
+
* ```
|
|
2667
|
+
*/
|
|
2668
|
+
const PaginationControls = () => {
|
|
2669
|
+
return {
|
|
2670
|
+
view: ({ attrs }) => {
|
|
2671
|
+
const { pagination, onPaginationChange, i18n } = attrs;
|
|
2672
|
+
if (!pagination)
|
|
2673
|
+
return null;
|
|
2674
|
+
const { page, pageSize, total } = pagination;
|
|
2675
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
2676
|
+
const startItem = page * pageSize + 1;
|
|
2677
|
+
const endItem = Math.min((page + 1) * pageSize, total);
|
|
2678
|
+
const showingText = (i18n === null || i18n === void 0 ? void 0 : i18n.showing) || 'Showing';
|
|
2679
|
+
const toText = (i18n === null || i18n === void 0 ? void 0 : i18n.to) || 'to';
|
|
2680
|
+
const ofText = (i18n === null || i18n === void 0 ? void 0 : i18n.of) || 'of';
|
|
2681
|
+
const entriesText = (i18n === null || i18n === void 0 ? void 0 : i18n.entries) || 'entries';
|
|
2682
|
+
const pageText = (i18n === null || i18n === void 0 ? void 0 : i18n.page) || 'Page';
|
|
2683
|
+
return m('.datatable-pagination', [
|
|
2684
|
+
m('.pagination-info', `${showingText} ${startItem} ${toText} ${endItem} ${ofText} ${total} ${entriesText}`),
|
|
2685
|
+
m('.pagination-controls', [
|
|
2686
|
+
m('button.btn-flat', {
|
|
2687
|
+
disabled: page === 0,
|
|
2688
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: 0 })),
|
|
2689
|
+
}, '⏮'),
|
|
2690
|
+
m('button.btn-flat', {
|
|
2691
|
+
disabled: page === 0,
|
|
2692
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: page - 1 })),
|
|
2693
|
+
}, '◀'),
|
|
2694
|
+
m('span.page-info', `${pageText} ${page + 1} ${ofText} ${totalPages}`),
|
|
2695
|
+
m('button.btn-flat', {
|
|
2696
|
+
disabled: page >= totalPages - 1,
|
|
2697
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: page + 1 })),
|
|
2698
|
+
}, '▶'),
|
|
2699
|
+
m('button.btn-flat', {
|
|
2700
|
+
disabled: page >= totalPages - 1,
|
|
2701
|
+
onclick: () => onPaginationChange(Object.assign(Object.assign({}, pagination), { page: totalPages - 1 })),
|
|
2702
|
+
}, '⏭'),
|
|
2703
|
+
]),
|
|
2704
|
+
]);
|
|
2705
|
+
},
|
|
2706
|
+
};
|
|
2707
|
+
};
|
|
2708
|
+
const TableContent = () => {
|
|
2709
|
+
return {
|
|
2710
|
+
view: ({ attrs: contentAttrs }) => {
|
|
2711
|
+
const { processedData, tableClasses, columns, selection, internalSort, allSelected, someSelected, helpers, onRowClick, onRowDoubleClick, getRowClassName, data, } = contentAttrs;
|
|
2712
|
+
return m('table', {
|
|
2713
|
+
class: tableClasses,
|
|
2714
|
+
}, m(TableHeader(), {
|
|
2715
|
+
columns,
|
|
2716
|
+
selection,
|
|
2717
|
+
sort: internalSort,
|
|
2718
|
+
allSelected,
|
|
2719
|
+
someSelected,
|
|
2720
|
+
helpers,
|
|
2721
|
+
}), m('tbody', processedData.map((row, index) => m(TableRow(), {
|
|
2722
|
+
key: (selection === null || selection === void 0 ? void 0 : selection.getRowKey(row, data.findIndex((originalRow) => originalRow === row))) || index,
|
|
2723
|
+
row,
|
|
2724
|
+
index,
|
|
2725
|
+
columns,
|
|
2726
|
+
selection,
|
|
2727
|
+
onRowClick,
|
|
2728
|
+
onRowDoubleClick,
|
|
2729
|
+
getRowClassName,
|
|
2730
|
+
helpers,
|
|
2731
|
+
data,
|
|
2732
|
+
}))));
|
|
2733
|
+
},
|
|
2734
|
+
};
|
|
2735
|
+
};
|
|
2736
|
+
/**
|
|
2737
|
+
* A comprehensive data table component with sorting, filtering, pagination, and selection capabilities.
|
|
2738
|
+
*
|
|
2739
|
+
* @template T The type of data objects displayed in each row
|
|
2740
|
+
*
|
|
2741
|
+
* @description
|
|
2742
|
+
* The DataTable component provides a feature-rich interface for displaying and interacting with tabular data.
|
|
2743
|
+
* It supports both controlled and uncontrolled modes for all interactive features.
|
|
2744
|
+
*
|
|
2745
|
+
* **Key Features:**
|
|
2746
|
+
* - Sorting: Click column headers to sort data ascending/descending
|
|
2747
|
+
* - Filtering: Global search across filterable columns
|
|
2748
|
+
* - Pagination: Navigate through large datasets with customizable page sizes
|
|
2749
|
+
* - Selection: Single or multiple row selection with callbacks
|
|
2750
|
+
* - Custom rendering: Use cellRenderer for complex cell content
|
|
2751
|
+
* - Responsive: Adapts to different screen sizes
|
|
2752
|
+
* - Internationalization: Customize all UI text
|
|
2753
|
+
* - Accessibility: Proper ARIA attributes and keyboard navigation
|
|
2754
|
+
*
|
|
2755
|
+
* @example Basic usage
|
|
2756
|
+
* ```typescript
|
|
2757
|
+
* interface User { id: number; name: string; email: string; }
|
|
2758
|
+
* const users: User[] = [...];
|
|
2759
|
+
* const columns: DataTableColumn<User>[] = [
|
|
2760
|
+
* { key: 'name', title: 'Name', field: 'name', sortable: true, filterable: true },
|
|
2761
|
+
* { key: 'email', title: 'Email', field: 'email', sortable: true, filterable: true }
|
|
2762
|
+
* ];
|
|
2763
|
+
*
|
|
2764
|
+
* return m(DataTable<User>, { data: users, columns });
|
|
2765
|
+
* ```
|
|
2766
|
+
*
|
|
2767
|
+
* @example Advanced usage with all features
|
|
2768
|
+
* ```typescript
|
|
2769
|
+
* return m(DataTable<User>, {
|
|
2770
|
+
* data: users,
|
|
2771
|
+
* columns,
|
|
2772
|
+
* title: 'User Management',
|
|
2773
|
+
* striped: true,
|
|
2774
|
+
* hoverable: true,
|
|
2775
|
+
* height: 400,
|
|
2776
|
+
*
|
|
2777
|
+
* // Pagination
|
|
2778
|
+
* pagination: { page: 0, pageSize: 10, total: users.length },
|
|
2779
|
+
* onPaginationChange: (pagination) => console.log('Page changed:', pagination),
|
|
2780
|
+
*
|
|
2781
|
+
* // Selection
|
|
2782
|
+
* selection: {
|
|
2783
|
+
* mode: 'multiple',
|
|
2784
|
+
* selectedKeys: [],
|
|
2785
|
+
* getRowKey: (user) => String(user.id),
|
|
2786
|
+
* onSelectionChange: (keys, selectedUsers) => console.log('Selection:', selectedUsers)
|
|
2787
|
+
* },
|
|
2788
|
+
*
|
|
2789
|
+
* // Search
|
|
2790
|
+
* enableGlobalSearch: true,
|
|
2791
|
+
* searchPlaceholder: 'Search users...',
|
|
2792
|
+
*
|
|
2793
|
+
* // Events
|
|
2794
|
+
* onRowClick: (user, index, event) => console.log('Clicked:', user),
|
|
2795
|
+
* onRowDoubleClick: (user) => editUser(user),
|
|
2796
|
+
*
|
|
2797
|
+
* // Styling
|
|
2798
|
+
* getRowClassName: (user) => user.active ? '' : 'inactive-row'
|
|
2799
|
+
* });
|
|
2800
|
+
* ```
|
|
2801
|
+
*
|
|
2802
|
+
* @returns A Mithril component that renders the data table
|
|
2803
|
+
*/
|
|
2804
|
+
const DataTable = () => {
|
|
2805
|
+
const state = {
|
|
2806
|
+
internalSort: undefined,
|
|
2807
|
+
internalFilter: undefined,
|
|
2808
|
+
internalPagination: undefined,
|
|
2809
|
+
processedData: [],
|
|
2810
|
+
tableId: '',
|
|
2811
|
+
// Performance optimization caches
|
|
2812
|
+
lastProcessedHash: ''};
|
|
2813
|
+
// Helper functions
|
|
2814
|
+
const quickDataHash = (data) => {
|
|
2815
|
+
if (data.length === 0)
|
|
2816
|
+
return '0';
|
|
2817
|
+
if (data.length === 1)
|
|
2818
|
+
return '1';
|
|
2819
|
+
// Sample first, middle, and last items for quick hash
|
|
2820
|
+
const first = JSON.stringify(data[0]);
|
|
2821
|
+
const middle = data.length > 2 ? JSON.stringify(data[Math.floor(data.length / 2)]) : '';
|
|
2822
|
+
const last = JSON.stringify(data[data.length - 1]);
|
|
2823
|
+
return `${data.length}-${first.length}-${middle.length}-${last.length}`;
|
|
2824
|
+
};
|
|
2825
|
+
const getDataHash = (attrs) => {
|
|
2826
|
+
const { data, sort, filter, pagination } = attrs;
|
|
2827
|
+
const { internalSort, internalFilter, internalPagination } = state;
|
|
2828
|
+
const hashInputs = {
|
|
2829
|
+
dataLength: data.length,
|
|
2830
|
+
dataHash: quickDataHash(data),
|
|
2831
|
+
sort: sort || internalSort,
|
|
2832
|
+
filter: filter || internalFilter,
|
|
2833
|
+
pagination: pagination || internalPagination,
|
|
2834
|
+
};
|
|
2835
|
+
return JSON.stringify(hashInputs);
|
|
2836
|
+
};
|
|
2837
|
+
const getCellValue = (row, column) => {
|
|
2838
|
+
if (column.field) {
|
|
2839
|
+
return row[column.field];
|
|
2840
|
+
}
|
|
2841
|
+
return row;
|
|
2842
|
+
};
|
|
2843
|
+
const applyFiltering = (data, filter, columns) => {
|
|
2844
|
+
var _a;
|
|
2845
|
+
if (!filter.searchTerm && !filter.columnFilters)
|
|
2846
|
+
return data;
|
|
2847
|
+
const filterableColumns = columns.filter((col) => col.filterable);
|
|
2848
|
+
if (filterableColumns.length === 0 && !filter.searchTerm)
|
|
2849
|
+
return data;
|
|
2850
|
+
const searchTerm = (_a = filter.searchTerm) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
2851
|
+
const hasColumnFilters = filter.columnFilters &&
|
|
2852
|
+
Object.keys(filter.columnFilters).some((key) => {
|
|
2853
|
+
const value = filter.columnFilters[key];
|
|
2854
|
+
return value !== null && value !== undefined && value !== '';
|
|
2855
|
+
});
|
|
2856
|
+
return data.filter((row) => {
|
|
2857
|
+
// Global search
|
|
2858
|
+
if (searchTerm) {
|
|
2859
|
+
const matchesGlobal = filterableColumns.some((column) => {
|
|
2860
|
+
const value = getCellValue(row, column);
|
|
2861
|
+
if (value == null)
|
|
2862
|
+
return false;
|
|
2863
|
+
return String(value).toLowerCase().includes(searchTerm);
|
|
2864
|
+
});
|
|
2865
|
+
if (!matchesGlobal)
|
|
2866
|
+
return false;
|
|
2867
|
+
}
|
|
2868
|
+
// Column-specific filters
|
|
2869
|
+
if (hasColumnFilters) {
|
|
2870
|
+
const matchesColumnFilters = Object.entries(filter.columnFilters).every(([columnKey, filterValue]) => {
|
|
2871
|
+
if (filterValue === null || filterValue === undefined || filterValue === '')
|
|
2872
|
+
return true;
|
|
2873
|
+
const column = columns.find((col) => col.key === columnKey);
|
|
2874
|
+
if (!column)
|
|
2875
|
+
return true;
|
|
2876
|
+
const value = getCellValue(row, column);
|
|
2877
|
+
if (value == null)
|
|
2878
|
+
return false;
|
|
2879
|
+
return String(value).toLowerCase().includes(String(filterValue).toLowerCase());
|
|
2880
|
+
});
|
|
2881
|
+
if (!matchesColumnFilters)
|
|
2882
|
+
return false;
|
|
2883
|
+
}
|
|
2884
|
+
return true;
|
|
2885
|
+
});
|
|
2886
|
+
};
|
|
2887
|
+
const applySorting = (data, sort, columns) => {
|
|
2888
|
+
const column = columns.find((col) => col.key === sort.column);
|
|
2889
|
+
if (!column || !column.sortable)
|
|
2890
|
+
return data;
|
|
2891
|
+
const multiplier = sort.direction === 'asc' ? 1 : -1;
|
|
2892
|
+
return [...data].sort((a, b) => {
|
|
2893
|
+
const aValue = getCellValue(a, column);
|
|
2894
|
+
const bValue = getCellValue(b, column);
|
|
2895
|
+
// Handle null/undefined values
|
|
2896
|
+
if (aValue == null && bValue == null)
|
|
2897
|
+
return 0;
|
|
2898
|
+
if (aValue == null)
|
|
2899
|
+
return -1 * multiplier;
|
|
2900
|
+
if (bValue == null)
|
|
2901
|
+
return 1 * multiplier;
|
|
2902
|
+
// Type-specific comparisons
|
|
2903
|
+
const aType = typeof aValue;
|
|
2904
|
+
const bType = typeof bValue;
|
|
2905
|
+
if (aType === bType) {
|
|
2906
|
+
if (aType === 'number') {
|
|
2907
|
+
return (aValue - bValue) * multiplier;
|
|
2908
|
+
}
|
|
2909
|
+
if (aType === 'boolean') {
|
|
2910
|
+
return (aValue === bValue ? 0 : aValue ? 1 : -1) * multiplier;
|
|
2911
|
+
}
|
|
2912
|
+
if (aValue instanceof Date && bValue instanceof Date) {
|
|
2913
|
+
return (aValue.getTime() - bValue.getTime()) * multiplier;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
// Fallback to string comparison
|
|
2917
|
+
return String(aValue).localeCompare(String(bValue)) * multiplier;
|
|
2918
|
+
});
|
|
2919
|
+
};
|
|
2920
|
+
const processData = (attrs) => {
|
|
2921
|
+
const { data } = attrs;
|
|
2922
|
+
const { internalSort, internalFilter, internalPagination } = state;
|
|
2923
|
+
let processedData = [...data];
|
|
2924
|
+
// Apply filtering
|
|
2925
|
+
if (internalFilter) {
|
|
2926
|
+
processedData = applyFiltering(processedData, internalFilter, attrs.columns);
|
|
2927
|
+
}
|
|
2928
|
+
// Apply sorting
|
|
2929
|
+
if (internalSort) {
|
|
2930
|
+
processedData = applySorting(processedData, internalSort, attrs.columns);
|
|
2931
|
+
}
|
|
2932
|
+
// Update total count for pagination
|
|
2933
|
+
if (internalPagination) {
|
|
2934
|
+
state.internalPagination = Object.assign(Object.assign({}, internalPagination), { total: processedData.length });
|
|
2935
|
+
}
|
|
2936
|
+
// Apply pagination
|
|
2937
|
+
if (internalPagination) {
|
|
2938
|
+
const { page, pageSize } = internalPagination;
|
|
2939
|
+
const start = page * pageSize;
|
|
2940
|
+
const end = start + pageSize;
|
|
2941
|
+
processedData = processedData.slice(start, end);
|
|
2942
|
+
}
|
|
2943
|
+
state.processedData = processedData;
|
|
2944
|
+
};
|
|
2945
|
+
// Create stable helper functions that don't get recreated on every render
|
|
2946
|
+
const createHelpers = (attrs) => ({
|
|
2947
|
+
getCellValue,
|
|
2948
|
+
handleSort: (columnKey) => {
|
|
2949
|
+
var _a;
|
|
2950
|
+
const column = attrs.columns.find((col) => col.key === columnKey);
|
|
2951
|
+
if (!column || !column.sortable)
|
|
2952
|
+
return;
|
|
2953
|
+
const currentSort = state.internalSort;
|
|
2954
|
+
let newSort;
|
|
2955
|
+
if ((currentSort === null || currentSort === void 0 ? void 0 : currentSort.column) === columnKey) {
|
|
2956
|
+
// Toggle direction
|
|
2957
|
+
if (currentSort.direction === 'asc') {
|
|
2958
|
+
newSort = { column: columnKey, direction: 'desc' };
|
|
2959
|
+
}
|
|
2960
|
+
else {
|
|
2961
|
+
newSort = { column: columnKey, direction: 'asc' };
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
else {
|
|
2965
|
+
// New column sort
|
|
2966
|
+
newSort = { column: columnKey, direction: 'asc' };
|
|
2967
|
+
}
|
|
2968
|
+
state.internalSort = newSort;
|
|
2969
|
+
(_a = attrs.onSortChange) === null || _a === void 0 ? void 0 : _a.call(attrs, newSort);
|
|
2970
|
+
},
|
|
2971
|
+
handleGlobalSearch: (searchTerm) => {
|
|
2972
|
+
var _a;
|
|
2973
|
+
const newFilter = Object.assign(Object.assign({}, state.internalFilter), { searchTerm });
|
|
2974
|
+
state.internalFilter = newFilter;
|
|
2975
|
+
// Reset pagination to first page when filtering
|
|
2976
|
+
if (state.internalPagination) {
|
|
2977
|
+
state.internalPagination = Object.assign(Object.assign({}, state.internalPagination), { page: 0 });
|
|
2978
|
+
}
|
|
2979
|
+
(_a = attrs.onFilterChange) === null || _a === void 0 ? void 0 : _a.call(attrs, newFilter);
|
|
2980
|
+
},
|
|
2981
|
+
handleSelectionChange: (rowKey, selected) => {
|
|
2982
|
+
var _a, _b;
|
|
2983
|
+
if (!attrs.selection)
|
|
2984
|
+
return;
|
|
2985
|
+
let newSelectedKeys;
|
|
2986
|
+
if (attrs.selection.mode === 'single') {
|
|
2987
|
+
newSelectedKeys = selected ? [rowKey] : [];
|
|
2988
|
+
}
|
|
2989
|
+
else if (attrs.selection.mode === 'multiple') {
|
|
2990
|
+
if (selected) {
|
|
2991
|
+
newSelectedKeys = [...attrs.selection.selectedKeys, rowKey];
|
|
2992
|
+
}
|
|
2993
|
+
else {
|
|
2994
|
+
newSelectedKeys = attrs.selection.selectedKeys.filter((key) => key !== rowKey);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
else {
|
|
2998
|
+
return; // No selection mode
|
|
2999
|
+
}
|
|
3000
|
+
// Get selected rows
|
|
3001
|
+
const selectedRows = attrs.data.filter((row, index) => {
|
|
3002
|
+
const key = attrs.selection.getRowKey(row, index);
|
|
3003
|
+
return newSelectedKeys.includes(key);
|
|
3004
|
+
});
|
|
3005
|
+
(_b = (_a = attrs.selection).onSelectionChange) === null || _b === void 0 ? void 0 : _b.call(_a, newSelectedKeys, selectedRows);
|
|
3006
|
+
},
|
|
3007
|
+
handleSelectAll: (selected) => {
|
|
3008
|
+
var _a, _b;
|
|
3009
|
+
if (!attrs.selection || attrs.selection.mode !== 'multiple')
|
|
3010
|
+
return;
|
|
3011
|
+
let newSelectedKeys;
|
|
3012
|
+
if (selected) {
|
|
3013
|
+
// Select all visible rows
|
|
3014
|
+
newSelectedKeys = state.processedData.map((row) => {
|
|
3015
|
+
const originalIndex = attrs.data.findIndex((originalRow) => originalRow === row);
|
|
3016
|
+
return attrs.selection.getRowKey(row, originalIndex);
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
else {
|
|
3020
|
+
newSelectedKeys = [];
|
|
3021
|
+
}
|
|
3022
|
+
const selectedRows = attrs.data.filter((row, index) => {
|
|
3023
|
+
const key = attrs.selection.getRowKey(row, index);
|
|
3024
|
+
return newSelectedKeys.includes(key);
|
|
3025
|
+
});
|
|
3026
|
+
(_b = (_a = attrs.selection).onSelectionChange) === null || _b === void 0 ? void 0 : _b.call(_a, newSelectedKeys, selectedRows);
|
|
3027
|
+
},
|
|
3028
|
+
});
|
|
3029
|
+
return {
|
|
3030
|
+
oninit(vnodeInit) {
|
|
3031
|
+
const { sort, filter, pagination } = vnodeInit.attrs;
|
|
3032
|
+
state.tableId = uniqueId();
|
|
3033
|
+
state.internalSort = sort || undefined;
|
|
3034
|
+
state.internalFilter = filter || { searchTerm: '', columnFilters: {} };
|
|
3035
|
+
state.internalPagination = pagination || undefined;
|
|
3036
|
+
processData(vnodeInit.attrs);
|
|
3037
|
+
},
|
|
3038
|
+
onbeforeupdate(vnodeUpdate) {
|
|
3039
|
+
// Only reprocess data if inputs have changed
|
|
3040
|
+
const currentHash = getDataHash(vnodeUpdate.attrs);
|
|
3041
|
+
if (currentHash !== state.lastProcessedHash) {
|
|
3042
|
+
processData(vnodeUpdate.attrs);
|
|
3043
|
+
state.lastProcessedHash = currentHash;
|
|
3044
|
+
}
|
|
3045
|
+
},
|
|
3046
|
+
view(vnodeView) {
|
|
3047
|
+
const attrs = vnodeView.attrs;
|
|
3048
|
+
const { loading, emptyMessage, striped, hoverable, responsive, centered, className, id, title, height, enableGlobalSearch, searchPlaceholder, selection, columns, onRowClick, onRowDoubleClick, getRowClassName, data, onPaginationChange, i18n, } = attrs;
|
|
3049
|
+
const { processedData, tableId, internalSort, internalPagination } = state;
|
|
3050
|
+
if (loading) {
|
|
3051
|
+
return m('.datatable-loading', [
|
|
3052
|
+
m('.preloader-wrapper.small.active', m('.spinner-layer.spinner-blue-only', m('.circle-clipper.left', m('.circle')))),
|
|
3053
|
+
m('p', (i18n === null || i18n === void 0 ? void 0 : i18n.loading) || 'Loading...'),
|
|
3054
|
+
]);
|
|
3055
|
+
}
|
|
3056
|
+
// Create stable helpers object using the factory function
|
|
3057
|
+
const helpers = createHelpers(attrs);
|
|
3058
|
+
// Calculate selection state for "select all" checkbox
|
|
3059
|
+
let allSelected = false;
|
|
3060
|
+
let someSelected = false;
|
|
3061
|
+
if (selection && selection.mode === 'multiple') {
|
|
3062
|
+
const visibleRowKeys = processedData.map((row) => {
|
|
3063
|
+
const originalIndex = data.findIndex((originalRow) => originalRow === row);
|
|
3064
|
+
return selection.getRowKey(row, originalIndex);
|
|
3065
|
+
});
|
|
3066
|
+
const selectedVisibleKeys = visibleRowKeys.filter((key) => selection.selectedKeys.includes(key));
|
|
3067
|
+
allSelected = visibleRowKeys.length > 0 && selectedVisibleKeys.length === visibleRowKeys.length;
|
|
3068
|
+
someSelected = selectedVisibleKeys.length > 0 && selectedVisibleKeys.length < visibleRowKeys.length;
|
|
3069
|
+
}
|
|
3070
|
+
const tableClasses = [
|
|
3071
|
+
'datatable',
|
|
3072
|
+
striped ? 'striped' : '',
|
|
3073
|
+
hoverable ? 'highlight' : '',
|
|
3074
|
+
responsive ? 'responsive-table' : '',
|
|
3075
|
+
centered ? 'centered' : '',
|
|
3076
|
+
className || '',
|
|
3077
|
+
]
|
|
3078
|
+
.filter(Boolean)
|
|
3079
|
+
.join(' ');
|
|
3080
|
+
return m('.datatable-container', {
|
|
3081
|
+
id: id || tableId,
|
|
3082
|
+
}, title && m('h5.datatable-title', title), enableGlobalSearch &&
|
|
3083
|
+
m(GlobalSearch, {
|
|
3084
|
+
searchPlaceholder,
|
|
3085
|
+
onSearch: helpers.handleGlobalSearch,
|
|
3086
|
+
i18n,
|
|
3087
|
+
}), m('.datatable-wrapper', {
|
|
3088
|
+
style: {
|
|
3089
|
+
maxHeight: height ? `${height}px` : undefined,
|
|
3090
|
+
overflowY: height ? 'auto' : undefined,
|
|
3091
|
+
},
|
|
3092
|
+
}, processedData.length === 0
|
|
3093
|
+
? m('.datatable-empty', emptyMessage || (i18n === null || i18n === void 0 ? void 0 : i18n.noDataAvailable) || 'No data available')
|
|
3094
|
+
: m(TableContent(), {
|
|
3095
|
+
processedData,
|
|
3096
|
+
height,
|
|
3097
|
+
tableClasses,
|
|
3098
|
+
columns,
|
|
3099
|
+
selection,
|
|
3100
|
+
internalSort,
|
|
3101
|
+
allSelected,
|
|
3102
|
+
someSelected,
|
|
3103
|
+
helpers,
|
|
3104
|
+
onRowClick,
|
|
3105
|
+
onRowDoubleClick,
|
|
3106
|
+
getRowClassName,
|
|
3107
|
+
data,
|
|
3108
|
+
})), m(PaginationControls, {
|
|
3109
|
+
pagination: internalPagination,
|
|
3110
|
+
onPaginationChange: (pagination) => {
|
|
3111
|
+
state.internalPagination = pagination;
|
|
3112
|
+
onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(pagination);
|
|
3113
|
+
},
|
|
3114
|
+
i18n,
|
|
3115
|
+
}));
|
|
3116
|
+
},
|
|
3117
|
+
};
|
|
3118
|
+
};
|
|
3119
|
+
|
|
3120
|
+
/** Pure TypeScript Dropdown component - no Materialize dependencies */
|
|
3121
|
+
const Dropdown = () => {
|
|
3122
|
+
const state = {
|
|
3123
|
+
isOpen: false,
|
|
3124
|
+
initialValue: undefined,
|
|
3125
|
+
id: '',
|
|
3126
|
+
focusedIndex: -1,
|
|
3127
|
+
inputRef: null,
|
|
3128
|
+
dropdownRef: null,
|
|
3129
|
+
};
|
|
3130
|
+
const handleKeyDown = (e, items, onchange) => {
|
|
3131
|
+
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
3132
|
+
switch (e.key) {
|
|
3133
|
+
case 'ArrowDown':
|
|
3134
|
+
e.preventDefault();
|
|
3135
|
+
if (!state.isOpen) {
|
|
3136
|
+
state.isOpen = true;
|
|
3137
|
+
state.focusedIndex = 0;
|
|
3138
|
+
}
|
|
3139
|
+
else {
|
|
3140
|
+
state.focusedIndex = Math.min(state.focusedIndex + 1, availableItems.length - 1);
|
|
3141
|
+
}
|
|
3142
|
+
break;
|
|
3143
|
+
case 'ArrowUp':
|
|
3144
|
+
e.preventDefault();
|
|
3145
|
+
if (state.isOpen) {
|
|
3146
|
+
state.focusedIndex = Math.max(state.focusedIndex - 1, 0);
|
|
3147
|
+
}
|
|
3148
|
+
break;
|
|
3149
|
+
case 'Enter':
|
|
3150
|
+
case ' ':
|
|
3151
|
+
e.preventDefault();
|
|
3152
|
+
if (state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < availableItems.length) {
|
|
3153
|
+
const selectedItem = availableItems[state.focusedIndex];
|
|
3154
|
+
const value = (selectedItem.id || selectedItem.label);
|
|
3155
|
+
state.initialValue = value;
|
|
3156
|
+
state.isOpen = false;
|
|
3157
|
+
state.focusedIndex = -1;
|
|
3158
|
+
if (onchange)
|
|
3159
|
+
onchange(value);
|
|
3160
|
+
}
|
|
3161
|
+
else if (!state.isOpen) {
|
|
3162
|
+
state.isOpen = true;
|
|
3163
|
+
state.focusedIndex = 0;
|
|
3164
|
+
}
|
|
3165
|
+
break;
|
|
3166
|
+
case 'Escape':
|
|
3167
|
+
e.preventDefault();
|
|
3168
|
+
state.isOpen = false;
|
|
3169
|
+
state.focusedIndex = -1;
|
|
3170
|
+
break;
|
|
3171
|
+
}
|
|
3172
|
+
};
|
|
3173
|
+
return {
|
|
3174
|
+
oninit: ({ attrs: { id = uniqueId(), initialValue, checkedId } }) => {
|
|
3175
|
+
state.id = id;
|
|
3176
|
+
state.initialValue = initialValue || checkedId;
|
|
3177
|
+
// Mithril will handle click events through the component structure
|
|
3178
|
+
},
|
|
3179
|
+
view: ({ attrs: { key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12' }, }) => {
|
|
3180
|
+
const { initialValue } = state;
|
|
3181
|
+
const selectedItem = initialValue
|
|
3182
|
+
? items.filter((i) => (i.id ? i.id === initialValue : i.label === initialValue)).shift()
|
|
3183
|
+
: undefined;
|
|
3184
|
+
const title = selectedItem ? selectedItem.label : label || 'Select';
|
|
3185
|
+
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
3186
|
+
return m('.dropdown-wrapper.input-field', { className, key, style }, [
|
|
3187
|
+
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3188
|
+
m(HelperText, { helperText }),
|
|
3189
|
+
m('.select-wrapper', {
|
|
3190
|
+
onclick: disabled
|
|
3191
|
+
? undefined
|
|
3192
|
+
: () => {
|
|
3193
|
+
state.isOpen = !state.isOpen;
|
|
3194
|
+
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
3195
|
+
},
|
|
3196
|
+
onkeydown: disabled ? undefined : (e) => handleKeyDown(e, items, onchange),
|
|
3197
|
+
tabindex: disabled ? -1 : 0,
|
|
3198
|
+
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
3199
|
+
'aria-haspopup': 'listbox',
|
|
3200
|
+
role: 'combobox',
|
|
3201
|
+
}, [
|
|
3202
|
+
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
3203
|
+
id: state.id,
|
|
3204
|
+
value: title,
|
|
3205
|
+
oncreate: ({ dom }) => {
|
|
3206
|
+
state.inputRef = dom;
|
|
3207
|
+
},
|
|
3208
|
+
onclick: (e) => {
|
|
3209
|
+
e.preventDefault();
|
|
3210
|
+
e.stopPropagation();
|
|
3211
|
+
if (!disabled) {
|
|
3212
|
+
state.isOpen = !state.isOpen;
|
|
3213
|
+
state.focusedIndex = state.isOpen ? 0 : -1;
|
|
3214
|
+
}
|
|
3215
|
+
},
|
|
3216
|
+
}),
|
|
3217
|
+
// Dropdown Menu using Select component's positioning logic
|
|
3218
|
+
state.isOpen &&
|
|
3219
|
+
m('ul.dropdown-content.select-dropdown', {
|
|
3220
|
+
tabindex: 0,
|
|
3221
|
+
role: 'listbox',
|
|
3222
|
+
'aria-labelledby': state.id,
|
|
3223
|
+
oncreate: ({ dom }) => {
|
|
3224
|
+
state.dropdownRef = dom;
|
|
3225
|
+
},
|
|
3226
|
+
onremove: () => {
|
|
3227
|
+
state.dropdownRef = null;
|
|
3228
|
+
},
|
|
3229
|
+
style: getDropdownStyles(state.inputRef, true, items.map((item) => (Object.assign(Object.assign({}, item), {
|
|
3230
|
+
// Convert dropdown items to format expected by getDropdownStyles
|
|
3231
|
+
group: undefined }))), true),
|
|
3232
|
+
}, items.map((item, index) => {
|
|
3233
|
+
if (item.divider) {
|
|
3234
|
+
return m('li.divider', {
|
|
3235
|
+
key: `divider-${index}`,
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
const itemIndex = availableItems.indexOf(item);
|
|
3239
|
+
const isFocused = itemIndex === state.focusedIndex;
|
|
3240
|
+
return m('li', Object.assign({ key: item.id || `item-${index}`, class: [
|
|
3241
|
+
item.disabled ? 'disabled' : '',
|
|
3242
|
+
isFocused ? 'focused' : '',
|
|
3243
|
+
(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) === item.id || (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.label) === item.label ? 'selected' : '',
|
|
3244
|
+
]
|
|
3245
|
+
.filter(Boolean)
|
|
3246
|
+
.join(' ') }, (item.disabled
|
|
3247
|
+
? {}
|
|
3248
|
+
: {
|
|
3249
|
+
onclick: (e) => {
|
|
3250
|
+
e.stopPropagation();
|
|
3251
|
+
const value = (item.id || item.label);
|
|
3252
|
+
state.initialValue = value;
|
|
3253
|
+
state.isOpen = false;
|
|
3254
|
+
state.focusedIndex = -1;
|
|
3255
|
+
if (onchange)
|
|
3256
|
+
onchange(value);
|
|
3257
|
+
},
|
|
3258
|
+
})), m('span', {
|
|
3259
|
+
style: {
|
|
3260
|
+
display: 'flex',
|
|
3261
|
+
alignItems: 'center',
|
|
3262
|
+
padding: '14px 16px',
|
|
3263
|
+
},
|
|
3264
|
+
}, [
|
|
3265
|
+
item.iconName
|
|
3266
|
+
? m('i.material-icons', {
|
|
3267
|
+
style: { marginRight: '32px' },
|
|
3268
|
+
}, item.iconName)
|
|
3269
|
+
: undefined,
|
|
3270
|
+
item.label,
|
|
3271
|
+
]));
|
|
3272
|
+
})),
|
|
3273
|
+
m(MaterialIcon, {
|
|
3274
|
+
name: 'caret',
|
|
3275
|
+
direction: 'down',
|
|
3276
|
+
class: 'caret',
|
|
3277
|
+
}),
|
|
3278
|
+
]),
|
|
3279
|
+
]);
|
|
3280
|
+
},
|
|
3281
|
+
};
|
|
3282
|
+
};
|
|
3283
|
+
|
|
3284
|
+
/**
|
|
3285
|
+
* Floating Action Button
|
|
3286
|
+
*/
|
|
3287
|
+
const FloatingActionButton = () => {
|
|
3288
|
+
const state = {
|
|
3289
|
+
isOpen: false,
|
|
3290
|
+
};
|
|
3291
|
+
const handleClickOutside = (e) => {
|
|
3292
|
+
const target = e.target;
|
|
3293
|
+
if (!target.closest('.fixed-action-btn')) {
|
|
3294
|
+
state.isOpen = false;
|
|
3295
|
+
}
|
|
3296
|
+
};
|
|
2611
3297
|
return {
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3298
|
+
oncreate: () => {
|
|
3299
|
+
document.addEventListener('click', handleClickOutside);
|
|
3300
|
+
},
|
|
3301
|
+
onremove: () => {
|
|
3302
|
+
document.removeEventListener('click', handleClickOutside);
|
|
3303
|
+
},
|
|
3304
|
+
view: ({ attrs: { className, iconName, iconClass, position, style = position === 'left' || position === 'inline-left'
|
|
3305
|
+
? 'position: absolute; display: inline-block; left: 24px;'
|
|
3306
|
+
: position === 'right' || position === 'inline-right'
|
|
3307
|
+
? 'position: absolute; display: inline-block; right: 24px;'
|
|
3308
|
+
: undefined, buttons, direction = 'top', hoverEnabled = true, }, }) => {
|
|
3309
|
+
const fabClasses = [
|
|
3310
|
+
'fixed-action-btn',
|
|
3311
|
+
direction ? `direction-${direction}` : '',
|
|
3312
|
+
state.isOpen ? 'active' : '',
|
|
3313
|
+
// hoverEnabled ? 'hover-enabled' : '',
|
|
3314
|
+
]
|
|
3315
|
+
.filter(Boolean)
|
|
3316
|
+
.join(' ');
|
|
3317
|
+
return m('div', {
|
|
3318
|
+
style: position === 'inline-right' || position === 'inline-left' ? 'position: relative; height: 70px;' : undefined,
|
|
3319
|
+
}, m(`.${fabClasses}`, {
|
|
3320
|
+
style,
|
|
3321
|
+
onclick: (e) => {
|
|
3322
|
+
e.stopPropagation();
|
|
3323
|
+
if (buttons && buttons.length > 0) {
|
|
3324
|
+
state.isOpen = !state.isOpen;
|
|
3325
|
+
}
|
|
3326
|
+
},
|
|
3327
|
+
onmouseover: hoverEnabled
|
|
3328
|
+
? () => {
|
|
3329
|
+
if (buttons && buttons.length > 0) {
|
|
3330
|
+
state.isOpen = true;
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
: undefined,
|
|
3334
|
+
onmouseleave: hoverEnabled
|
|
3335
|
+
? () => {
|
|
3336
|
+
state.isOpen = false;
|
|
3337
|
+
}
|
|
3338
|
+
: undefined,
|
|
2621
3339
|
}, [
|
|
2622
|
-
m('.btn',
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
disabled,
|
|
2629
|
-
onchange: onchange
|
|
2630
|
-
? (e) => {
|
|
2631
|
-
const i = e.target;
|
|
2632
|
-
if (i && i.files && onchange) {
|
|
2633
|
-
canClear = true;
|
|
2634
|
-
onchange(i.files);
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
: undefined,
|
|
2638
|
-
}),
|
|
2639
|
-
]),
|
|
2640
|
-
m('.file-path-wrapper', m('input.file-path.validate[type=text]', {
|
|
2641
|
-
placeholder,
|
|
2642
|
-
oncreate: ({ dom }) => {
|
|
2643
|
-
i = dom;
|
|
2644
|
-
if (initialValue)
|
|
2645
|
-
i.value = initialValue;
|
|
2646
|
-
},
|
|
2647
|
-
})),
|
|
2648
|
-
(canClear || (i === null || i === void 0 ? void 0 : i.value)) &&
|
|
2649
|
-
m('a.waves-effect.waves-teal.btn-flat', {
|
|
3340
|
+
m('a.btn-floating.btn-large', {
|
|
3341
|
+
className,
|
|
3342
|
+
}, m('i.material-icons', { className: iconClass }, iconName)),
|
|
3343
|
+
buttons &&
|
|
3344
|
+
buttons.length > 0 &&
|
|
3345
|
+
m('ul', buttons.map((button, index) => m('li', m(`a.btn-floating.${button.className || 'red'}`, {
|
|
2650
3346
|
style: {
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
padding: 0,
|
|
3347
|
+
opacity: state.isOpen ? '1' : '0',
|
|
3348
|
+
transform: state.isOpen ? 'scale(1)' : 'scale(0.4)',
|
|
3349
|
+
transition: `all 0.3s ease ${index * 40}ms`,
|
|
2655
3350
|
},
|
|
2656
|
-
onclick: () => {
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
3351
|
+
onclick: (e) => {
|
|
3352
|
+
e.stopPropagation();
|
|
3353
|
+
if (button.onClick)
|
|
3354
|
+
button.onClick(e);
|
|
2660
3355
|
},
|
|
2661
|
-
}, m(
|
|
2662
|
-
|
|
2663
|
-
className: 'close',
|
|
2664
|
-
})),
|
|
2665
|
-
]);
|
|
3356
|
+
}, m('i.material-icons', { className: button.iconClass }, button.iconName))))),
|
|
3357
|
+
]));
|
|
2666
3358
|
},
|
|
2667
3359
|
};
|
|
2668
3360
|
};
|
|
@@ -2948,21 +3640,23 @@
|
|
|
2948
3640
|
role: 'dialog',
|
|
2949
3641
|
'aria-labelledby': `${id}-title`,
|
|
2950
3642
|
'aria-describedby': description ? `${id}-desc` : undefined,
|
|
2951
|
-
style: {
|
|
2952
|
-
|
|
2953
|
-
|
|
3643
|
+
style: Object.assign(Object.assign({ display: state.isOpen ? 'flex' : 'none', position: 'fixed' }, (bottomSheet ? {
|
|
3644
|
+
// Bottom sheet positioning
|
|
3645
|
+
top: 'auto',
|
|
3646
|
+
bottom: '0',
|
|
3647
|
+
left: '0',
|
|
3648
|
+
right: '0',
|
|
3649
|
+
transform: 'none',
|
|
3650
|
+
maxWidth: '100%',
|
|
3651
|
+
borderRadius: '8px 8px 0 0',
|
|
3652
|
+
} : {
|
|
3653
|
+
// Regular modal positioning
|
|
2954
3654
|
top: '50%',
|
|
2955
3655
|
left: '50%',
|
|
2956
3656
|
transform: 'translate(-50%, -50%)',
|
|
2957
|
-
backgroundColor: '#fff',
|
|
2958
|
-
borderRadius: '4px',
|
|
2959
3657
|
maxWidth: '75%',
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
zIndex: '1003',
|
|
2963
|
-
padding: '0',
|
|
2964
|
-
boxShadow: '0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.20)',
|
|
2965
|
-
},
|
|
3658
|
+
borderRadius: '4px',
|
|
3659
|
+
})), { backgroundColor: 'var(--mm-modal-background, #fff)', maxHeight: '85%', overflow: 'auto', zIndex: '1003', padding: '0', flexDirection: 'column', boxShadow: '0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.20)' }),
|
|
2966
3660
|
onclick: (e) => e.stopPropagation(), // Prevent backdrop click when clicking inside modal
|
|
2967
3661
|
}, [
|
|
2968
3662
|
// Close button
|
|
@@ -2982,7 +3676,12 @@
|
|
|
2982
3676
|
}, '×'),
|
|
2983
3677
|
// Modal content
|
|
2984
3678
|
m('.modal-content', {
|
|
2985
|
-
style: {
|
|
3679
|
+
style: {
|
|
3680
|
+
padding: '24px',
|
|
3681
|
+
paddingTop: showCloseButton ? '48px' : '24px',
|
|
3682
|
+
minHeight: 'auto',
|
|
3683
|
+
flex: '1 1 auto',
|
|
3684
|
+
},
|
|
2986
3685
|
}, [
|
|
2987
3686
|
m('h4', { id: `${id}-title`, style: { margin: '0 0 20px 0' } }, title),
|
|
2988
3687
|
description &&
|
|
@@ -2994,7 +3693,7 @@
|
|
|
2994
3693
|
m('.modal-footer', {
|
|
2995
3694
|
style: {
|
|
2996
3695
|
padding: '4px 6px',
|
|
2997
|
-
borderTop: '1px solid rgba(160,160,160,0.2)',
|
|
3696
|
+
borderTop: '1px solid var(--mm-border-color, rgba(160,160,160,0.2))',
|
|
2998
3697
|
textAlign: 'right',
|
|
2999
3698
|
},
|
|
3000
3699
|
}, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
|
|
@@ -3008,109 +3707,6 @@
|
|
|
3008
3707
|
};
|
|
3009
3708
|
};
|
|
3010
3709
|
|
|
3011
|
-
/** Component to show a check box */
|
|
3012
|
-
const InputCheckbox = () => {
|
|
3013
|
-
return {
|
|
3014
|
-
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3015
|
-
const checkboxId = inputId || uniqueId();
|
|
3016
|
-
return m(`p`, { className, style }, m('label', { for: checkboxId }, [
|
|
3017
|
-
m('input[type=checkbox][tabindex=0]', {
|
|
3018
|
-
id: checkboxId,
|
|
3019
|
-
checked,
|
|
3020
|
-
disabled,
|
|
3021
|
-
onclick: onchange
|
|
3022
|
-
? (e) => {
|
|
3023
|
-
if (e.target && typeof e.target.checked !== 'undefined') {
|
|
3024
|
-
onchange(e.target.checked);
|
|
3025
|
-
}
|
|
3026
|
-
}
|
|
3027
|
-
: undefined,
|
|
3028
|
-
}),
|
|
3029
|
-
label ? (typeof label === 'string' ? m('span', label) : label) : undefined,
|
|
3030
|
-
]), description && m(HelperText, { className: 'input-checkbox-desc', helperText: description }));
|
|
3031
|
-
},
|
|
3032
|
-
};
|
|
3033
|
-
};
|
|
3034
|
-
/** A list of checkboxes */
|
|
3035
|
-
const Options = () => {
|
|
3036
|
-
const state = {};
|
|
3037
|
-
const isChecked = (id) => state.checkedIds.indexOf(id) >= 0;
|
|
3038
|
-
const selectAll = (options, callback) => {
|
|
3039
|
-
const allIds = options.map((option) => option.id);
|
|
3040
|
-
state.checkedIds = [...allIds];
|
|
3041
|
-
if (callback)
|
|
3042
|
-
callback(allIds);
|
|
3043
|
-
};
|
|
3044
|
-
const selectNone = (callback) => {
|
|
3045
|
-
state.checkedIds = [];
|
|
3046
|
-
if (callback)
|
|
3047
|
-
callback([]);
|
|
3048
|
-
};
|
|
3049
|
-
return {
|
|
3050
|
-
oninit: ({ attrs: { initialValue, checkedId, id } }) => {
|
|
3051
|
-
const iv = checkedId || initialValue;
|
|
3052
|
-
state.checkedId = checkedId;
|
|
3053
|
-
state.checkedIds = iv ? (iv instanceof Array ? [...iv] : [iv]) : [];
|
|
3054
|
-
state.componentId = id || uniqueId();
|
|
3055
|
-
},
|
|
3056
|
-
view: ({ attrs: { label, options, description, className = 'col s12', style, disabled, checkboxClass, newRow, isMandatory, layout = 'vertical', showSelectAll = false, onchange: callback, }, }) => {
|
|
3057
|
-
const onchange = callback
|
|
3058
|
-
? (propId, checked) => {
|
|
3059
|
-
const checkedIds = state.checkedIds.filter((i) => i !== propId);
|
|
3060
|
-
if (checked) {
|
|
3061
|
-
checkedIds.push(propId);
|
|
3062
|
-
}
|
|
3063
|
-
state.checkedIds = checkedIds;
|
|
3064
|
-
callback(checkedIds);
|
|
3065
|
-
}
|
|
3066
|
-
: undefined;
|
|
3067
|
-
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim();
|
|
3068
|
-
const optionsContent = layout === 'horizontal'
|
|
3069
|
-
? m('div.grid-container', options.map((option) => m(InputCheckbox, {
|
|
3070
|
-
disabled: disabled || option.disabled,
|
|
3071
|
-
label: option.label,
|
|
3072
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3073
|
-
className: option.className || checkboxClass,
|
|
3074
|
-
checked: isChecked(option.id),
|
|
3075
|
-
description: option.description,
|
|
3076
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3077
|
-
})))
|
|
3078
|
-
: options.map((option) => m(InputCheckbox, {
|
|
3079
|
-
disabled: disabled || option.disabled,
|
|
3080
|
-
label: option.label,
|
|
3081
|
-
onchange: onchange ? (v) => onchange(option.id, v) : undefined,
|
|
3082
|
-
className: option.className || checkboxClass,
|
|
3083
|
-
checked: isChecked(option.id),
|
|
3084
|
-
description: option.description,
|
|
3085
|
-
inputId: `${state.componentId}-${option.id}`,
|
|
3086
|
-
}));
|
|
3087
|
-
return m('div', { id: state.componentId, className: cn, style }, [
|
|
3088
|
-
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
3089
|
-
showSelectAll &&
|
|
3090
|
-
m('div.select-all-controls', { style: 'margin-bottom: 10px;' }, [
|
|
3091
|
-
m('a', {
|
|
3092
|
-
href: '#',
|
|
3093
|
-
onclick: (e) => {
|
|
3094
|
-
e.preventDefault();
|
|
3095
|
-
selectAll(options, callback);
|
|
3096
|
-
},
|
|
3097
|
-
style: 'margin-right: 15px;',
|
|
3098
|
-
}, 'Select All'),
|
|
3099
|
-
m('a', {
|
|
3100
|
-
href: '#',
|
|
3101
|
-
onclick: (e) => {
|
|
3102
|
-
e.preventDefault();
|
|
3103
|
-
selectNone(callback);
|
|
3104
|
-
},
|
|
3105
|
-
}, 'Select None'),
|
|
3106
|
-
]),
|
|
3107
|
-
description && m(HelperText, { helperText: description }),
|
|
3108
|
-
m('form', { action: '#' }, optionsContent),
|
|
3109
|
-
]);
|
|
3110
|
-
},
|
|
3111
|
-
};
|
|
3112
|
-
};
|
|
3113
|
-
|
|
3114
3710
|
const PaginationItem = () => ({
|
|
3115
3711
|
view: ({ attrs: { title, href, active, disabled } }) => m('li', { className: active ? 'active' : disabled ? 'disabled' : 'waves-effect' }, typeof title === 'number' ? m(m.route.Link, { href }, title) : title),
|
|
3116
3712
|
});
|
|
@@ -3375,12 +3971,15 @@
|
|
|
3375
3971
|
};
|
|
3376
3972
|
const updateTimeFromInput = (inputValue) => {
|
|
3377
3973
|
let value = ((inputValue || options.defaultTime || '') + '').split(':');
|
|
3974
|
+
let amPmWasProvided = false;
|
|
3378
3975
|
if (options.twelveHour && value.length > 1) {
|
|
3379
3976
|
if (value[1].toUpperCase().indexOf('AM') > -1) {
|
|
3380
3977
|
state.amOrPm = 'AM';
|
|
3978
|
+
amPmWasProvided = true;
|
|
3381
3979
|
}
|
|
3382
3980
|
else if (value[1].toUpperCase().indexOf('PM') > -1) {
|
|
3383
3981
|
state.amOrPm = 'PM';
|
|
3982
|
+
amPmWasProvided = true;
|
|
3384
3983
|
}
|
|
3385
3984
|
value[1] = value[1].replace('AM', '').replace('PM', '').trim();
|
|
3386
3985
|
}
|
|
@@ -3389,21 +3988,33 @@
|
|
|
3389
3988
|
value = [now.getHours().toString(), now.getMinutes().toString()];
|
|
3390
3989
|
if (options.twelveHour) {
|
|
3391
3990
|
state.amOrPm = parseInt(value[0]) >= 12 ? 'PM' : 'AM';
|
|
3991
|
+
amPmWasProvided = false; // For 'now', we need to do conversion
|
|
3392
3992
|
}
|
|
3393
3993
|
}
|
|
3394
3994
|
let hours = +value[0] || 0;
|
|
3395
3995
|
let minutes = +value[1] || 0;
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3996
|
+
if (options.twelveHour) {
|
|
3997
|
+
if (!amPmWasProvided) {
|
|
3998
|
+
// No AM/PM was provided, assume this is 24-hour format input - convert it
|
|
3999
|
+
if (hours >= 12) {
|
|
4000
|
+
state.amOrPm = 'PM';
|
|
4001
|
+
if (hours > 12) {
|
|
4002
|
+
hours = hours - 12;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
else {
|
|
4006
|
+
state.amOrPm = 'AM';
|
|
4007
|
+
if (hours === 0) {
|
|
4008
|
+
hours = 12;
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
3401
4011
|
}
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
4012
|
+
else {
|
|
4013
|
+
// AM/PM was provided, hours are already in 12-hour format
|
|
4014
|
+
// Just handle midnight/noon edge cases
|
|
4015
|
+
if (hours === 0 && state.amOrPm === 'AM') {
|
|
4016
|
+
hours = 12;
|
|
4017
|
+
}
|
|
3407
4018
|
}
|
|
3408
4019
|
}
|
|
3409
4020
|
state.hours = hours;
|
|
@@ -6189,6 +6800,27 @@
|
|
|
6189
6800
|
};
|
|
6190
6801
|
};
|
|
6191
6802
|
|
|
6803
|
+
/**
|
|
6804
|
+
* @fileoverview Core TypeScript utility types for mithril-materialized library
|
|
6805
|
+
* These types improve type safety and developer experience across all components
|
|
6806
|
+
*/
|
|
6807
|
+
/**
|
|
6808
|
+
* Type guard to check if validation result indicates success
|
|
6809
|
+
* @param result - The validation result to check
|
|
6810
|
+
* @returns True if validation passed
|
|
6811
|
+
*/
|
|
6812
|
+
const isValidationSuccess = (result) => result === true || result === '';
|
|
6813
|
+
/**
|
|
6814
|
+
* Type guard to check if validation result indicates an error
|
|
6815
|
+
* @param result - The validation result to check
|
|
6816
|
+
* @returns True if validation failed
|
|
6817
|
+
*/
|
|
6818
|
+
const isValidationError = (result) => !isValidationSuccess(result);
|
|
6819
|
+
// ============================================================================
|
|
6820
|
+
// EXPORTS
|
|
6821
|
+
// ============================================================================
|
|
6822
|
+
// All types are already exported via individual export declarations above
|
|
6823
|
+
|
|
6192
6824
|
exports.AnchorItem = AnchorItem;
|
|
6193
6825
|
exports.Autocomplete = Autocomplete;
|
|
6194
6826
|
exports.Breadcrumb = Breadcrumb;
|
|
@@ -6203,6 +6835,7 @@
|
|
|
6203
6835
|
exports.CollapsibleItem = CollapsibleItem;
|
|
6204
6836
|
exports.Collection = Collection;
|
|
6205
6837
|
exports.ColorInput = ColorInput;
|
|
6838
|
+
exports.DataTable = DataTable;
|
|
6206
6839
|
exports.DatePicker = DatePicker;
|
|
6207
6840
|
exports.Dropdown = Dropdown;
|
|
6208
6841
|
exports.EmailInput = EmailInput;
|
|
@@ -6222,6 +6855,7 @@
|
|
|
6222
6855
|
exports.NumberInput = NumberInput;
|
|
6223
6856
|
exports.Options = Options;
|
|
6224
6857
|
exports.Pagination = Pagination;
|
|
6858
|
+
exports.PaginationControls = PaginationControls;
|
|
6225
6859
|
exports.Parallax = Parallax;
|
|
6226
6860
|
exports.PasswordInput = PasswordInput;
|
|
6227
6861
|
exports.Pushpin = Pushpin;
|
|
@@ -6258,6 +6892,8 @@
|
|
|
6258
6892
|
exports.initPushpins = initPushpins;
|
|
6259
6893
|
exports.initTooltips = initTooltips;
|
|
6260
6894
|
exports.isNumeric = isNumeric;
|
|
6895
|
+
exports.isValidationError = isValidationError;
|
|
6896
|
+
exports.isValidationSuccess = isValidationSuccess;
|
|
6261
6897
|
exports.padLeft = padLeft;
|
|
6262
6898
|
exports.range = range;
|
|
6263
6899
|
exports.toast = toast;
|