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