mithril-materialized 2.0.0-beta.5 → 2.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced.css +1888 -0
- package/dist/breadcrumb.d.ts +53 -0
- package/dist/components.css +2310 -0
- package/dist/core.css +3402 -0
- package/dist/file-upload.d.ts +34 -0
- package/dist/forms.css +2284 -0
- package/dist/index.css +1262 -83
- package/dist/index.d.ts +5 -0
- package/dist/index.esm.js +874 -33
- package/dist/index.js +885 -32
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +885 -32
- package/dist/pickers.css +487 -0
- package/dist/sidenav.d.ts +76 -0
- package/dist/tabs.d.ts +0 -2
- package/dist/theme-switcher.d.ts +49 -0
- package/dist/utilities.css +3197 -0
- package/dist/wizard.d.ts +58 -0
- package/package.json +11 -5
- package/sass/components/_breadcrumb.scss +248 -0
- package/sass/components/_buttons.scss +3 -3
- package/sass/components/_collapsible.scss +8 -8
- package/sass/components/_datepicker.scss +17 -15
- package/sass/components/_file-upload.scss +228 -0
- package/sass/components/_global.scss +7 -5
- package/sass/components/_modal.scss +5 -2
- package/sass/components/_navbar.scss +13 -5
- package/sass/components/_sidenav.scss +164 -7
- package/sass/components/_tabs.scss +5 -4
- package/sass/components/_theme-switcher.scss +120 -0
- package/sass/components/_theme-variables.scss +187 -0
- package/sass/components/_timepicker.scss +5 -5
- package/sass/components/_wizard.scss +416 -0
- package/sass/components/forms/_input-fields.scss +34 -12
- package/sass/components/forms/_radio-buttons.scss +10 -10
- package/sass/components/forms/_switches.scss +6 -6
- package/sass/materialize.scss +7 -0
package/dist/index.umd.js
CHANGED
|
@@ -1162,23 +1162,8 @@
|
|
|
1162
1162
|
header || iconName
|
|
1163
1163
|
? m('.collapsible-header', {
|
|
1164
1164
|
onclick: onToggle,
|
|
1165
|
-
style: {
|
|
1166
|
-
cursor: 'pointer',
|
|
1167
|
-
padding: '1rem',
|
|
1168
|
-
backgroundColor: '#fff',
|
|
1169
|
-
borderBottom: '1px solid #ddd',
|
|
1170
|
-
display: 'flex',
|
|
1171
|
-
alignItems: 'center',
|
|
1172
|
-
transition: 'background-color 0.2s ease',
|
|
1173
|
-
},
|
|
1174
|
-
onmouseover: (e) => {
|
|
1175
|
-
e.target.style.backgroundColor = '#f5f5f5';
|
|
1176
|
-
},
|
|
1177
|
-
onmouseleave: (e) => {
|
|
1178
|
-
e.target.style.backgroundColor = '#fff';
|
|
1179
|
-
},
|
|
1180
1165
|
}, [
|
|
1181
|
-
iconName ? m('i.material-icons',
|
|
1166
|
+
iconName ? m('i.material-icons', iconName) : undefined,
|
|
1182
1167
|
header ? (typeof header === 'string' ? m('span', header) : header) : undefined,
|
|
1183
1168
|
])
|
|
1184
1169
|
: undefined,
|
|
@@ -4398,13 +4383,9 @@
|
|
|
4398
4383
|
/** CSS-only Tabs component - no MaterializeCSS dependencies */
|
|
4399
4384
|
const Tabs = () => {
|
|
4400
4385
|
const toAnchored = () => {
|
|
4401
|
-
let activeTabFound = false;
|
|
4402
4386
|
return (tab) => {
|
|
4403
|
-
const active = activeTabFound ? false : tab.active;
|
|
4404
|
-
if (active)
|
|
4405
|
-
activeTabFound = true;
|
|
4406
4387
|
const tabId = createId(tab.title, tab.id);
|
|
4407
|
-
return Object.assign(Object.assign({}, tab), {
|
|
4388
|
+
return Object.assign(Object.assign({}, tab), { tabId, anchorId: `anchor-${tabId}` });
|
|
4408
4389
|
};
|
|
4409
4390
|
};
|
|
4410
4391
|
const state = {
|
|
@@ -4415,7 +4396,9 @@
|
|
|
4415
4396
|
indicatorStyle: {
|
|
4416
4397
|
left: '0px',
|
|
4417
4398
|
width: '0px',
|
|
4418
|
-
}
|
|
4399
|
+
},
|
|
4400
|
+
lastIndicatorUpdate: '',
|
|
4401
|
+
};
|
|
4419
4402
|
const createId = (title, id) => (id ? id : title.replace(/ /g, '').toLowerCase());
|
|
4420
4403
|
const updateIndicator = () => {
|
|
4421
4404
|
const tabElement = document.getElementById(state.activeTabId);
|
|
@@ -4437,6 +4420,7 @@
|
|
|
4437
4420
|
}
|
|
4438
4421
|
};
|
|
4439
4422
|
const handleTabClick = (tabId, tabElement, attrs) => {
|
|
4423
|
+
console.log({ state, tabId });
|
|
4440
4424
|
if (state.activeTabId === tabId)
|
|
4441
4425
|
return;
|
|
4442
4426
|
state.activeTabId = tabId;
|
|
@@ -4496,20 +4480,15 @@
|
|
|
4496
4480
|
const selectedTab = selectedTabId ? anchoredTabs.find((a) => a.tabId === selectedTabId) : undefined;
|
|
4497
4481
|
if (selectedTab) {
|
|
4498
4482
|
state.activeTabId = selectedTab.tabId;
|
|
4499
|
-
selectedTab.active = true;
|
|
4500
4483
|
return selectedTab;
|
|
4501
4484
|
}
|
|
4502
|
-
const
|
|
4503
|
-
if (
|
|
4504
|
-
|
|
4505
|
-
state.activeTabId = activeTab.tabId;
|
|
4506
|
-
return activeTab;
|
|
4507
|
-
}
|
|
4485
|
+
const curTab = state.activeTabId && anchoredTabs.find((a) => a.tabId === state.activeTabId);
|
|
4486
|
+
if (curTab)
|
|
4487
|
+
return curTab;
|
|
4508
4488
|
// Default to first non-disabled tab
|
|
4509
|
-
const firstAvailableTab = anchoredTabs.find((
|
|
4489
|
+
const firstAvailableTab = anchoredTabs.find((a) => !a.disabled && !a.href);
|
|
4510
4490
|
if (firstAvailableTab) {
|
|
4511
4491
|
state.activeTabId = firstAvailableTab.tabId;
|
|
4512
|
-
firstAvailableTab.active = true;
|
|
4513
4492
|
return firstAvailableTab;
|
|
4514
4493
|
}
|
|
4515
4494
|
return undefined;
|
|
@@ -4548,7 +4527,7 @@
|
|
|
4548
4527
|
disabled,
|
|
4549
4528
|
}, m('a', {
|
|
4550
4529
|
id: anchorId,
|
|
4551
|
-
className: tab.
|
|
4530
|
+
className: tab.tabId === state.activeTabId ? 'active' : undefined,
|
|
4552
4531
|
target,
|
|
4553
4532
|
href: href || `#${anchorId}`,
|
|
4554
4533
|
onclick: disabled || href
|
|
@@ -5343,8 +5322,872 @@
|
|
|
5343
5322
|
return tooltips;
|
|
5344
5323
|
};
|
|
5345
5324
|
|
|
5325
|
+
/**
|
|
5326
|
+
* Theme switching utilities and component
|
|
5327
|
+
*/
|
|
5328
|
+
class ThemeManager {
|
|
5329
|
+
/**
|
|
5330
|
+
* Set the theme for the entire application
|
|
5331
|
+
*/
|
|
5332
|
+
static setTheme(theme) {
|
|
5333
|
+
this.currentTheme = theme;
|
|
5334
|
+
const root = document.documentElement;
|
|
5335
|
+
if (theme === 'auto') {
|
|
5336
|
+
// Remove explicit theme, let CSS media query handle it
|
|
5337
|
+
root.removeAttribute('data-theme');
|
|
5338
|
+
}
|
|
5339
|
+
else {
|
|
5340
|
+
// Set explicit theme
|
|
5341
|
+
root.setAttribute('data-theme', theme);
|
|
5342
|
+
}
|
|
5343
|
+
// Store preference in localStorage
|
|
5344
|
+
try {
|
|
5345
|
+
localStorage.setItem('mm-theme', theme);
|
|
5346
|
+
}
|
|
5347
|
+
catch (e) {
|
|
5348
|
+
// localStorage might not be available
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
/**
|
|
5352
|
+
* Get the current theme
|
|
5353
|
+
*/
|
|
5354
|
+
static getTheme() {
|
|
5355
|
+
return this.currentTheme;
|
|
5356
|
+
}
|
|
5357
|
+
/**
|
|
5358
|
+
* Get the effective theme (resolves 'auto' to actual theme)
|
|
5359
|
+
*/
|
|
5360
|
+
static getEffectiveTheme() {
|
|
5361
|
+
if (this.currentTheme !== 'auto') {
|
|
5362
|
+
return this.currentTheme;
|
|
5363
|
+
}
|
|
5364
|
+
// Check CSS media query for auto mode
|
|
5365
|
+
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
5366
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
5367
|
+
}
|
|
5368
|
+
return 'light';
|
|
5369
|
+
}
|
|
5370
|
+
/**
|
|
5371
|
+
* Initialize theme from localStorage or system preference
|
|
5372
|
+
*/
|
|
5373
|
+
static initialize() {
|
|
5374
|
+
let savedTheme = 'auto';
|
|
5375
|
+
try {
|
|
5376
|
+
const stored = localStorage.getItem('mm-theme');
|
|
5377
|
+
if (stored && ['light', 'dark', 'auto'].includes(stored)) {
|
|
5378
|
+
savedTheme = stored;
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
catch (e) {
|
|
5382
|
+
// localStorage might not be available
|
|
5383
|
+
}
|
|
5384
|
+
this.setTheme(savedTheme);
|
|
5385
|
+
}
|
|
5386
|
+
/**
|
|
5387
|
+
* Toggle between light and dark themes
|
|
5388
|
+
*/
|
|
5389
|
+
static toggle() {
|
|
5390
|
+
const current = this.getEffectiveTheme();
|
|
5391
|
+
this.setTheme(current === 'light' ? 'dark' : 'light');
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
ThemeManager.currentTheme = 'auto';
|
|
5395
|
+
/**
|
|
5396
|
+
* Theme Switcher Component
|
|
5397
|
+
* Provides UI controls for changing themes
|
|
5398
|
+
*/
|
|
5399
|
+
const ThemeSwitcher = () => {
|
|
5400
|
+
return {
|
|
5401
|
+
oninit: () => {
|
|
5402
|
+
// Initialize theme manager if not already done
|
|
5403
|
+
if (typeof window !== 'undefined') {
|
|
5404
|
+
ThemeManager.initialize();
|
|
5405
|
+
}
|
|
5406
|
+
},
|
|
5407
|
+
view: ({ attrs }) => {
|
|
5408
|
+
const { theme = ThemeManager.getTheme(), onThemeChange, showLabels = true, className = '' } = attrs;
|
|
5409
|
+
const handleThemeChange = (newTheme) => {
|
|
5410
|
+
ThemeManager.setTheme(newTheme);
|
|
5411
|
+
if (onThemeChange) {
|
|
5412
|
+
onThemeChange(newTheme);
|
|
5413
|
+
}
|
|
5414
|
+
};
|
|
5415
|
+
return m('.theme-switcher', { class: className }, [
|
|
5416
|
+
showLabels && m('span.theme-switcher-label', 'Theme:'),
|
|
5417
|
+
m('.theme-switcher-buttons', [
|
|
5418
|
+
m('button.btn-flat', {
|
|
5419
|
+
class: theme === 'light' ? 'active' : '',
|
|
5420
|
+
onclick: () => handleThemeChange('light'),
|
|
5421
|
+
title: 'Light theme'
|
|
5422
|
+
}, [
|
|
5423
|
+
m('i.material-icons', 'light_mode'),
|
|
5424
|
+
showLabels && m('span', 'Light')
|
|
5425
|
+
]),
|
|
5426
|
+
m('button.btn-flat', {
|
|
5427
|
+
class: theme === 'auto' ? 'active' : '',
|
|
5428
|
+
onclick: () => handleThemeChange('auto'),
|
|
5429
|
+
title: 'Auto theme (system preference)'
|
|
5430
|
+
}, [
|
|
5431
|
+
m('i.material-icons', 'brightness_auto'),
|
|
5432
|
+
showLabels && m('span', 'Auto')
|
|
5433
|
+
]),
|
|
5434
|
+
m('button.btn-flat', {
|
|
5435
|
+
class: theme === 'dark' ? 'active' : '',
|
|
5436
|
+
onclick: () => handleThemeChange('dark'),
|
|
5437
|
+
title: 'Dark theme'
|
|
5438
|
+
}, [
|
|
5439
|
+
m('i.material-icons', 'dark_mode'),
|
|
5440
|
+
showLabels && m('span', 'Dark')
|
|
5441
|
+
])
|
|
5442
|
+
])
|
|
5443
|
+
]);
|
|
5444
|
+
}
|
|
5445
|
+
};
|
|
5446
|
+
};
|
|
5447
|
+
/**
|
|
5448
|
+
* Simple theme toggle button (just switches between light/dark)
|
|
5449
|
+
*/
|
|
5450
|
+
const ThemeToggle = () => {
|
|
5451
|
+
return {
|
|
5452
|
+
oninit: () => {
|
|
5453
|
+
// Initialize theme manager if not already done
|
|
5454
|
+
if (typeof window !== 'undefined') {
|
|
5455
|
+
ThemeManager.initialize();
|
|
5456
|
+
}
|
|
5457
|
+
},
|
|
5458
|
+
view: ({ attrs }) => {
|
|
5459
|
+
const currentTheme = ThemeManager.getEffectiveTheme();
|
|
5460
|
+
return m('button.btn-flat.theme-toggle', {
|
|
5461
|
+
class: attrs.className || '',
|
|
5462
|
+
onclick: () => {
|
|
5463
|
+
ThemeManager.toggle();
|
|
5464
|
+
m.redraw();
|
|
5465
|
+
},
|
|
5466
|
+
title: `Switch to ${currentTheme === 'light' ? 'dark' : 'light'} theme`,
|
|
5467
|
+
style: 'margin: 0; height: 64px; line-height: 64px; border-radius: 0; min-width: 64px; padding: 0;'
|
|
5468
|
+
}, [
|
|
5469
|
+
m('i.material-icons', {
|
|
5470
|
+
style: 'color: inherit; font-size: 24px;'
|
|
5471
|
+
}, currentTheme === 'light' ? 'dark_mode' : 'light_mode')
|
|
5472
|
+
]);
|
|
5473
|
+
}
|
|
5474
|
+
};
|
|
5475
|
+
};
|
|
5476
|
+
|
|
5477
|
+
/**
|
|
5478
|
+
* File Upload Component with Drag and Drop
|
|
5479
|
+
* Supports multiple files, file type validation, size limits, and image preview
|
|
5480
|
+
*/
|
|
5481
|
+
const FileUpload = () => {
|
|
5482
|
+
let state;
|
|
5483
|
+
const validateFile = (file, attrs) => {
|
|
5484
|
+
// Check file size
|
|
5485
|
+
if (attrs.maxSize && file.size > attrs.maxSize) {
|
|
5486
|
+
const maxSizeMB = (attrs.maxSize / (1024 * 1024)).toFixed(1);
|
|
5487
|
+
return `File size exceeds ${maxSizeMB}MB limit`;
|
|
5488
|
+
}
|
|
5489
|
+
// Check file type
|
|
5490
|
+
if (attrs.accept) {
|
|
5491
|
+
const acceptedTypes = attrs.accept.split(',').map(type => type.trim());
|
|
5492
|
+
const isAccepted = acceptedTypes.some(acceptedType => {
|
|
5493
|
+
if (acceptedType.startsWith('.')) {
|
|
5494
|
+
// Extension check
|
|
5495
|
+
return file.name.toLowerCase().endsWith(acceptedType.toLowerCase());
|
|
5496
|
+
}
|
|
5497
|
+
else {
|
|
5498
|
+
// MIME type check
|
|
5499
|
+
return file.type.match(acceptedType.replace('*', '.*'));
|
|
5500
|
+
}
|
|
5501
|
+
});
|
|
5502
|
+
if (!isAccepted) {
|
|
5503
|
+
return `File type not accepted. Accepted: ${attrs.accept}`;
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
return null;
|
|
5507
|
+
};
|
|
5508
|
+
const createFilePreview = (file) => {
|
|
5509
|
+
if (file.type.startsWith('image/')) {
|
|
5510
|
+
const reader = new FileReader();
|
|
5511
|
+
reader.onload = (e) => {
|
|
5512
|
+
var _a;
|
|
5513
|
+
file.preview = (_a = e.target) === null || _a === void 0 ? void 0 : _a.result;
|
|
5514
|
+
m.redraw();
|
|
5515
|
+
};
|
|
5516
|
+
reader.readAsDataURL(file);
|
|
5517
|
+
}
|
|
5518
|
+
};
|
|
5519
|
+
const handleFiles = (fileList, attrs) => {
|
|
5520
|
+
const newFiles = Array.from(fileList);
|
|
5521
|
+
const validFiles = [];
|
|
5522
|
+
// Validate each file
|
|
5523
|
+
for (const file of newFiles) {
|
|
5524
|
+
const error = validateFile(file, attrs);
|
|
5525
|
+
if (error) {
|
|
5526
|
+
file.uploadError = error;
|
|
5527
|
+
}
|
|
5528
|
+
else {
|
|
5529
|
+
validFiles.push(file);
|
|
5530
|
+
if (attrs.showPreview) {
|
|
5531
|
+
createFilePreview(file);
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
// Check max files limit
|
|
5536
|
+
if (attrs.maxFiles) {
|
|
5537
|
+
const totalFiles = state.files.length + validFiles.length;
|
|
5538
|
+
if (totalFiles > attrs.maxFiles) {
|
|
5539
|
+
const allowedCount = attrs.maxFiles - state.files.length;
|
|
5540
|
+
validFiles.splice(allowedCount);
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
// Add valid files to state
|
|
5544
|
+
if (attrs.multiple) {
|
|
5545
|
+
state.files = [...state.files, ...validFiles];
|
|
5546
|
+
}
|
|
5547
|
+
else {
|
|
5548
|
+
state.files = validFiles.slice(0, 1);
|
|
5549
|
+
}
|
|
5550
|
+
// Notify parent component
|
|
5551
|
+
if (attrs.onFilesSelected) {
|
|
5552
|
+
attrs.onFilesSelected(state.files.filter(f => !f.uploadError));
|
|
5553
|
+
}
|
|
5554
|
+
};
|
|
5555
|
+
const removeFile = (fileToRemove, attrs) => {
|
|
5556
|
+
state.files = state.files.filter(file => file !== fileToRemove);
|
|
5557
|
+
if (attrs.onFileRemoved) {
|
|
5558
|
+
attrs.onFileRemoved(fileToRemove);
|
|
5559
|
+
}
|
|
5560
|
+
};
|
|
5561
|
+
const formatFileSize = (bytes) => {
|
|
5562
|
+
if (bytes === 0)
|
|
5563
|
+
return '0 B';
|
|
5564
|
+
const k = 1024;
|
|
5565
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
5566
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
5567
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
5568
|
+
};
|
|
5569
|
+
return {
|
|
5570
|
+
oninit: () => {
|
|
5571
|
+
state = {
|
|
5572
|
+
id: uniqueId(),
|
|
5573
|
+
files: [],
|
|
5574
|
+
isDragOver: false,
|
|
5575
|
+
isUploading: false
|
|
5576
|
+
};
|
|
5577
|
+
},
|
|
5578
|
+
view: ({ attrs }) => {
|
|
5579
|
+
const { accept, multiple = false, disabled = false, label = 'Choose files or drag them here', helperText, showPreview = true, className = '', error } = attrs;
|
|
5580
|
+
return m('.file-upload-container', { class: className }, [
|
|
5581
|
+
// Upload area
|
|
5582
|
+
m('.file-upload-area', {
|
|
5583
|
+
class: [
|
|
5584
|
+
state.isDragOver ? 'drag-over' : '',
|
|
5585
|
+
disabled ? 'disabled' : '',
|
|
5586
|
+
error ? 'error' : '',
|
|
5587
|
+
state.files.length > 0 ? 'has-files' : ''
|
|
5588
|
+
].filter(Boolean).join(' '),
|
|
5589
|
+
ondragover: (e) => {
|
|
5590
|
+
if (disabled)
|
|
5591
|
+
return;
|
|
5592
|
+
e.preventDefault();
|
|
5593
|
+
e.stopPropagation();
|
|
5594
|
+
state.isDragOver = true;
|
|
5595
|
+
},
|
|
5596
|
+
ondragleave: (e) => {
|
|
5597
|
+
if (disabled)
|
|
5598
|
+
return;
|
|
5599
|
+
e.preventDefault();
|
|
5600
|
+
e.stopPropagation();
|
|
5601
|
+
state.isDragOver = false;
|
|
5602
|
+
},
|
|
5603
|
+
ondrop: (e) => {
|
|
5604
|
+
var _a;
|
|
5605
|
+
if (disabled)
|
|
5606
|
+
return;
|
|
5607
|
+
e.preventDefault();
|
|
5608
|
+
e.stopPropagation();
|
|
5609
|
+
state.isDragOver = false;
|
|
5610
|
+
if ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) {
|
|
5611
|
+
handleFiles(e.dataTransfer.files, attrs);
|
|
5612
|
+
}
|
|
5613
|
+
},
|
|
5614
|
+
onclick: () => {
|
|
5615
|
+
if (disabled)
|
|
5616
|
+
return;
|
|
5617
|
+
const input = document.getElementById(state.id);
|
|
5618
|
+
input === null || input === void 0 ? void 0 : input.click();
|
|
5619
|
+
}
|
|
5620
|
+
}, [
|
|
5621
|
+
m('input[type="file"]', {
|
|
5622
|
+
id: state.id,
|
|
5623
|
+
accept,
|
|
5624
|
+
multiple,
|
|
5625
|
+
disabled,
|
|
5626
|
+
style: { display: 'none' },
|
|
5627
|
+
onchange: (e) => {
|
|
5628
|
+
const target = e.target;
|
|
5629
|
+
if (target.files) {
|
|
5630
|
+
handleFiles(target.files, attrs);
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
}),
|
|
5634
|
+
m('.file-upload-content', [
|
|
5635
|
+
m('i.material-icons.file-upload-icon', 'cloud_upload'),
|
|
5636
|
+
m('p.file-upload-label', label),
|
|
5637
|
+
helperText && m('p.file-upload-helper', helperText),
|
|
5638
|
+
accept && m('p.file-upload-types', `Accepted: ${accept}`)
|
|
5639
|
+
])
|
|
5640
|
+
]),
|
|
5641
|
+
// Error message
|
|
5642
|
+
error && m('.file-upload-error', error),
|
|
5643
|
+
// File list
|
|
5644
|
+
state.files.length > 0 && m('.file-upload-list', [
|
|
5645
|
+
m('h6', 'Selected Files:'),
|
|
5646
|
+
state.files.map(file => m('.file-upload-item', { key: file.name + file.size }, [
|
|
5647
|
+
// Preview thumbnail
|
|
5648
|
+
showPreview && file.preview && m('.file-preview', [
|
|
5649
|
+
m('img', { src: file.preview, alt: file.name })
|
|
5650
|
+
]),
|
|
5651
|
+
// File info
|
|
5652
|
+
m('.file-info', [
|
|
5653
|
+
m('.file-name', file.name),
|
|
5654
|
+
m('.file-details', [
|
|
5655
|
+
m('span.file-size', formatFileSize(file.size)),
|
|
5656
|
+
file.type && m('span.file-type', file.type)
|
|
5657
|
+
]),
|
|
5658
|
+
// Progress bar (if uploading)
|
|
5659
|
+
file.uploadProgress !== undefined && m('.file-progress', [
|
|
5660
|
+
m('.progress', [
|
|
5661
|
+
m('.determinate', {
|
|
5662
|
+
style: { width: `${file.uploadProgress}%` }
|
|
5663
|
+
})
|
|
5664
|
+
])
|
|
5665
|
+
]),
|
|
5666
|
+
// Error message
|
|
5667
|
+
file.uploadError && m('.file-error', file.uploadError)
|
|
5668
|
+
]),
|
|
5669
|
+
// Remove button
|
|
5670
|
+
m('button.btn-flat.file-remove', {
|
|
5671
|
+
onclick: (e) => {
|
|
5672
|
+
e.stopPropagation();
|
|
5673
|
+
removeFile(file, attrs);
|
|
5674
|
+
},
|
|
5675
|
+
title: 'Remove file'
|
|
5676
|
+
}, [
|
|
5677
|
+
m('i.material-icons', 'close')
|
|
5678
|
+
])
|
|
5679
|
+
]))
|
|
5680
|
+
])
|
|
5681
|
+
]);
|
|
5682
|
+
}
|
|
5683
|
+
};
|
|
5684
|
+
};
|
|
5685
|
+
|
|
5686
|
+
/**
|
|
5687
|
+
* Sidenav Component
|
|
5688
|
+
* A responsive navigation drawer that slides in from the side
|
|
5689
|
+
*/
|
|
5690
|
+
const Sidenav = () => {
|
|
5691
|
+
let state;
|
|
5692
|
+
const handleBackdropClick = (attrs) => {
|
|
5693
|
+
if (attrs.closeOnBackdropClick !== false && attrs.onToggle) {
|
|
5694
|
+
attrs.onToggle(false);
|
|
5695
|
+
}
|
|
5696
|
+
};
|
|
5697
|
+
const handleEscapeKey = (e, attrs) => {
|
|
5698
|
+
if (e.key === 'Escape' && attrs.closeOnEscape !== false && attrs.onToggle) {
|
|
5699
|
+
attrs.onToggle(false);
|
|
5700
|
+
}
|
|
5701
|
+
};
|
|
5702
|
+
const setBodyOverflow = (isOpen, mode) => {
|
|
5703
|
+
if (typeof document !== 'undefined') {
|
|
5704
|
+
document.body.style.overflow = isOpen && mode === 'overlay' ? 'hidden' : '';
|
|
5705
|
+
}
|
|
5706
|
+
};
|
|
5707
|
+
return {
|
|
5708
|
+
oninit: ({ attrs }) => {
|
|
5709
|
+
state = {
|
|
5710
|
+
id: attrs.id || uniqueId(),
|
|
5711
|
+
isOpen: attrs.isOpen || false,
|
|
5712
|
+
isAnimating: false
|
|
5713
|
+
};
|
|
5714
|
+
// Set up keyboard listener
|
|
5715
|
+
if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
|
|
5716
|
+
document.addEventListener('keydown', (e) => handleEscapeKey(e, attrs));
|
|
5717
|
+
}
|
|
5718
|
+
},
|
|
5719
|
+
onbeforeupdate: ({ attrs }) => {
|
|
5720
|
+
const wasOpen = state.isOpen;
|
|
5721
|
+
const isOpen = attrs.isOpen || false;
|
|
5722
|
+
if (wasOpen !== isOpen) {
|
|
5723
|
+
state.isOpen = isOpen;
|
|
5724
|
+
state.isAnimating = true;
|
|
5725
|
+
setBodyOverflow(isOpen, attrs.mode || 'overlay');
|
|
5726
|
+
// Clear animation state after animation completes
|
|
5727
|
+
setTimeout(() => {
|
|
5728
|
+
state.isAnimating = false;
|
|
5729
|
+
m.redraw();
|
|
5730
|
+
}, attrs.animationDuration || 300);
|
|
5731
|
+
}
|
|
5732
|
+
},
|
|
5733
|
+
onremove: ({ attrs }) => {
|
|
5734
|
+
// Clean up
|
|
5735
|
+
setBodyOverflow(false, attrs.mode || 'overlay');
|
|
5736
|
+
if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
|
|
5737
|
+
document.removeEventListener('keydown', (e) => handleEscapeKey(e, attrs));
|
|
5738
|
+
}
|
|
5739
|
+
},
|
|
5740
|
+
view: ({ attrs, children }) => {
|
|
5741
|
+
const { position = 'left', mode = 'overlay', width = 300, className = '', showBackdrop = true, animationDuration = 300, fixed = false } = attrs;
|
|
5742
|
+
const isOpen = state.isOpen;
|
|
5743
|
+
return [
|
|
5744
|
+
// Backdrop (using existing materialize class)
|
|
5745
|
+
showBackdrop && mode === 'overlay' && m('.sidenav-overlay', {
|
|
5746
|
+
style: {
|
|
5747
|
+
display: isOpen ? 'block' : 'none',
|
|
5748
|
+
opacity: isOpen ? '1' : '0'
|
|
5749
|
+
},
|
|
5750
|
+
onclick: () => handleBackdropClick(attrs)
|
|
5751
|
+
}),
|
|
5752
|
+
// Sidenav (using existing materialize structure)
|
|
5753
|
+
m('ul.sidenav', {
|
|
5754
|
+
id: state.id,
|
|
5755
|
+
class: [
|
|
5756
|
+
position === 'right' ? 'right-aligned' : '',
|
|
5757
|
+
fixed ? 'sidenav-fixed' : '',
|
|
5758
|
+
className
|
|
5759
|
+
].filter(Boolean).join(' '),
|
|
5760
|
+
style: {
|
|
5761
|
+
width: `${width}px`,
|
|
5762
|
+
transform: isOpen ? 'translateX(0)' :
|
|
5763
|
+
position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
|
|
5764
|
+
'transition-duration': `${animationDuration}ms`
|
|
5765
|
+
}
|
|
5766
|
+
}, children)
|
|
5767
|
+
];
|
|
5768
|
+
}
|
|
5769
|
+
};
|
|
5770
|
+
};
|
|
5771
|
+
/**
|
|
5772
|
+
* Sidenav Item Component
|
|
5773
|
+
* Individual items for the sidenav menu
|
|
5774
|
+
*/
|
|
5775
|
+
const SidenavItem = () => {
|
|
5776
|
+
return {
|
|
5777
|
+
view: ({ attrs, children }) => {
|
|
5778
|
+
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false } = attrs;
|
|
5779
|
+
if (divider) {
|
|
5780
|
+
return m('li.divider');
|
|
5781
|
+
}
|
|
5782
|
+
if (subheader) {
|
|
5783
|
+
return m('li.subheader', text || children);
|
|
5784
|
+
}
|
|
5785
|
+
const itemClasses = [
|
|
5786
|
+
active ? 'active' : '',
|
|
5787
|
+
disabled ? 'disabled' : '',
|
|
5788
|
+
className
|
|
5789
|
+
].filter(Boolean).join(' ');
|
|
5790
|
+
const content = [
|
|
5791
|
+
icon && m('i.material-icons', icon),
|
|
5792
|
+
text || children
|
|
5793
|
+
];
|
|
5794
|
+
if (href && !disabled) {
|
|
5795
|
+
return m('li', { class: itemClasses }, [
|
|
5796
|
+
m('a', {
|
|
5797
|
+
href,
|
|
5798
|
+
onclick: disabled ? undefined : onclick
|
|
5799
|
+
}, content)
|
|
5800
|
+
]);
|
|
5801
|
+
}
|
|
5802
|
+
return m('li', { class: itemClasses }, [
|
|
5803
|
+
m('a', {
|
|
5804
|
+
onclick: disabled ? undefined : onclick,
|
|
5805
|
+
href: '#!'
|
|
5806
|
+
}, content)
|
|
5807
|
+
]);
|
|
5808
|
+
}
|
|
5809
|
+
};
|
|
5810
|
+
};
|
|
5811
|
+
/**
|
|
5812
|
+
* Sidenav utilities for programmatic control
|
|
5813
|
+
*/
|
|
5814
|
+
class SidenavManager {
|
|
5815
|
+
/**
|
|
5816
|
+
* Open a sidenav by ID
|
|
5817
|
+
*/
|
|
5818
|
+
static open(id) {
|
|
5819
|
+
const element = document.getElementById(id);
|
|
5820
|
+
if (element) {
|
|
5821
|
+
element.classList.add('open');
|
|
5822
|
+
element.classList.remove('closed');
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
/**
|
|
5826
|
+
* Close a sidenav by ID
|
|
5827
|
+
*/
|
|
5828
|
+
static close(id) {
|
|
5829
|
+
const element = document.getElementById(id);
|
|
5830
|
+
if (element) {
|
|
5831
|
+
element.classList.remove('open');
|
|
5832
|
+
element.classList.add('closed');
|
|
5833
|
+
}
|
|
5834
|
+
}
|
|
5835
|
+
/**
|
|
5836
|
+
* Toggle a sidenav by ID
|
|
5837
|
+
*/
|
|
5838
|
+
static toggle(id) {
|
|
5839
|
+
const element = document.getElementById(id);
|
|
5840
|
+
if (element) {
|
|
5841
|
+
const isOpen = element.classList.contains('open');
|
|
5842
|
+
if (isOpen) {
|
|
5843
|
+
this.close(id);
|
|
5844
|
+
}
|
|
5845
|
+
else {
|
|
5846
|
+
this.open(id);
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5852
|
+
/**
|
|
5853
|
+
* Breadcrumb Component
|
|
5854
|
+
* Displays a navigation path showing the user's location within a site hierarchy
|
|
5855
|
+
*/
|
|
5856
|
+
const Breadcrumb = () => {
|
|
5857
|
+
return {
|
|
5858
|
+
view: ({ attrs }) => {
|
|
5859
|
+
const { items = [], separator = 'chevron_right', className = '', showIcons = false, maxItems, showHome = false } = attrs;
|
|
5860
|
+
if (items.length === 0) {
|
|
5861
|
+
return null;
|
|
5862
|
+
}
|
|
5863
|
+
let displayItems = [...items];
|
|
5864
|
+
// Handle max items with ellipsis
|
|
5865
|
+
if (maxItems && items.length > maxItems) {
|
|
5866
|
+
const firstItem = items[0];
|
|
5867
|
+
const lastItems = items.slice(-(maxItems - 2));
|
|
5868
|
+
displayItems = [
|
|
5869
|
+
firstItem,
|
|
5870
|
+
{ text: '...', disabled: true, className: 'breadcrumb-ellipsis' },
|
|
5871
|
+
...lastItems
|
|
5872
|
+
];
|
|
5873
|
+
}
|
|
5874
|
+
return m('nav.breadcrumb', { class: className }, [
|
|
5875
|
+
m('ol.breadcrumb-list', displayItems.map((item, index) => {
|
|
5876
|
+
const isLast = index === displayItems.length - 1;
|
|
5877
|
+
const isFirst = index === 0;
|
|
5878
|
+
return [
|
|
5879
|
+
// Breadcrumb item
|
|
5880
|
+
m('li.breadcrumb-item', {
|
|
5881
|
+
class: [
|
|
5882
|
+
item.active || isLast ? 'active' : '',
|
|
5883
|
+
item.disabled ? 'disabled' : '',
|
|
5884
|
+
item.className || ''
|
|
5885
|
+
].filter(Boolean).join(' ')
|
|
5886
|
+
}, [
|
|
5887
|
+
item.href && !item.disabled && !isLast ?
|
|
5888
|
+
// Link item
|
|
5889
|
+
m('a.breadcrumb-link', {
|
|
5890
|
+
href: item.href,
|
|
5891
|
+
onclick: item.onclick
|
|
5892
|
+
}, [
|
|
5893
|
+
(showIcons && item.icon) && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
5894
|
+
(showHome && isFirst && !item.icon) && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
5895
|
+
m('span.breadcrumb-text', item.text)
|
|
5896
|
+
]) :
|
|
5897
|
+
// Text item (active or disabled)
|
|
5898
|
+
m('span.breadcrumb-text', {
|
|
5899
|
+
onclick: item.disabled ? undefined : item.onclick
|
|
5900
|
+
}, [
|
|
5901
|
+
(showIcons && item.icon) && m('i.material-icons.breadcrumb-icon', item.icon),
|
|
5902
|
+
(showHome && isFirst && !item.icon) && m('i.material-icons.breadcrumb-icon', 'home'),
|
|
5903
|
+
item.text
|
|
5904
|
+
])
|
|
5905
|
+
]),
|
|
5906
|
+
// Separator (except for last item)
|
|
5907
|
+
!isLast && m('li.breadcrumb-separator', [
|
|
5908
|
+
m('i.material-icons', separator)
|
|
5909
|
+
])
|
|
5910
|
+
];
|
|
5911
|
+
}).reduce((acc, val) => acc.concat(val), []))
|
|
5912
|
+
]);
|
|
5913
|
+
}
|
|
5914
|
+
};
|
|
5915
|
+
};
|
|
5916
|
+
/**
|
|
5917
|
+
* Simple Breadcrumb utility for common use cases
|
|
5918
|
+
*/
|
|
5919
|
+
const createBreadcrumb = (path, basePath = '/') => {
|
|
5920
|
+
const segments = path.split('/').filter(Boolean);
|
|
5921
|
+
const items = [];
|
|
5922
|
+
// Add home item
|
|
5923
|
+
items.push({
|
|
5924
|
+
text: 'Home',
|
|
5925
|
+
href: basePath,
|
|
5926
|
+
icon: 'home'
|
|
5927
|
+
});
|
|
5928
|
+
// Add path segments
|
|
5929
|
+
let currentPath = basePath;
|
|
5930
|
+
segments.forEach((segment, index) => {
|
|
5931
|
+
currentPath += (currentPath.endsWith('/') ? '' : '/') + segment;
|
|
5932
|
+
const isLast = index === segments.length - 1;
|
|
5933
|
+
items.push({
|
|
5934
|
+
text: segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' '),
|
|
5935
|
+
href: isLast ? undefined : currentPath,
|
|
5936
|
+
active: isLast
|
|
5937
|
+
});
|
|
5938
|
+
});
|
|
5939
|
+
return items;
|
|
5940
|
+
};
|
|
5941
|
+
/**
|
|
5942
|
+
* Breadcrumb utilities
|
|
5943
|
+
*/
|
|
5944
|
+
class BreadcrumbManager {
|
|
5945
|
+
/**
|
|
5946
|
+
* Create breadcrumb items from a route path
|
|
5947
|
+
*/
|
|
5948
|
+
static fromRoute(route, routeConfig = {}) {
|
|
5949
|
+
const segments = route.split('/').filter(Boolean);
|
|
5950
|
+
const items = [];
|
|
5951
|
+
// Add home
|
|
5952
|
+
items.push({
|
|
5953
|
+
text: 'Home',
|
|
5954
|
+
href: '/',
|
|
5955
|
+
icon: 'home'
|
|
5956
|
+
});
|
|
5957
|
+
let currentPath = '';
|
|
5958
|
+
segments.forEach((segment, index) => {
|
|
5959
|
+
currentPath += '/' + segment;
|
|
5960
|
+
const isLast = index === segments.length - 1;
|
|
5961
|
+
// Use custom text from config or format segment
|
|
5962
|
+
const text = routeConfig[currentPath] ||
|
|
5963
|
+
segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
|
|
5964
|
+
items.push({
|
|
5965
|
+
text,
|
|
5966
|
+
href: isLast ? undefined : currentPath,
|
|
5967
|
+
active: isLast
|
|
5968
|
+
});
|
|
5969
|
+
});
|
|
5970
|
+
return items;
|
|
5971
|
+
}
|
|
5972
|
+
/**
|
|
5973
|
+
* Create breadcrumb items from a hierarchical object
|
|
5974
|
+
*/
|
|
5975
|
+
static fromHierarchy(hierarchy, textKey = 'name', pathKey = 'path') {
|
|
5976
|
+
return hierarchy.map((item, index) => ({
|
|
5977
|
+
text: item[textKey],
|
|
5978
|
+
href: index === hierarchy.length - 1 ? undefined : item[pathKey],
|
|
5979
|
+
active: index === hierarchy.length - 1
|
|
5980
|
+
}));
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5984
|
+
/**
|
|
5985
|
+
* Wizard/Stepper Component
|
|
5986
|
+
* A multi-step interface for guiding users through a process
|
|
5987
|
+
*/
|
|
5988
|
+
const Wizard = () => {
|
|
5989
|
+
let state;
|
|
5990
|
+
const validateStep = async (stepIndex, steps) => {
|
|
5991
|
+
const step = steps[stepIndex];
|
|
5992
|
+
if (!step || !step.validate)
|
|
5993
|
+
return true;
|
|
5994
|
+
state.isValidating = true;
|
|
5995
|
+
try {
|
|
5996
|
+
const isValid = await step.validate();
|
|
5997
|
+
if (isValid) {
|
|
5998
|
+
state.completedSteps.add(stepIndex);
|
|
5999
|
+
state.errorSteps.delete(stepIndex);
|
|
6000
|
+
}
|
|
6001
|
+
else {
|
|
6002
|
+
state.errorSteps.add(stepIndex);
|
|
6003
|
+
state.completedSteps.delete(stepIndex);
|
|
6004
|
+
}
|
|
6005
|
+
return isValid;
|
|
6006
|
+
}
|
|
6007
|
+
catch (error) {
|
|
6008
|
+
state.errorSteps.add(stepIndex);
|
|
6009
|
+
state.completedSteps.delete(stepIndex);
|
|
6010
|
+
return false;
|
|
6011
|
+
}
|
|
6012
|
+
finally {
|
|
6013
|
+
state.isValidating = false;
|
|
6014
|
+
m.redraw();
|
|
6015
|
+
}
|
|
6016
|
+
};
|
|
6017
|
+
const goToStep = async (stepIndex, attrs) => {
|
|
6018
|
+
const { linear = true, onStepChange, steps } = attrs;
|
|
6019
|
+
if (stepIndex < 0 || stepIndex >= steps.length)
|
|
6020
|
+
return false;
|
|
6021
|
+
// Check if step is disabled
|
|
6022
|
+
if (steps[stepIndex].disabled)
|
|
6023
|
+
return false;
|
|
6024
|
+
// In linear mode, validate all previous steps
|
|
6025
|
+
if (linear && stepIndex > state.currentStep) {
|
|
6026
|
+
for (let i = state.currentStep; i < stepIndex; i++) {
|
|
6027
|
+
const isValid = await validateStep(i, steps);
|
|
6028
|
+
if (!isValid && !steps[i].optional) {
|
|
6029
|
+
return false;
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
// Validate current step before moving forward
|
|
6034
|
+
if (stepIndex > state.currentStep) {
|
|
6035
|
+
const isValid = await validateStep(state.currentStep, steps);
|
|
6036
|
+
if (!isValid && !steps[state.currentStep].optional) {
|
|
6037
|
+
return false;
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
const oldStep = state.currentStep;
|
|
6041
|
+
state.currentStep = stepIndex;
|
|
6042
|
+
// Always call onStepChange when step changes
|
|
6043
|
+
if (onStepChange && oldStep !== stepIndex) {
|
|
6044
|
+
onStepChange(stepIndex, steps[stepIndex].id || `step-${stepIndex}`);
|
|
6045
|
+
}
|
|
6046
|
+
// Force redraw to update UI
|
|
6047
|
+
m.redraw();
|
|
6048
|
+
return true;
|
|
6049
|
+
};
|
|
6050
|
+
const nextStep = async (attrs) => {
|
|
6051
|
+
const { steps } = attrs;
|
|
6052
|
+
// Check if we're on the last step
|
|
6053
|
+
if (state.currentStep === steps.length - 1) {
|
|
6054
|
+
// This is the complete action
|
|
6055
|
+
if (attrs.onComplete) {
|
|
6056
|
+
attrs.onComplete();
|
|
6057
|
+
}
|
|
6058
|
+
return;
|
|
6059
|
+
}
|
|
6060
|
+
// Try to move to next step
|
|
6061
|
+
await goToStep(state.currentStep + 1, attrs);
|
|
6062
|
+
};
|
|
6063
|
+
const previousStep = (attrs) => {
|
|
6064
|
+
goToStep(state.currentStep - 1, attrs);
|
|
6065
|
+
};
|
|
6066
|
+
const skipStep = (attrs) => {
|
|
6067
|
+
const { steps } = attrs;
|
|
6068
|
+
const currentStepData = steps[state.currentStep];
|
|
6069
|
+
if (currentStepData && currentStepData.optional) {
|
|
6070
|
+
goToStep(state.currentStep + 1, attrs);
|
|
6071
|
+
}
|
|
6072
|
+
};
|
|
6073
|
+
return {
|
|
6074
|
+
oninit: ({ attrs }) => {
|
|
6075
|
+
state = {
|
|
6076
|
+
id: uniqueId(),
|
|
6077
|
+
currentStep: attrs.currentStep || 0,
|
|
6078
|
+
isValidating: false,
|
|
6079
|
+
completedSteps: new Set(),
|
|
6080
|
+
errorSteps: new Set()
|
|
6081
|
+
};
|
|
6082
|
+
},
|
|
6083
|
+
onbeforeupdate: ({ attrs }) => {
|
|
6084
|
+
// Sync external currentStep changes
|
|
6085
|
+
if (typeof attrs.currentStep === 'number' && attrs.currentStep !== state.currentStep) {
|
|
6086
|
+
state.currentStep = Math.max(0, attrs.currentStep);
|
|
6087
|
+
}
|
|
6088
|
+
},
|
|
6089
|
+
view: ({ attrs }) => {
|
|
6090
|
+
const { steps, showStepNumbers = true, className = '', showNavigation = true, labels = {}, orientation = 'horizontal', allowHeaderNavigation = false } = attrs;
|
|
6091
|
+
// Ensure currentStep is within bounds
|
|
6092
|
+
if (state.currentStep >= steps.length) {
|
|
6093
|
+
state.currentStep = Math.max(0, steps.length - 1);
|
|
6094
|
+
}
|
|
6095
|
+
const currentStepData = steps[state.currentStep];
|
|
6096
|
+
const isFirstStep = state.currentStep === 0;
|
|
6097
|
+
const isLastStep = state.currentStep === steps.length - 1;
|
|
6098
|
+
const activeContent = (currentStepData === null || currentStepData === void 0 ? void 0 : currentStepData.vnode) ? currentStepData.vnode() : null;
|
|
6099
|
+
return m('.wizard', { class: `${orientation} ${className}` }, [
|
|
6100
|
+
// Step indicator
|
|
6101
|
+
m('.wizard-header', [
|
|
6102
|
+
m('.wizard-steps', steps.map((step, index) => {
|
|
6103
|
+
const isActive = index === state.currentStep;
|
|
6104
|
+
const isCompleted = state.completedSteps.has(index);
|
|
6105
|
+
const hasError = state.errorSteps.has(index);
|
|
6106
|
+
return m('.wizard-step', {
|
|
6107
|
+
class: [
|
|
6108
|
+
isActive ? 'active' : '',
|
|
6109
|
+
isCompleted ? 'completed' : '',
|
|
6110
|
+
hasError ? 'error' : '',
|
|
6111
|
+
step.disabled ? 'disabled' : '',
|
|
6112
|
+
step.optional ? 'optional' : ''
|
|
6113
|
+
].filter(Boolean).join(' '),
|
|
6114
|
+
onclick: allowHeaderNavigation && !step.disabled ?
|
|
6115
|
+
() => goToStep(index, attrs) : undefined
|
|
6116
|
+
}, [
|
|
6117
|
+
// Step number/icon
|
|
6118
|
+
m('.wizard-step-indicator', [
|
|
6119
|
+
isCompleted ?
|
|
6120
|
+
m('i.material-icons', 'check') :
|
|
6121
|
+
hasError ?
|
|
6122
|
+
m('i.material-icons', 'error') :
|
|
6123
|
+
step.icon ?
|
|
6124
|
+
m('i.material-icons', step.icon) :
|
|
6125
|
+
showStepNumbers ?
|
|
6126
|
+
m('span.wizard-step-number', index + 1) :
|
|
6127
|
+
null
|
|
6128
|
+
]),
|
|
6129
|
+
// Step content
|
|
6130
|
+
m('.wizard-step-content', [
|
|
6131
|
+
m('.wizard-step-title', step.title),
|
|
6132
|
+
step.subtitle && m('.wizard-step-subtitle', step.subtitle),
|
|
6133
|
+
step.optional && m('.wizard-step-optional', labels.optional || 'Optional')
|
|
6134
|
+
]),
|
|
6135
|
+
// Connector line (except for last step in horizontal mode)
|
|
6136
|
+
orientation === 'horizontal' && index < steps.length - 1 &&
|
|
6137
|
+
m('.wizard-step-connector')
|
|
6138
|
+
]);
|
|
6139
|
+
}))
|
|
6140
|
+
]),
|
|
6141
|
+
// Step content
|
|
6142
|
+
m('.wizard-body', [
|
|
6143
|
+
activeContent && m('.wizard-step-panel', {
|
|
6144
|
+
key: (currentStepData === null || currentStepData === void 0 ? void 0 : currentStepData.id) || `step-${state.currentStep}`
|
|
6145
|
+
}, activeContent)
|
|
6146
|
+
]),
|
|
6147
|
+
// Navigation
|
|
6148
|
+
showNavigation && m('.wizard-footer', [
|
|
6149
|
+
m('.wizard-navigation', [
|
|
6150
|
+
// Previous button
|
|
6151
|
+
!isFirstStep && m('button.btn-flat.wizard-btn-previous', {
|
|
6152
|
+
onclick: () => previousStep(attrs),
|
|
6153
|
+
disabled: state.isValidating
|
|
6154
|
+
}, labels.previous || 'Previous'),
|
|
6155
|
+
// Skip button (for optional steps)
|
|
6156
|
+
currentStepData && currentStepData.optional && !isLastStep &&
|
|
6157
|
+
m('button.btn-flat.wizard-btn-skip', {
|
|
6158
|
+
onclick: () => skipStep(attrs),
|
|
6159
|
+
disabled: state.isValidating
|
|
6160
|
+
}, labels.skip || 'Skip'),
|
|
6161
|
+
// Next/Complete button
|
|
6162
|
+
m('button.btn.wizard-btn-next', {
|
|
6163
|
+
onclick: () => nextStep(attrs),
|
|
6164
|
+
disabled: state.isValidating,
|
|
6165
|
+
class: isLastStep ? 'wizard-btn-complete' : ''
|
|
6166
|
+
}, [
|
|
6167
|
+
state.isValidating && m('i.material-icons.left', 'hourglass_empty'),
|
|
6168
|
+
isLastStep ? (labels.complete || 'Complete') : (labels.next || 'Next')
|
|
6169
|
+
])
|
|
6170
|
+
])
|
|
6171
|
+
])
|
|
6172
|
+
]);
|
|
6173
|
+
}
|
|
6174
|
+
};
|
|
6175
|
+
};
|
|
6176
|
+
/**
|
|
6177
|
+
* Simple linear stepper for forms
|
|
6178
|
+
*/
|
|
6179
|
+
const Stepper = () => {
|
|
6180
|
+
return {
|
|
6181
|
+
view: ({ attrs }) => {
|
|
6182
|
+
return m(Wizard, Object.assign(Object.assign({}, attrs), { linear: true, showNavigation: false, allowHeaderNavigation: false, orientation: 'horizontal' }));
|
|
6183
|
+
}
|
|
6184
|
+
};
|
|
6185
|
+
};
|
|
6186
|
+
|
|
5346
6187
|
exports.AnchorItem = AnchorItem;
|
|
5347
6188
|
exports.Autocomplete = Autocomplete;
|
|
6189
|
+
exports.Breadcrumb = Breadcrumb;
|
|
6190
|
+
exports.BreadcrumbManager = BreadcrumbManager;
|
|
5348
6191
|
exports.Button = Button;
|
|
5349
6192
|
exports.ButtonFactory = ButtonFactory;
|
|
5350
6193
|
exports.Carousel = Carousel;
|
|
@@ -5359,6 +6202,7 @@
|
|
|
5359
6202
|
exports.Dropdown = Dropdown;
|
|
5360
6203
|
exports.EmailInput = EmailInput;
|
|
5361
6204
|
exports.FileInput = FileInput;
|
|
6205
|
+
exports.FileUpload = FileUpload;
|
|
5362
6206
|
exports.FlatButton = FlatButton;
|
|
5363
6207
|
exports.FloatingActionButton = FloatingActionButton;
|
|
5364
6208
|
exports.HelperText = HelperText;
|
|
@@ -5384,18 +6228,27 @@
|
|
|
5384
6228
|
exports.SearchSelect = SearchSelect;
|
|
5385
6229
|
exports.SecondaryContent = SecondaryContent;
|
|
5386
6230
|
exports.Select = Select;
|
|
6231
|
+
exports.Sidenav = Sidenav;
|
|
6232
|
+
exports.SidenavItem = SidenavItem;
|
|
6233
|
+
exports.SidenavManager = SidenavManager;
|
|
5387
6234
|
exports.SmallButton = SmallButton;
|
|
6235
|
+
exports.Stepper = Stepper;
|
|
5388
6236
|
exports.SubmitButton = SubmitButton;
|
|
5389
6237
|
exports.Switch = Switch;
|
|
5390
6238
|
exports.Tabs = Tabs;
|
|
5391
6239
|
exports.TextArea = TextArea;
|
|
5392
6240
|
exports.TextInput = TextInput;
|
|
6241
|
+
exports.ThemeManager = ThemeManager;
|
|
6242
|
+
exports.ThemeSwitcher = ThemeSwitcher;
|
|
6243
|
+
exports.ThemeToggle = ThemeToggle;
|
|
5393
6244
|
exports.TimePicker = TimePicker;
|
|
5394
6245
|
exports.Toast = Toast;
|
|
5395
6246
|
exports.ToastComponent = ToastComponent;
|
|
5396
6247
|
exports.Tooltip = Tooltip;
|
|
5397
6248
|
exports.TooltipComponent = TooltipComponent;
|
|
5398
6249
|
exports.UrlInput = UrlInput;
|
|
6250
|
+
exports.Wizard = Wizard;
|
|
6251
|
+
exports.createBreadcrumb = createBreadcrumb;
|
|
5399
6252
|
exports.getDropdownStyles = getDropdownStyles;
|
|
5400
6253
|
exports.initPushpins = initPushpins;
|
|
5401
6254
|
exports.initTooltips = initTooltips;
|