mithril-materialized 3.15.0 → 3.17.0
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 +32 -2
- package/dist/advanced.css +109 -4
- package/dist/button.d.ts +3 -1
- package/dist/circular-progress.d.ts +2 -2
- package/dist/combobox.d.ts +35 -0
- package/dist/components.css +144 -17
- package/dist/core.css +278 -3
- package/dist/floating-action-button.d.ts +2 -1
- package/dist/form-section.d.ts +35 -0
- package/dist/forms.css +280 -5
- package/dist/index.css +357 -22
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.js +728 -299
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +741 -298
- package/dist/index.js.map +1 -0
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +741 -298
- package/dist/index.umd.js.map +1 -0
- package/dist/input-options.d.ts +4 -1
- package/dist/likert-scale.d.ts +2 -1
- package/dist/linear-progress.d.ts +2 -2
- package/dist/material-icon.d.ts +2 -1
- package/dist/radio.d.ts +9 -0
- package/dist/range-slider.d.ts +3 -2
- package/dist/rating.d.ts +2 -1
- package/dist/search-select.d.ts +15 -2
- package/dist/select.d.ts +14 -1
- package/dist/sidenav.d.ts +11 -1
- package/dist/treeview.d.ts +2 -2
- package/dist/types.d.ts +5 -0
- package/dist/utilities.css +93 -2
- package/dist/utils.d.ts +30 -1
- package/package.json +20 -20
- package/sass/components/_badge-component.scss +8 -0
- package/sass/components/_buttons.scss +9 -9
- package/sass/components/_global.scss +93 -0
- package/sass/components/_likert-scale.scss +24 -0
- package/sass/components/_sidenav.scss +21 -3
- package/sass/components/_theme-variables.scss +22 -1
- package/sass/components/_toggle-group.scss +3 -0
- package/sass/components/forms/_form-groups.scss +1 -1
- package/sass/components/forms/_form-section.scss +63 -0
- package/sass/components/forms/_forms.scss +1 -0
- package/sass/components/forms/_input-fields.scss +55 -0
- package/sass/components/forms/_range-enhanced.scss +3 -3
- package/sass/components/forms/_select.scss +85 -0
package/dist/index.umd.js
CHANGED
|
@@ -38,6 +38,35 @@
|
|
|
38
38
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// import './styles/input.css';
|
|
42
|
+
const Mandatory = { view: ({ attrs }) => m('span.mandatory', Object.assign({}, attrs), '*') };
|
|
43
|
+
/** Simple label element, used for most components. */
|
|
44
|
+
const Label = () => {
|
|
45
|
+
return {
|
|
46
|
+
view: (_a) => {
|
|
47
|
+
var _b = _a.attrs, { label, id, isMandatory, isActive, className, initialValue } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className", "initialValue"]);
|
|
48
|
+
return label
|
|
49
|
+
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id, oncreate: ({ dom }) => {
|
|
50
|
+
if (!initialValue)
|
|
51
|
+
return;
|
|
52
|
+
const labelEl = dom;
|
|
53
|
+
labelEl.classList.add('active');
|
|
54
|
+
} }), [m.trust(label), isMandatory ? m(Mandatory) : undefined])
|
|
55
|
+
: undefined;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/** Create a helper text, often used for displaying a small help text. May be replaced by the validation message. */
|
|
60
|
+
const HelperText = () => {
|
|
61
|
+
return {
|
|
62
|
+
view: ({ attrs: { helperText, dataError, dataSuccess, className } }) => {
|
|
63
|
+
return helperText || dataError || dataSuccess
|
|
64
|
+
? m('span.helper-text.left', { className, 'data-error': dataError, 'data-success': dataSuccess }, dataError ? m.trust(dataError) : dataSuccess ? m.trust(dataSuccess) : helperText ? m.trust(helperText) : '')
|
|
65
|
+
: undefined;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
41
70
|
// Utility functions for the library
|
|
42
71
|
/**
|
|
43
72
|
* Create a unique ID
|
|
@@ -98,6 +127,42 @@
|
|
|
98
127
|
* @returns
|
|
99
128
|
*/
|
|
100
129
|
const padLeft = (n, width = 2, z = '0') => String(n).padStart(width, z);
|
|
130
|
+
const normalizeSelection = (value) => {
|
|
131
|
+
if (value === undefined) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return Array.isArray(value) ? value : [value];
|
|
135
|
+
};
|
|
136
|
+
const resolveControllableValue = ({ controlled, disabled, controlledValue, defaultValue, internalValue, fallbackValue, }) => {
|
|
137
|
+
var _a, _b;
|
|
138
|
+
if (controlled) {
|
|
139
|
+
return controlledValue !== null && controlledValue !== void 0 ? controlledValue : fallbackValue;
|
|
140
|
+
}
|
|
141
|
+
if (disabled) {
|
|
142
|
+
return (_a = defaultValue !== null && defaultValue !== void 0 ? defaultValue : controlledValue) !== null && _a !== void 0 ? _a : fallbackValue;
|
|
143
|
+
}
|
|
144
|
+
return (_b = internalValue !== null && internalValue !== void 0 ? internalValue : defaultValue) !== null && _b !== void 0 ? _b : fallbackValue;
|
|
145
|
+
};
|
|
146
|
+
const renderFieldChrome = ({ label, id, isMandatory, isActive, initialValue, helperText, dataError, dataSuccess, }) => {
|
|
147
|
+
return [
|
|
148
|
+
label
|
|
149
|
+
? m(Label, {
|
|
150
|
+
label,
|
|
151
|
+
id,
|
|
152
|
+
isMandatory,
|
|
153
|
+
isActive,
|
|
154
|
+
initialValue,
|
|
155
|
+
})
|
|
156
|
+
: undefined,
|
|
157
|
+
helperText || dataError || dataSuccess
|
|
158
|
+
? m(HelperText, {
|
|
159
|
+
helperText,
|
|
160
|
+
dataError,
|
|
161
|
+
dataSuccess,
|
|
162
|
+
})
|
|
163
|
+
: undefined,
|
|
164
|
+
].filter(Boolean);
|
|
165
|
+
};
|
|
101
166
|
// Keep only essential dropdown positioning styles
|
|
102
167
|
const getDropdownStyles = (inputRef, overlap = false, options, isDropDown = false) => {
|
|
103
168
|
if (!inputRef) {
|
|
@@ -129,8 +194,10 @@
|
|
|
129
194
|
groups.add(option.group);
|
|
130
195
|
}
|
|
131
196
|
});
|
|
132
|
-
//
|
|
133
|
-
|
|
197
|
+
// Match the select dropdown's CSS max-height. Positioning with the full
|
|
198
|
+
// option count would otherwise leave a gap above an input near the bottom
|
|
199
|
+
// of the viewport when the rendered menu is capped at 400px.
|
|
200
|
+
estimatedHeight = Math.min(totalOptions * itemHeight + groups.size * groupHeaderHeight, 400);
|
|
134
201
|
}
|
|
135
202
|
const spaceBelow = viewportHeight - rect.bottom;
|
|
136
203
|
const spaceAbove = rect.top;
|
|
@@ -146,33 +213,26 @@
|
|
|
146
213
|
const needsScrolling = estimatedHeight > effectiveAvailableSpace;
|
|
147
214
|
// Calculate the actual height the dropdown will take
|
|
148
215
|
const actualHeight = needsScrolling ? effectiveAvailableSpace : estimatedHeight;
|
|
149
|
-
// Calculate positioning when dropdown should appear above
|
|
150
|
-
let topOffset;
|
|
151
|
-
if (shouldPositionAbove) {
|
|
152
|
-
// Calculate how much space we actually have from top of viewport to top of input
|
|
153
|
-
const availableSpaceFromViewportTop = rect.top;
|
|
154
|
-
// If dropdown fits comfortably above input, use normal positioning
|
|
155
|
-
if (actualHeight <= availableSpaceFromViewportTop) {
|
|
156
|
-
topOffset = 12 - actualHeight + (isDropDown ? itemHeight : 0); // Bottom of dropdown aligns with top of input
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
// If dropdown is too tall, position it at the very top of viewport
|
|
160
|
-
// This makes the dropdown use all available space from viewport top to input top
|
|
161
|
-
topOffset = -availableSpaceFromViewportTop + 5; // 5px margin from viewport top
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
topOffset = overlap ? 0 : '100%';
|
|
166
|
-
}
|
|
167
216
|
const styles = {
|
|
168
217
|
display: 'block',
|
|
169
218
|
opacity: 1,
|
|
170
219
|
position: 'absolute',
|
|
171
|
-
top: typeof topOffset === 'number' ? `${topOffset}px` : topOffset,
|
|
172
220
|
left: '0',
|
|
173
221
|
zIndex: 1000,
|
|
174
222
|
width: `${rect.width}px`,
|
|
175
223
|
};
|
|
224
|
+
if (shouldPositionAbove) {
|
|
225
|
+
// This menu is absolutely positioned inside the select wrapper. A negative
|
|
226
|
+
// `top` calculated from viewport coordinates becomes wrong when an ancestor
|
|
227
|
+
// scrolls (as ScenarioSpark's main area does). Anchor the menu to its local
|
|
228
|
+
// containing block instead; its height can then change without detaching it
|
|
229
|
+
// from the input.
|
|
230
|
+
styles.top = 'auto';
|
|
231
|
+
styles.bottom = `${isDropDown ? -18 : inputRef.offsetHeight - 12}px`;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
styles.top = overlap ? 0 : '100%';
|
|
235
|
+
}
|
|
176
236
|
// Only add scrolling constraints when necessary
|
|
177
237
|
if (needsScrolling) {
|
|
178
238
|
styles.maxHeight = `${actualHeight}px`;
|
|
@@ -239,9 +299,17 @@
|
|
|
239
299
|
* @param zIndex - Z-index for portal container (default: 1004)
|
|
240
300
|
*/
|
|
241
301
|
const renderToPortal = (containerId, vnode, zIndex = 1004) => {
|
|
242
|
-
const
|
|
302
|
+
const existingContainer = portalContainers.get(containerId);
|
|
303
|
+
const container = existingContainer ? existingContainer.element : getPortalContainer(containerId, zIndex);
|
|
243
304
|
m.render(container, vnode);
|
|
244
305
|
};
|
|
306
|
+
const syncPortalContent = ({ containerId, shouldRender, vnode, zIndex = 1004 }) => {
|
|
307
|
+
if (!shouldRender || vnode === null) {
|
|
308
|
+
clearPortal(containerId);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
renderToPortal(containerId, vnode, zIndex);
|
|
312
|
+
};
|
|
245
313
|
/**
|
|
246
314
|
* Clears portal content and releases container reference.
|
|
247
315
|
* If this is the last reference, the container will be removed from the DOM.
|
|
@@ -256,35 +324,6 @@
|
|
|
256
324
|
}
|
|
257
325
|
};
|
|
258
326
|
|
|
259
|
-
// import './styles/input.css';
|
|
260
|
-
const Mandatory = { view: ({ attrs }) => m('span.mandatory', Object.assign({}, attrs), '*') };
|
|
261
|
-
/** Simple label element, used for most components. */
|
|
262
|
-
const Label = () => {
|
|
263
|
-
return {
|
|
264
|
-
view: (_a) => {
|
|
265
|
-
var _b = _a.attrs, { label, id, isMandatory, isActive, className, initialValue } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className", "initialValue"]);
|
|
266
|
-
return label
|
|
267
|
-
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id, oncreate: ({ dom }) => {
|
|
268
|
-
if (!initialValue)
|
|
269
|
-
return;
|
|
270
|
-
const labelEl = dom;
|
|
271
|
-
labelEl.classList.add('active');
|
|
272
|
-
} }), [m.trust(label), isMandatory ? m(Mandatory) : undefined])
|
|
273
|
-
: undefined;
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
};
|
|
277
|
-
/** Create a helper text, often used for displaying a small help text. May be replaced by the validation message. */
|
|
278
|
-
const HelperText = () => {
|
|
279
|
-
return {
|
|
280
|
-
view: ({ attrs: { helperText, dataError, dataSuccess, className } }) => {
|
|
281
|
-
return helperText || dataError || dataSuccess
|
|
282
|
-
? m('span.helper-text.left', { className, 'data-error': dataError, 'data-success': dataSuccess }, dataError ? m.trust(dataError) : dataSuccess ? m.trust(dataSuccess) : helperText ? m.trust(helperText) : '')
|
|
283
|
-
: undefined;
|
|
284
|
-
},
|
|
285
|
-
};
|
|
286
|
-
};
|
|
287
|
-
|
|
288
327
|
/** Component to auto complete your text input - Pure Mithril implementation */
|
|
289
328
|
const Autocomplete = () => {
|
|
290
329
|
const state = {
|
|
@@ -307,6 +346,28 @@
|
|
|
307
346
|
.slice(0, limit);
|
|
308
347
|
return filtered;
|
|
309
348
|
};
|
|
349
|
+
const highlightMatch = (text, query) => {
|
|
350
|
+
if (!query) {
|
|
351
|
+
return text;
|
|
352
|
+
}
|
|
353
|
+
const lowerText = text.toLowerCase();
|
|
354
|
+
const lowerQuery = query.toLowerCase();
|
|
355
|
+
const nodes = [];
|
|
356
|
+
let start = 0;
|
|
357
|
+
let index = lowerText.indexOf(lowerQuery, start);
|
|
358
|
+
while (index !== -1) {
|
|
359
|
+
if (index > start) {
|
|
360
|
+
nodes.push(text.slice(start, index));
|
|
361
|
+
}
|
|
362
|
+
nodes.push(m('span.highlight', text.slice(index, index + query.length)));
|
|
363
|
+
start = index + query.length;
|
|
364
|
+
index = lowerText.indexOf(lowerQuery, start);
|
|
365
|
+
}
|
|
366
|
+
if (start < text.length) {
|
|
367
|
+
nodes.push(text.slice(start));
|
|
368
|
+
}
|
|
369
|
+
return nodes.length > 0 ? nodes : text;
|
|
370
|
+
};
|
|
310
371
|
const selectSuggestion = (suggestion, attrs) => {
|
|
311
372
|
const controlled = isControlled(attrs);
|
|
312
373
|
// Update internal state for uncontrolled mode
|
|
@@ -412,7 +473,6 @@
|
|
|
412
473
|
Object.keys(data).some((key) => key.toLowerCase() === currentValue.toLowerCase());
|
|
413
474
|
// Only open dropdown if there are suggestions and no perfect match
|
|
414
475
|
state.isOpen = state.suggestions.length > 0 && currentValue.length >= minLength && !hasExactMatch;
|
|
415
|
-
const replacer = new RegExp(`(${currentValue})`, 'i');
|
|
416
476
|
return m('.input-field.autocomplete-wrapper', {
|
|
417
477
|
className: cn,
|
|
418
478
|
style,
|
|
@@ -493,9 +553,7 @@
|
|
|
493
553
|
},
|
|
494
554
|
}, suggestion.value.replace('icon:', ''))
|
|
495
555
|
: null,
|
|
496
|
-
m('span', suggestion.key
|
|
497
|
-
? m.trust(suggestion.key.replace(replacer, (i) => `<span class="highlight">${i}</span>`))
|
|
498
|
-
: ''),
|
|
556
|
+
m('span', suggestion.key ? highlightMatch(suggestion.key, currentValue) : ''),
|
|
499
557
|
]))),
|
|
500
558
|
m(Label, {
|
|
501
559
|
label,
|
|
@@ -722,7 +780,7 @@
|
|
|
722
780
|
/**
|
|
723
781
|
* A factory to create new buttons.
|
|
724
782
|
*
|
|
725
|
-
* @example FlatButton = ButtonFactory('
|
|
783
|
+
* @example FlatButton = ButtonFactory('button.waves-effect.waves-teal.btn-flat');
|
|
726
784
|
*/
|
|
727
785
|
const ButtonFactory = (element, defaultClassNames, type = '') => {
|
|
728
786
|
return () => {
|
|
@@ -745,15 +803,21 @@
|
|
|
745
803
|
ontouchend: WavesEffect.onTouchEnd,
|
|
746
804
|
}
|
|
747
805
|
: {};
|
|
748
|
-
|
|
806
|
+
const tagName = element.split(/[.#]/)[0] || element;
|
|
807
|
+
const elementSuffix = element.slice(tagName.length);
|
|
808
|
+
const isLink = Boolean(attrs.href);
|
|
809
|
+
const renderedElement = `${isLink ? 'a' : 'button'}${elementSuffix}`;
|
|
810
|
+
return m(renderedElement, Object.assign(Object.assign(Object.assign({}, params), wavesHandlers), { className: cn, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined,
|
|
811
|
+
// Links retain native navigation semantics and must not have a button type.
|
|
812
|
+
type: isLink ? undefined : buttonType }), iconName ? m(Icon, { iconName, className: iconClass !== undefined ? iconClass : 'left' }) : undefined, label ? label : undefined, children);
|
|
749
813
|
},
|
|
750
814
|
};
|
|
751
815
|
};
|
|
752
816
|
};
|
|
753
|
-
const Button = ButtonFactory('
|
|
754
|
-
const LargeButton = ButtonFactory('
|
|
755
|
-
const SmallButton = ButtonFactory('
|
|
756
|
-
const FlatButton = ButtonFactory('
|
|
817
|
+
const Button = ButtonFactory('button', 'waves-effect waves-light btn', 'button');
|
|
818
|
+
const LargeButton = ButtonFactory('button', 'waves-effect waves-light btn-large', 'button');
|
|
819
|
+
const SmallButton = ButtonFactory('button', 'waves-effect waves-light btn-small', 'button');
|
|
820
|
+
const FlatButton = ButtonFactory('button', 'waves-effect waves-teal btn-flat', 'button');
|
|
757
821
|
const IconButton = ButtonFactory('button', 'btn-flat btn-icon waves-effect waves-teal', 'button');
|
|
758
822
|
const RoundIconButton = ButtonFactory('button', 'btn-floating btn-large waves-effect waves-light', 'button');
|
|
759
823
|
const SubmitButton = ButtonFactory('button', 'btn waves-effect waves-light', 'submit');
|
|
@@ -810,7 +874,7 @@
|
|
|
810
874
|
// Add square styling for icon-only confirming state
|
|
811
875
|
const buttonStyle = !label && isConfirming
|
|
812
876
|
? Object.assign(Object.assign({}, props.style), { width: '36px', height: '36px', padding: '0', minWidth: '28px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', borderRadius: '2px' }) : props.style;
|
|
813
|
-
return m(ButtonComponent, Object.assign(Object.assign({}, props), { style: buttonStyle, className: `${props.className || ''} ${cn}`, iconName: currentIconName, iconClass: label ?
|
|
877
|
+
return m(ButtonComponent, Object.assign(Object.assign({}, props), { style: buttonStyle, className: `${props.className || ''} ${cn}`, iconName: currentIconName, iconClass: label ? iconClass || 'left' : '', label, onclick: handleClick }));
|
|
814
878
|
},
|
|
815
879
|
};
|
|
816
880
|
};
|
|
@@ -1277,11 +1341,19 @@
|
|
|
1277
1341
|
};
|
|
1278
1342
|
const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
|
|
1279
1343
|
const transform = rotation ? `rotate(${rotation}deg)` : undefined;
|
|
1344
|
+
const baseStyle = {
|
|
1345
|
+
display: 'inline-block',
|
|
1346
|
+
verticalAlign: 'middle',
|
|
1347
|
+
transform,
|
|
1348
|
+
};
|
|
1349
|
+
const combinedStyle = typeof style === 'string'
|
|
1350
|
+
? `display:inline-block;vertical-align:middle;${transform ? `transform:${transform};` : ''}${style}`
|
|
1351
|
+
: Object.assign(Object.assign({}, baseStyle), style);
|
|
1280
1352
|
const icon = iconPaths[name];
|
|
1281
1353
|
if (!icon || !Array.isArray(icon)) {
|
|
1282
1354
|
return m(Icon, Object.assign(Object.assign({}, props), { iconName: name }));
|
|
1283
1355
|
}
|
|
1284
|
-
return m('svg', Object.assign(Object.assign({}, props), { style:
|
|
1356
|
+
return m('svg', Object.assign(Object.assign({}, props), { style: combinedStyle, height: '24px', width: '24px', viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }), icon === null || icon === void 0 ? void 0 : icon.map((d) => m('path', {
|
|
1285
1357
|
d,
|
|
1286
1358
|
fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
|
|
1287
1359
|
})));
|
|
@@ -1680,6 +1752,106 @@
|
|
|
1680
1752
|
};
|
|
1681
1753
|
};
|
|
1682
1754
|
|
|
1755
|
+
const isHandledKey = (key) => key === 'ArrowDown' || key === 'ArrowUp' || key === 'Enter' || key === ' ' || key === 'Escape';
|
|
1756
|
+
const getTotalRows = (optionCount, includeActionRow) => Math.max(0, optionCount) + (includeActionRow ? 1 : 0);
|
|
1757
|
+
const getComboboxKeyResult = ({ key, isOpen, focusedIndex, optionCount, includeActionRow, }) => {
|
|
1758
|
+
const totalRows = getTotalRows(optionCount, includeActionRow);
|
|
1759
|
+
if (!isHandledKey(key)) {
|
|
1760
|
+
return { isOpen, focusedIndex, action: 'none', preventDefault: false };
|
|
1761
|
+
}
|
|
1762
|
+
if (key === 'Escape') {
|
|
1763
|
+
return { isOpen: false, focusedIndex: -1, action: 'close', preventDefault: true };
|
|
1764
|
+
}
|
|
1765
|
+
if (key === 'ArrowDown') {
|
|
1766
|
+
if (!isOpen) {
|
|
1767
|
+
return {
|
|
1768
|
+
isOpen: true,
|
|
1769
|
+
focusedIndex: totalRows > 0 ? 0 : -1,
|
|
1770
|
+
action: 'open',
|
|
1771
|
+
preventDefault: true,
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
return {
|
|
1775
|
+
isOpen: true,
|
|
1776
|
+
focusedIndex: totalRows > 0 ? Math.min(focusedIndex + 1, totalRows - 1) : -1,
|
|
1777
|
+
action: 'none',
|
|
1778
|
+
preventDefault: true,
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
if (key === 'ArrowUp') {
|
|
1782
|
+
if (!isOpen) {
|
|
1783
|
+
return {
|
|
1784
|
+
isOpen: true,
|
|
1785
|
+
focusedIndex: totalRows > 0 ? 0 : -1,
|
|
1786
|
+
action: 'open',
|
|
1787
|
+
preventDefault: true,
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
return {
|
|
1791
|
+
isOpen: true,
|
|
1792
|
+
focusedIndex: totalRows > 0 ? Math.max(focusedIndex - 1, 0) : -1,
|
|
1793
|
+
action: 'none',
|
|
1794
|
+
preventDefault: true,
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
if (!isOpen) {
|
|
1798
|
+
return {
|
|
1799
|
+
isOpen: true,
|
|
1800
|
+
focusedIndex: totalRows > 0 ? 0 : -1,
|
|
1801
|
+
action: 'open',
|
|
1802
|
+
preventDefault: true,
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
if (focusedIndex < 0) {
|
|
1806
|
+
return { isOpen, focusedIndex, action: 'none', preventDefault: true };
|
|
1807
|
+
}
|
|
1808
|
+
const isActionRow = includeActionRow && focusedIndex === optionCount;
|
|
1809
|
+
return {
|
|
1810
|
+
isOpen,
|
|
1811
|
+
focusedIndex,
|
|
1812
|
+
action: isActionRow ? 'selectAction' : 'selectFocused',
|
|
1813
|
+
preventDefault: true,
|
|
1814
|
+
};
|
|
1815
|
+
};
|
|
1816
|
+
const getComboboxOptionId = (baseId, optionIndex) => `${baseId}-option-${optionIndex}`;
|
|
1817
|
+
const createAsyncComboboxState = (initialOptions = []) => ({
|
|
1818
|
+
options: initialOptions,
|
|
1819
|
+
isLoading: false,
|
|
1820
|
+
error: null,
|
|
1821
|
+
latestRequestId: 0,
|
|
1822
|
+
});
|
|
1823
|
+
const startAsyncComboboxRequest = (state) => {
|
|
1824
|
+
const requestId = state.latestRequestId + 1;
|
|
1825
|
+
return {
|
|
1826
|
+
requestId,
|
|
1827
|
+
nextState: Object.assign(Object.assign({}, state), { isLoading: true, error: null, latestRequestId: requestId }),
|
|
1828
|
+
};
|
|
1829
|
+
};
|
|
1830
|
+
const resolveAsyncComboboxRequest = (state, requestId, options) => {
|
|
1831
|
+
if (requestId !== state.latestRequestId) {
|
|
1832
|
+
return state;
|
|
1833
|
+
}
|
|
1834
|
+
return Object.assign(Object.assign({}, state), { options, isLoading: false, error: null });
|
|
1835
|
+
};
|
|
1836
|
+
const rejectAsyncComboboxRequest = (state, requestId, errorMessage) => {
|
|
1837
|
+
if (requestId !== state.latestRequestId) {
|
|
1838
|
+
return state;
|
|
1839
|
+
}
|
|
1840
|
+
return Object.assign(Object.assign({}, state), { options: [], isLoading: false, error: errorMessage });
|
|
1841
|
+
};
|
|
1842
|
+
const getComboboxViewState = ({ isLoading, error, optionCount, }) => {
|
|
1843
|
+
if (isLoading) {
|
|
1844
|
+
return 'loading';
|
|
1845
|
+
}
|
|
1846
|
+
if (error) {
|
|
1847
|
+
return 'error';
|
|
1848
|
+
}
|
|
1849
|
+
if (optionCount === 0) {
|
|
1850
|
+
return 'empty';
|
|
1851
|
+
}
|
|
1852
|
+
return 'ready';
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1683
1855
|
exports.CollectionMode = void 0;
|
|
1684
1856
|
(function (CollectionMode) {
|
|
1685
1857
|
CollectionMode[CollectionMode["BASIC"] = 0] = "BASIC";
|
|
@@ -2379,6 +2551,7 @@
|
|
|
2379
2551
|
e.stopPropagation();
|
|
2380
2552
|
gotoMonth(index);
|
|
2381
2553
|
state.monthDropdownOpen = false;
|
|
2554
|
+
m.redraw();
|
|
2382
2555
|
},
|
|
2383
2556
|
}, monthName))),
|
|
2384
2557
|
]),
|
|
@@ -2403,6 +2576,7 @@
|
|
|
2403
2576
|
e.stopPropagation();
|
|
2404
2577
|
gotoYear(i);
|
|
2405
2578
|
state.yearDropdownOpen = false;
|
|
2579
|
+
m.redraw();
|
|
2406
2580
|
},
|
|
2407
2581
|
}, i))),
|
|
2408
2582
|
]),
|
|
@@ -3287,6 +3461,10 @@
|
|
|
3287
3461
|
},
|
|
3288
3462
|
};
|
|
3289
3463
|
|
|
3464
|
+
const isReadonly = (attrs) => {
|
|
3465
|
+
const legacyReadonly = attrs.readonly;
|
|
3466
|
+
return Boolean(attrs.readOnly || legacyReadonly);
|
|
3467
|
+
};
|
|
3290
3468
|
/** Character counter component that tracks text length against maxLength */
|
|
3291
3469
|
const CharacterCounter = () => {
|
|
3292
3470
|
return {
|
|
@@ -3365,7 +3543,7 @@
|
|
|
3365
3543
|
return {
|
|
3366
3544
|
oninit: ({ attrs }) => {
|
|
3367
3545
|
const controlled = isControlled(attrs);
|
|
3368
|
-
const isNonInteractive = attrs
|
|
3546
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3369
3547
|
// Warn developer for improper controlled usage
|
|
3370
3548
|
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
3371
3549
|
console.warn(`TextArea received 'value' prop without 'oninput' or 'onchange' handler. ` +
|
|
@@ -3382,7 +3560,7 @@
|
|
|
3382
3560
|
var _a, _b, _c, _d;
|
|
3383
3561
|
const { className = 'col s12', helperText, iconName, id = state.id, value, placeholder, isMandatory, label, maxLength, oninput, onchange, onkeydown, onkeypress, onkeyup, onblur, style } = attrs, params = __rest(attrs, ["className", "helperText", "iconName", "id", "value", "placeholder", "isMandatory", "label", "maxLength", "oninput", "onchange", "onkeydown", "onkeypress", "onkeyup", "onblur", "style"]);
|
|
3384
3562
|
const controlled = isControlled(attrs);
|
|
3385
|
-
const isNonInteractive = attrs
|
|
3563
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3386
3564
|
let currentValue;
|
|
3387
3565
|
if (controlled) {
|
|
3388
3566
|
currentValue = value || '';
|
|
@@ -3498,14 +3676,12 @@
|
|
|
3498
3676
|
onkeypress(ev, ev.target.value);
|
|
3499
3677
|
}
|
|
3500
3678
|
: undefined })),
|
|
3501
|
-
|
|
3679
|
+
...renderFieldChrome({
|
|
3502
3680
|
label,
|
|
3503
3681
|
id,
|
|
3504
3682
|
isMandatory,
|
|
3505
3683
|
isActive: currentValue || placeholder || state.active,
|
|
3506
3684
|
initialValue: currentValue !== '',
|
|
3507
|
-
}),
|
|
3508
|
-
m(HelperText, {
|
|
3509
3685
|
helperText,
|
|
3510
3686
|
dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
|
|
3511
3687
|
dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
|
|
@@ -3585,7 +3761,7 @@
|
|
|
3585
3761
|
return {
|
|
3586
3762
|
oninit: ({ attrs }) => {
|
|
3587
3763
|
const controlled = isControlled(attrs);
|
|
3588
|
-
const isNonInteractive = attrs
|
|
3764
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3589
3765
|
// Warn developer for improper controlled usage
|
|
3590
3766
|
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
3591
3767
|
console.warn(`${type} input with label '${attrs.label}' received 'value' prop without 'oninput' handler. ` +
|
|
@@ -3625,7 +3801,7 @@
|
|
|
3625
3801
|
}
|
|
3626
3802
|
const isNumeric = ['number', 'range'].includes(type);
|
|
3627
3803
|
const controlled = isControlled(attrs);
|
|
3628
|
-
const isNonInteractive = attrs
|
|
3804
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3629
3805
|
let value;
|
|
3630
3806
|
if (controlled) {
|
|
3631
3807
|
value = attrs.value;
|
|
@@ -3644,9 +3820,28 @@
|
|
|
3644
3820
|
const rangeType = type === 'range' && !attrs.minmax;
|
|
3645
3821
|
// Only add validate class if input is interactive and validation is needed
|
|
3646
3822
|
const shouldValidate = !isNonInteractive && (validate || type === 'email' || type === 'url' || isNumeric);
|
|
3823
|
+
const inputClass = [
|
|
3824
|
+
type === 'number' ? 'number-input' : '',
|
|
3825
|
+
type === 'range' && attrs.vertical ? 'range-slider vertical' : '',
|
|
3826
|
+
shouldValidate ? 'validate' : '',
|
|
3827
|
+
]
|
|
3828
|
+
.filter(Boolean)
|
|
3829
|
+
.join(' ');
|
|
3830
|
+
const stepNumberInput = (direction) => {
|
|
3831
|
+
const input = state.inputElement;
|
|
3832
|
+
if (!input)
|
|
3833
|
+
return;
|
|
3834
|
+
if (direction === 'up') {
|
|
3835
|
+
input.stepUp();
|
|
3836
|
+
}
|
|
3837
|
+
else {
|
|
3838
|
+
input.stepDown();
|
|
3839
|
+
}
|
|
3840
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3841
|
+
};
|
|
3647
3842
|
return m('.input-field', { className: cn, style }, [
|
|
3648
3843
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3649
|
-
m('input', Object.assign(Object.assign({ class:
|
|
3844
|
+
m('input', Object.assign(Object.assign({ class: inputClass || undefined }, params), { type, tabindex: 0, id,
|
|
3650
3845
|
placeholder, value: controlled ? value : undefined, style: type === 'range' && attrs.vertical
|
|
3651
3846
|
? {
|
|
3652
3847
|
height: attrs.height || '200px',
|
|
@@ -3728,7 +3923,7 @@
|
|
|
3728
3923
|
const target = e.target;
|
|
3729
3924
|
state.hasInteracted = true;
|
|
3730
3925
|
// Skip validation for readonly/disabled inputs
|
|
3731
|
-
if (attrs
|
|
3926
|
+
if (isReadonly(attrs) || attrs.disabled) {
|
|
3732
3927
|
// Call original onblur if provided
|
|
3733
3928
|
if (attrs.onblur) {
|
|
3734
3929
|
attrs.onblur(e);
|
|
@@ -3818,6 +4013,22 @@
|
|
|
3818
4013
|
onchange(getValue(state.inputElement));
|
|
3819
4014
|
}
|
|
3820
4015
|
} })),
|
|
4016
|
+
type === 'number' && !isNonInteractive
|
|
4017
|
+
? m('.number-input-controls', [
|
|
4018
|
+
m('button.number-input-control.number-input-control-up[type=button]', {
|
|
4019
|
+
'aria-label': `Increase ${label || 'value'}`,
|
|
4020
|
+
title: 'Increase value',
|
|
4021
|
+
onmousedown: (event) => event.preventDefault(),
|
|
4022
|
+
onclick: () => stepNumberInput('up'),
|
|
4023
|
+
}, m(MaterialIcon, { name: 'chevron', direction: 'up' })),
|
|
4024
|
+
m('button.number-input-control.number-input-control-down[type=button]', {
|
|
4025
|
+
'aria-label': `Decrease ${label || 'value'}`,
|
|
4026
|
+
title: 'Decrease value',
|
|
4027
|
+
onmousedown: (event) => event.preventDefault(),
|
|
4028
|
+
onclick: () => stepNumberInput('down'),
|
|
4029
|
+
}, m(MaterialIcon, { name: 'chevron' })),
|
|
4030
|
+
])
|
|
4031
|
+
: undefined,
|
|
3821
4032
|
// Clear button - only for text inputs with canClear enabled and has content
|
|
3822
4033
|
canClear && type === 'text' && ((_f = state.inputElement) === null || _f === void 0 ? void 0 : _f.value)
|
|
3823
4034
|
? m(MaterialIcon, {
|
|
@@ -3830,14 +4041,12 @@
|
|
|
3830
4041
|
},
|
|
3831
4042
|
})
|
|
3832
4043
|
: undefined,
|
|
3833
|
-
|
|
4044
|
+
...renderFieldChrome({
|
|
3834
4045
|
label,
|
|
3835
4046
|
id,
|
|
3836
4047
|
isMandatory,
|
|
3837
4048
|
isActive,
|
|
3838
4049
|
initialValue: value !== undefined && value !== '',
|
|
3839
|
-
}),
|
|
3840
|
-
m(HelperText, {
|
|
3841
4050
|
helperText,
|
|
3842
4051
|
dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
|
|
3843
4052
|
dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
|
|
@@ -3937,7 +4146,7 @@
|
|
|
3937
4146
|
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3938
4147
|
if (!checkboxId)
|
|
3939
4148
|
checkboxId = inputId || uniqueId();
|
|
3940
|
-
return m('div', { className, style }, m('label', { for: checkboxId }, [
|
|
4149
|
+
return m('div', { className, style }, m('label', { for: checkboxId, style: { position: 'relative', display: 'inline-block' } }, [
|
|
3941
4150
|
m('input[type=checkbox][tabindex=0]', {
|
|
3942
4151
|
className: disabled ? 'disabled' : undefined,
|
|
3943
4152
|
id: checkboxId,
|
|
@@ -4702,17 +4911,6 @@
|
|
|
4702
4911
|
const updatePortalDropdown = (items, selectedLabel, onSelectItem, maxHeight) => {
|
|
4703
4912
|
if (!state.isInsideModal)
|
|
4704
4913
|
return;
|
|
4705
|
-
// Clean up existing portal
|
|
4706
|
-
const existingPortal = document.getElementById(`${state.id}-dropdown`);
|
|
4707
|
-
if (existingPortal) {
|
|
4708
|
-
existingPortal.remove();
|
|
4709
|
-
}
|
|
4710
|
-
if (!state.isOpen || !state.inputRef)
|
|
4711
|
-
return;
|
|
4712
|
-
// Create portal element
|
|
4713
|
-
const portalElement = document.createElement('div');
|
|
4714
|
-
portalElement.id = `${state.id}-dropdown`;
|
|
4715
|
-
document.body.appendChild(portalElement);
|
|
4716
4914
|
// Create dropdown content
|
|
4717
4915
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
4718
4916
|
const dropdownContent = items.map((item) => {
|
|
@@ -4726,15 +4924,14 @@
|
|
|
4726
4924
|
class: `${isSelected ? 'selected' : ''} ${isFocused ? 'focused' : ''}${item.disabled ? ' disabled' : ''}`,
|
|
4727
4925
|
onclick: item.disabled ? undefined : () => onSelectItem(item),
|
|
4728
4926
|
}, m('span', {
|
|
4927
|
+
class: 'mm-layout-row mm-layout-row--center',
|
|
4729
4928
|
style: {
|
|
4730
|
-
display: 'flex',
|
|
4731
|
-
alignItems: 'center',
|
|
4732
4929
|
padding: '14px 16px',
|
|
4733
4930
|
},
|
|
4734
4931
|
}, [
|
|
4735
4932
|
item.iconName
|
|
4736
4933
|
? m('i.material-icons', {
|
|
4737
|
-
|
|
4934
|
+
class: 'mm-layout-item-icon',
|
|
4738
4935
|
}, item.iconName)
|
|
4739
4936
|
: undefined,
|
|
4740
4937
|
item.label,
|
|
@@ -4751,8 +4948,12 @@
|
|
|
4751
4948
|
state.dropdownRef = null;
|
|
4752
4949
|
},
|
|
4753
4950
|
}, dropdownContent);
|
|
4754
|
-
|
|
4755
|
-
|
|
4951
|
+
syncPortalContent({
|
|
4952
|
+
containerId: `${state.id}-dropdown`,
|
|
4953
|
+
shouldRender: state.isOpen && !!state.inputRef,
|
|
4954
|
+
vnode: dropdownVnode,
|
|
4955
|
+
zIndex: 10000,
|
|
4956
|
+
});
|
|
4756
4957
|
};
|
|
4757
4958
|
return {
|
|
4758
4959
|
oninit: ({ attrs }) => {
|
|
@@ -4773,15 +4974,19 @@
|
|
|
4773
4974
|
// Cleanup global listener
|
|
4774
4975
|
document.removeEventListener('click', closeDropdown);
|
|
4775
4976
|
// Cleanup portal
|
|
4776
|
-
|
|
4777
|
-
if (portalElement) {
|
|
4778
|
-
portalElement.remove();
|
|
4779
|
-
}
|
|
4977
|
+
syncPortalContent({ containerId: `${state.id}-dropdown`, shouldRender: false, vnode: null });
|
|
4780
4978
|
},
|
|
4781
4979
|
view: ({ attrs }) => {
|
|
4782
4980
|
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
4783
4981
|
const controlled = isControlled(attrs);
|
|
4784
|
-
const currentCheckedId =
|
|
4982
|
+
const currentCheckedId = resolveControllableValue({
|
|
4983
|
+
controlled,
|
|
4984
|
+
disabled,
|
|
4985
|
+
controlledValue: checkedId,
|
|
4986
|
+
defaultValue: attrs.defaultCheckedId,
|
|
4987
|
+
internalValue: state.internalCheckedId,
|
|
4988
|
+
fallbackValue: undefined,
|
|
4989
|
+
});
|
|
4785
4990
|
const handleSelection = (value) => {
|
|
4786
4991
|
// Update internal state for uncontrolled mode
|
|
4787
4992
|
if (!controlled) {
|
|
@@ -4813,6 +5018,7 @@
|
|
|
4813
5018
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
4814
5019
|
m(HelperText, { helperText }),
|
|
4815
5020
|
m('.select-wrapper', {
|
|
5021
|
+
class: 'mm-layout-row mm-layout-row--center',
|
|
4816
5022
|
onkeydown: disabled
|
|
4817
5023
|
? undefined
|
|
4818
5024
|
: (e) => {
|
|
@@ -4880,15 +5086,14 @@
|
|
|
4880
5086
|
handleSelection(value);
|
|
4881
5087
|
},
|
|
4882
5088
|
}, m('span', {
|
|
5089
|
+
class: 'mm-layout-row mm-layout-row--center',
|
|
4883
5090
|
style: {
|
|
4884
|
-
display: 'flex',
|
|
4885
|
-
alignItems: 'center',
|
|
4886
5091
|
padding: '14px 16px',
|
|
4887
5092
|
},
|
|
4888
5093
|
}, [
|
|
4889
5094
|
item.iconName
|
|
4890
5095
|
? m('i.material-icons', {
|
|
4891
|
-
|
|
5096
|
+
class: 'mm-layout-item-icon',
|
|
4892
5097
|
}, item.iconName)
|
|
4893
5098
|
: undefined,
|
|
4894
5099
|
item.label,
|
|
@@ -6845,9 +7050,9 @@
|
|
|
6845
7050
|
};
|
|
6846
7051
|
|
|
6847
7052
|
const RadioButton = () => ({
|
|
6848
|
-
view: ({ attrs: { id, groupId, label, onchange, className = 'col s12', checked, disabled, inputId } }) => {
|
|
7053
|
+
view: ({ attrs: { id, groupId, label, onchange, className = 'col s12', checked, disabled, inputId, allowHtml } }) => {
|
|
6849
7054
|
const radioId = inputId || `${groupId}-${id}`;
|
|
6850
|
-
return m('p', { className }, m('label', { for: radioId }, [
|
|
7055
|
+
return m('p', { className }, m('label', { for: radioId, style: { position: 'relative', display: 'inline-block' } }, [
|
|
6851
7056
|
m('input[type=radio][tabindex=0]', {
|
|
6852
7057
|
id: radioId,
|
|
6853
7058
|
name: groupId,
|
|
@@ -6855,7 +7060,7 @@
|
|
|
6855
7060
|
checked,
|
|
6856
7061
|
onclick: onchange ? () => onchange(id) : undefined,
|
|
6857
7062
|
}),
|
|
6858
|
-
m('span', m.trust(label)),
|
|
7063
|
+
m('span', allowHtml ? m.trust(label) : label),
|
|
6859
7064
|
]));
|
|
6860
7065
|
},
|
|
6861
7066
|
});
|
|
@@ -6882,8 +7087,9 @@
|
|
|
6882
7087
|
}
|
|
6883
7088
|
},
|
|
6884
7089
|
view: ({ attrs }) => {
|
|
6885
|
-
var _a, _b;
|
|
6886
|
-
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout =
|
|
7090
|
+
var _a, _b, _c;
|
|
7091
|
+
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout, direction, allowHtml = false, onchange, } = attrs;
|
|
7092
|
+
const resolvedLayout = (_a = layout !== null && layout !== void 0 ? layout : direction) !== null && _a !== void 0 ? _a : 'vertical';
|
|
6887
7093
|
const { groupId, componentId } = state;
|
|
6888
7094
|
const controlled = isControlled(attrs);
|
|
6889
7095
|
// Get current checked ID from props or internal state
|
|
@@ -6893,11 +7099,11 @@
|
|
|
6893
7099
|
}
|
|
6894
7100
|
else if (disabled) {
|
|
6895
7101
|
// Non-interactive components: prefer defaultCheckedId, fallback to checkedId
|
|
6896
|
-
currentCheckedId = (
|
|
7102
|
+
currentCheckedId = (_b = attrs.defaultCheckedId) !== null && _b !== void 0 ? _b : checkedId;
|
|
6897
7103
|
}
|
|
6898
7104
|
else {
|
|
6899
7105
|
// Interactive uncontrolled: use internal state
|
|
6900
|
-
currentCheckedId = (
|
|
7106
|
+
currentCheckedId = (_c = state.internalCheckedId) !== null && _c !== void 0 ? _c : attrs.defaultCheckedId;
|
|
6901
7107
|
}
|
|
6902
7108
|
const handleChange = (id) => {
|
|
6903
7109
|
// Update internal state for uncontrolled mode
|
|
@@ -6912,16 +7118,16 @@
|
|
|
6912
7118
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
6913
7119
|
const radioItems = options.map((r) => ({
|
|
6914
7120
|
component: (RadioButton),
|
|
6915
|
-
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}
|
|
7121
|
+
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}`, allowHtml }),
|
|
6916
7122
|
key: r.id,
|
|
6917
7123
|
}));
|
|
6918
7124
|
const optionsContent = m(OptionsList, {
|
|
6919
7125
|
options: radioItems,
|
|
6920
|
-
layout,
|
|
7126
|
+
layout: resolvedLayout,
|
|
6921
7127
|
});
|
|
6922
7128
|
return m('div', { id: componentId, className: cn }, [
|
|
6923
7129
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
6924
|
-
description && m('p.helper-text', m.trust(description)),
|
|
7130
|
+
description && m('p.helper-text', allowHtml ? m.trust(description) : description),
|
|
6925
7131
|
m('form', { action: '#' }, optionsContent),
|
|
6926
7132
|
]);
|
|
6927
7133
|
},
|
|
@@ -7058,6 +7264,26 @@
|
|
|
7058
7264
|
opacity: 1,
|
|
7059
7265
|
};
|
|
7060
7266
|
};
|
|
7267
|
+
const formatSelectionSummary = (attrs, selectedOptions, selectedIds, placeholder) => {
|
|
7268
|
+
const mode = attrs.summaryMode || 'labels';
|
|
7269
|
+
const selectedCount = selectedIds.length;
|
|
7270
|
+
const totalCount = attrs.options.filter((option) => !option.disabled).length;
|
|
7271
|
+
if (mode === 'labels') {
|
|
7272
|
+
return selectedOptions.length > 0 ? selectedOptions.map((o) => o.label || o.id).join(', ') : placeholder;
|
|
7273
|
+
}
|
|
7274
|
+
const defaultCountLabel = `${selectedCount}/${totalCount} selected`;
|
|
7275
|
+
const formattedCount = attrs.countLabel ? attrs.countLabel(selectedCount, totalCount) : defaultCountLabel;
|
|
7276
|
+
if (mode === 'count') {
|
|
7277
|
+
return selectedCount > 0 ? formattedCount : placeholder;
|
|
7278
|
+
}
|
|
7279
|
+
if (selectedCount === 0) {
|
|
7280
|
+
return attrs.noneSelectedLabel || placeholder;
|
|
7281
|
+
}
|
|
7282
|
+
if (totalCount > 0 && selectedCount >= totalCount) {
|
|
7283
|
+
return attrs.allSelectedLabel || 'All selected';
|
|
7284
|
+
}
|
|
7285
|
+
return formattedCount;
|
|
7286
|
+
};
|
|
7061
7287
|
const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
|
|
7062
7288
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
7063
7289
|
// Render ungrouped options first
|
|
@@ -7126,17 +7352,6 @@
|
|
|
7126
7352
|
const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
|
|
7127
7353
|
if (!state.isInsideModal)
|
|
7128
7354
|
return;
|
|
7129
|
-
// Clean up existing portal
|
|
7130
|
-
const existingPortal = document.getElementById(state.dropdownId);
|
|
7131
|
-
if (existingPortal) {
|
|
7132
|
-
existingPortal.remove();
|
|
7133
|
-
}
|
|
7134
|
-
if (!state.isOpen || !state.inputRef)
|
|
7135
|
-
return;
|
|
7136
|
-
// Create portal element
|
|
7137
|
-
const portalElement = document.createElement('div');
|
|
7138
|
-
portalElement.id = state.dropdownId;
|
|
7139
|
-
document.body.appendChild(portalElement);
|
|
7140
7355
|
// Create dropdown with proper positioning
|
|
7141
7356
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
7142
7357
|
tabindex: 0,
|
|
@@ -7148,8 +7363,12 @@
|
|
|
7148
7363
|
state.dropdownRef = null;
|
|
7149
7364
|
},
|
|
7150
7365
|
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
|
|
7151
|
-
|
|
7152
|
-
|
|
7366
|
+
syncPortalContent({
|
|
7367
|
+
containerId: state.dropdownId,
|
|
7368
|
+
shouldRender: state.isOpen && !!state.inputRef,
|
|
7369
|
+
vnode: dropdownVnode,
|
|
7370
|
+
zIndex: 10000,
|
|
7371
|
+
});
|
|
7153
7372
|
};
|
|
7154
7373
|
return {
|
|
7155
7374
|
oninit: ({ attrs }) => {
|
|
@@ -7182,35 +7401,29 @@
|
|
|
7182
7401
|
document.removeEventListener('click', closeDropdown);
|
|
7183
7402
|
// Cleanup portaled dropdown if it exists
|
|
7184
7403
|
if (state.isInsideModal && state.dropdownRef) {
|
|
7185
|
-
|
|
7186
|
-
if (portalElement && portalElement.parentNode) {
|
|
7187
|
-
portalElement.parentNode.removeChild(portalElement);
|
|
7188
|
-
}
|
|
7404
|
+
syncPortalContent({ containerId: state.dropdownId, shouldRender: false, vnode: null });
|
|
7189
7405
|
}
|
|
7190
7406
|
},
|
|
7191
7407
|
view: ({ attrs }) => {
|
|
7192
|
-
var _a;
|
|
7193
7408
|
const controlled = isControlled(attrs);
|
|
7194
|
-
const { newRow, className = 'col s12', key, options = [], multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, disabled, } = attrs;
|
|
7409
|
+
const { newRow, className = 'col s12', key, options = [], multiple = false, label, helperText, placeholder = '', isMandatory, iconName, appearance = 'standard', style, disabled, } = attrs;
|
|
7195
7410
|
state.isMultiple = multiple;
|
|
7196
7411
|
// Get selected IDs from props or internal state
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
selectedIds = state.internalSelectedIds;
|
|
7210
|
-
}
|
|
7211
|
-
const finalClassName = newRow ? `${className} clear` : className;
|
|
7412
|
+
const selectedIds = resolveControllableValue({
|
|
7413
|
+
controlled,
|
|
7414
|
+
disabled,
|
|
7415
|
+
controlledValue: normalizeSelection(attrs.checkedId),
|
|
7416
|
+
defaultValue: normalizeSelection(attrs.defaultCheckedId),
|
|
7417
|
+
internalValue: state.internalSelectedIds,
|
|
7418
|
+
fallbackValue: [],
|
|
7419
|
+
});
|
|
7420
|
+
const layoutClassName = newRow ? `${className} clear` : className;
|
|
7421
|
+
const appearanceClassName = appearance === 'outlined' ? 'select-appearance-outlined' : '';
|
|
7422
|
+
const finalClassName = [layoutClassName, appearanceClassName].filter(Boolean).join(' ');
|
|
7423
|
+
const shouldInlineLabel = appearance === 'outlined' && !!label;
|
|
7212
7424
|
const selectedOptionsUnsorted = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
7213
7425
|
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
7426
|
+
const triggerValue = formatSelectionSummary(attrs, selectedOptions, selectedIds, placeholder);
|
|
7214
7427
|
// Update portal dropdown when inside modal
|
|
7215
7428
|
if (state.isInsideModal) {
|
|
7216
7429
|
updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
|
|
@@ -7230,9 +7443,15 @@
|
|
|
7230
7443
|
'aria-controls': state.dropdownId,
|
|
7231
7444
|
role: 'combobox',
|
|
7232
7445
|
}, [
|
|
7446
|
+
shouldInlineLabel &&
|
|
7447
|
+
m('span.select-inline-label', [
|
|
7448
|
+
label,
|
|
7449
|
+
isMandatory && m('span.mandatory', { style: { marginLeft: '2px' } }, '*'),
|
|
7450
|
+
]),
|
|
7233
7451
|
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
7234
7452
|
id: state.id,
|
|
7235
|
-
value:
|
|
7453
|
+
value: triggerValue,
|
|
7454
|
+
'aria-label': label,
|
|
7236
7455
|
oncreate: ({ dom }) => {
|
|
7237
7456
|
state.inputRef = dom;
|
|
7238
7457
|
},
|
|
@@ -7264,14 +7483,12 @@
|
|
|
7264
7483
|
}),
|
|
7265
7484
|
]),
|
|
7266
7485
|
// Label
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
// Helper text
|
|
7274
|
-
helperText && m(HelperText, { helperText }),
|
|
7486
|
+
...renderFieldChrome({
|
|
7487
|
+
label: shouldInlineLabel ? undefined : label,
|
|
7488
|
+
id: state.id,
|
|
7489
|
+
isMandatory,
|
|
7490
|
+
helperText,
|
|
7491
|
+
}),
|
|
7275
7492
|
]);
|
|
7276
7493
|
},
|
|
7277
7494
|
};
|
|
@@ -7490,59 +7707,65 @@
|
|
|
7490
7707
|
};
|
|
7491
7708
|
};
|
|
7492
7709
|
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7710
|
+
const SelectedChip = ({ option, onRemove, }) => m('.chip', [
|
|
7711
|
+
option.label || option.id.toString(),
|
|
7712
|
+
m(MaterialIcon, {
|
|
7713
|
+
name: 'close',
|
|
7714
|
+
className: 'close',
|
|
7715
|
+
onclick: (e) => {
|
|
7716
|
+
e.stopPropagation();
|
|
7717
|
+
onRemove(option.id);
|
|
7718
|
+
},
|
|
7719
|
+
}),
|
|
7720
|
+
]);
|
|
7721
|
+
const DropdownOption = ({ option, index, optionId, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox, }) => {
|
|
7722
|
+
const optionLabel = option.label || option.id.toString();
|
|
7723
|
+
return m('li', {
|
|
7724
|
+
id: optionId,
|
|
7725
|
+
role: 'option',
|
|
7726
|
+
'aria-selected': selectedIds.includes(option.id) ? 'true' : 'false',
|
|
7727
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
7728
|
+
onmouseover: () => {
|
|
7729
|
+
if (!option.disabled) {
|
|
7730
|
+
onMouseOver(index);
|
|
7731
|
+
}
|
|
7732
|
+
},
|
|
7733
|
+
}, m('label', {
|
|
7734
|
+
class: 'search-select-option-label',
|
|
7735
|
+
onclick: (e) => {
|
|
7736
|
+
// A single-select row has no native checkbox to emit change.
|
|
7737
|
+
if (!showCheckbox) {
|
|
7738
|
+
e.preventDefault();
|
|
7739
|
+
onToggle(option);
|
|
7740
|
+
}
|
|
7741
|
+
},
|
|
7742
|
+
}, [
|
|
7743
|
+
showCheckbox &&
|
|
7744
|
+
m('input', {
|
|
7745
|
+
type: 'checkbox',
|
|
7746
|
+
checked: selectedIds.includes(option.id),
|
|
7747
|
+
disabled: option.disabled,
|
|
7748
|
+
onchange: (e) => {
|
|
7518
7749
|
e.stopPropagation();
|
|
7519
7750
|
onToggle(option);
|
|
7520
7751
|
},
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
onMouseOver(index);
|
|
7525
|
-
}
|
|
7526
|
-
},
|
|
7527
|
-
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
7528
|
-
showCheckbox &&
|
|
7529
|
-
m('input', {
|
|
7530
|
-
type: 'checkbox',
|
|
7531
|
-
id: checkboxId,
|
|
7532
|
-
checked: selectedIds.includes(option.id),
|
|
7533
|
-
}),
|
|
7534
|
-
m('span', optionLabel),
|
|
7535
|
-
]));
|
|
7536
|
-
},
|
|
7537
|
-
};
|
|
7752
|
+
}),
|
|
7753
|
+
m('span', optionLabel),
|
|
7754
|
+
]));
|
|
7538
7755
|
};
|
|
7539
7756
|
/**
|
|
7540
7757
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
7541
7758
|
*/
|
|
7542
|
-
const SearchSelect = () => {
|
|
7759
|
+
const SearchSelect = (maybeVnode) => {
|
|
7760
|
+
var _a;
|
|
7761
|
+
const cachedInstance = (_a = maybeVnode === null || maybeVnode === void 0 ? void 0 : maybeVnode.state) === null || _a === void 0 ? void 0 : _a.__searchSelectInstance;
|
|
7762
|
+
if (cachedInstance) {
|
|
7763
|
+
return cachedInstance;
|
|
7764
|
+
}
|
|
7543
7765
|
// State initialization
|
|
7544
7766
|
const state = {
|
|
7545
7767
|
id: '',
|
|
7768
|
+
listboxId: '',
|
|
7546
7769
|
isOpen: false,
|
|
7547
7770
|
searchTerm: '',
|
|
7548
7771
|
inputRef: null,
|
|
@@ -7550,17 +7773,38 @@
|
|
|
7550
7773
|
focusedIndex: -1,
|
|
7551
7774
|
internalSelectedIds: [],
|
|
7552
7775
|
createdOptions: [],
|
|
7553
|
-
|
|
7776
|
+
asyncOptions: [],
|
|
7777
|
+
isLoading: false,
|
|
7778
|
+
loadError: null,
|
|
7779
|
+
latestRequestId: 0,
|
|
7780
|
+
};
|
|
7781
|
+
const updateAsyncState = (nextState) => {
|
|
7782
|
+
state.asyncOptions = nextState.options;
|
|
7783
|
+
state.isLoading = nextState.isLoading;
|
|
7784
|
+
state.loadError = nextState.error;
|
|
7785
|
+
state.latestRequestId = nextState.latestRequestId;
|
|
7786
|
+
};
|
|
7787
|
+
const readAsyncState = () => ({
|
|
7788
|
+
options: state.asyncOptions,
|
|
7789
|
+
isLoading: state.isLoading,
|
|
7790
|
+
error: state.loadError,
|
|
7791
|
+
latestRequestId: state.latestRequestId,
|
|
7792
|
+
});
|
|
7554
7793
|
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
7555
7794
|
const componentId = uniqueId();
|
|
7556
7795
|
const searchInputId = `${componentId}-search`;
|
|
7557
7796
|
// Handle click outside
|
|
7558
7797
|
const handleClickOutside = (e) => {
|
|
7559
7798
|
const target = e.target;
|
|
7799
|
+
const targetElement = e.target instanceof Element ? e.target : null;
|
|
7560
7800
|
if (state.dropdownRef && state.dropdownRef.contains(target)) {
|
|
7561
7801
|
// Click inside dropdown, do nothing
|
|
7562
7802
|
return;
|
|
7563
7803
|
}
|
|
7804
|
+
if (targetElement && targetElement.closest('.chips-container')) {
|
|
7805
|
+
// Click on trigger, do nothing
|
|
7806
|
+
return;
|
|
7807
|
+
}
|
|
7564
7808
|
if (state.inputRef && state.inputRef.contains(target)) {
|
|
7565
7809
|
// Click on trigger handled by onclick event
|
|
7566
7810
|
return;
|
|
@@ -7571,40 +7815,51 @@
|
|
|
7571
7815
|
}
|
|
7572
7816
|
m.redraw();
|
|
7573
7817
|
};
|
|
7574
|
-
// Handle keyboard navigation
|
|
7575
|
-
const handleKeyDown = (e,
|
|
7576
|
-
|
|
7818
|
+
// Handle keyboard navigation through shared combobox primitive.
|
|
7819
|
+
const handleKeyDown = (e, optionCount, includeActionRow) => {
|
|
7820
|
+
const result = getComboboxKeyResult({
|
|
7821
|
+
key: e.key,
|
|
7822
|
+
isOpen: state.isOpen,
|
|
7823
|
+
focusedIndex: state.focusedIndex,
|
|
7824
|
+
optionCount,
|
|
7825
|
+
includeActionRow,
|
|
7826
|
+
});
|
|
7827
|
+
if (result.preventDefault) {
|
|
7828
|
+
e.preventDefault();
|
|
7829
|
+
}
|
|
7830
|
+
state.isOpen = result.isOpen;
|
|
7831
|
+
state.focusedIndex = result.focusedIndex;
|
|
7832
|
+
return result.action;
|
|
7833
|
+
};
|
|
7834
|
+
const loadAsyncOptions = async (attrs, query) => {
|
|
7835
|
+
if (!attrs.loadOptions) {
|
|
7577
7836
|
return;
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
if (state.focusedIndex >= 0) {
|
|
7591
|
-
if (showAddNew && state.focusedIndex === filteredOptions.length) {
|
|
7592
|
-
// Handle add new option
|
|
7593
|
-
return 'addNew';
|
|
7594
|
-
}
|
|
7595
|
-
else if (state.focusedIndex < filteredOptions.length) {
|
|
7596
|
-
// This will be handled in the view method where attrs are available
|
|
7597
|
-
return 'selectOption';
|
|
7598
|
-
}
|
|
7599
|
-
}
|
|
7600
|
-
break;
|
|
7601
|
-
case 'Escape':
|
|
7602
|
-
e.preventDefault();
|
|
7603
|
-
state.isOpen = false;
|
|
7837
|
+
}
|
|
7838
|
+
const started = startAsyncComboboxRequest(readAsyncState());
|
|
7839
|
+
updateAsyncState(started.nextState);
|
|
7840
|
+
m.redraw();
|
|
7841
|
+
try {
|
|
7842
|
+
const loadedOptions = await attrs.loadOptions(query);
|
|
7843
|
+
const resolved = resolveAsyncComboboxRequest(readAsyncState(), started.requestId, loadedOptions);
|
|
7844
|
+
updateAsyncState(resolved);
|
|
7845
|
+
if (started.requestId !== state.latestRequestId) {
|
|
7846
|
+
return;
|
|
7847
|
+
}
|
|
7848
|
+
if (state.focusedIndex >= loadedOptions.length) {
|
|
7604
7849
|
state.focusedIndex = -1;
|
|
7605
|
-
|
|
7850
|
+
}
|
|
7851
|
+
}
|
|
7852
|
+
catch (error) {
|
|
7853
|
+
const rejected = rejectAsyncComboboxRequest(readAsyncState(), started.requestId, error instanceof Error ? error.message : 'Unable to load options');
|
|
7854
|
+
updateAsyncState(rejected);
|
|
7855
|
+
if (started.requestId !== state.latestRequestId) {
|
|
7856
|
+
return;
|
|
7857
|
+
}
|
|
7858
|
+
state.focusedIndex = -1;
|
|
7859
|
+
}
|
|
7860
|
+
finally {
|
|
7861
|
+
m.redraw();
|
|
7606
7862
|
}
|
|
7607
|
-
return null;
|
|
7608
7863
|
};
|
|
7609
7864
|
// Create new option and add to state
|
|
7610
7865
|
const createAndSelectOption = async (attrs) => {
|
|
@@ -7685,9 +7940,10 @@
|
|
|
7685
7940
|
attrs.onchange(newIds);
|
|
7686
7941
|
}
|
|
7687
7942
|
};
|
|
7688
|
-
|
|
7943
|
+
const componentInstance = {
|
|
7689
7944
|
oninit: ({ attrs }) => {
|
|
7690
7945
|
state.id = attrs.id || uniqueId();
|
|
7946
|
+
state.listboxId = `${state.id}-listbox`;
|
|
7691
7947
|
// Initialize internal state for uncontrolled mode
|
|
7692
7948
|
if (!isControlled(attrs)) {
|
|
7693
7949
|
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
@@ -7714,20 +7970,26 @@
|
|
|
7714
7970
|
: [attrs.checkedId]
|
|
7715
7971
|
: []
|
|
7716
7972
|
: state.internalSelectedIds;
|
|
7717
|
-
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
|
|
7973
|
+
const { options = [], loadOptions, oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
|
|
7718
7974
|
// Use i18n values if provided, otherwise use defaults
|
|
7719
7975
|
const texts = {
|
|
7720
7976
|
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
7977
|
+
loadingOptions: i18n.loadingOptions || 'Loading options...',
|
|
7978
|
+
loadingError: i18n.loadingError || 'Unable to load options',
|
|
7721
7979
|
addNewPrefix: i18n.addNewPrefix || '+',
|
|
7722
7980
|
showingXofY: i18n.showingXofY || 'Showing {shown} of {total} options',
|
|
7723
7981
|
maxSelectionsReached: i18n.maxSelectionsReached || 'Maximum {max} selections reached',
|
|
7724
7982
|
};
|
|
7725
7983
|
// Check if max selections is reached
|
|
7726
7984
|
const isMaxSelectionsReached = maxSelectedOptions && selectedIds.length >= maxSelectedOptions;
|
|
7727
|
-
//
|
|
7728
|
-
const
|
|
7985
|
+
// In async mode, the active list is sourced remotely.
|
|
7986
|
+
const sourceOptions = loadOptions ? state.asyncOptions : options;
|
|
7987
|
+
// Merge active options with internally created options
|
|
7988
|
+
const allOptions = [...sourceOptions, ...state.createdOptions];
|
|
7989
|
+
// Keep selected label lookups stable across static, async, and created sets.
|
|
7990
|
+
const lookupOptions = [...options, ...state.asyncOptions, ...state.createdOptions];
|
|
7729
7991
|
// Get selected options for display
|
|
7730
|
-
const selectedOptionsUnsorted =
|
|
7992
|
+
const selectedOptionsUnsorted = lookupOptions.filter((opt) => selectedIds.includes(opt.id));
|
|
7731
7993
|
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
7732
7994
|
// Safely filter options
|
|
7733
7995
|
const filteredOptions = allOptions.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
@@ -7740,6 +8002,14 @@
|
|
|
7740
8002
|
const showAddNew = oncreateNewOption &&
|
|
7741
8003
|
state.searchTerm &&
|
|
7742
8004
|
!displayedOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
|
|
8005
|
+
const activeDescendantId = state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < displayedOptions.length
|
|
8006
|
+
? getComboboxOptionId(state.id, state.focusedIndex)
|
|
8007
|
+
: undefined;
|
|
8008
|
+
const viewState = getComboboxViewState({
|
|
8009
|
+
isLoading: state.isLoading,
|
|
8010
|
+
error: state.loadError,
|
|
8011
|
+
optionCount: displayedOptions.length,
|
|
8012
|
+
});
|
|
7743
8013
|
// Render the dropdown
|
|
7744
8014
|
return m('.input-field.multi-select-dropdown', { className }, [
|
|
7745
8015
|
m('.chips.chips-initial.chips-container', {
|
|
@@ -7750,14 +8020,34 @@
|
|
|
7750
8020
|
// console.log('SearchSelect clicked', state.isOpen, e); // Debug log
|
|
7751
8021
|
e.preventDefault();
|
|
7752
8022
|
e.stopPropagation();
|
|
8023
|
+
const wasOpen = state.isOpen;
|
|
7753
8024
|
state.isOpen = !state.isOpen;
|
|
8025
|
+
if (!wasOpen && state.isOpen && loadOptions) {
|
|
8026
|
+
void loadAsyncOptions(attrs, state.searchTerm);
|
|
8027
|
+
}
|
|
7754
8028
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
7755
8029
|
},
|
|
7756
|
-
|
|
8030
|
+
onkeydown: async (e) => {
|
|
8031
|
+
const action = handleKeyDown(e, displayedOptions.length, !!showAddNew);
|
|
8032
|
+
if (action === 'open' && loadOptions) {
|
|
8033
|
+
await loadAsyncOptions(attrs, state.searchTerm);
|
|
8034
|
+
return;
|
|
8035
|
+
}
|
|
8036
|
+
if (action === 'selectAction' && oncreateNewOption) {
|
|
8037
|
+
await createAndSelectOption(attrs);
|
|
8038
|
+
}
|
|
8039
|
+
if (action === 'selectFocused' && state.focusedIndex < displayedOptions.length) {
|
|
8040
|
+
toggleOption(displayedOptions[state.focusedIndex], attrs);
|
|
8041
|
+
}
|
|
8042
|
+
},
|
|
8043
|
+
class: 'chips chips-container mm-layout-row mm-layout-row--wrap mm-layout-row--align-end',
|
|
8044
|
+
role: 'combobox',
|
|
8045
|
+
tabindex: 0,
|
|
8046
|
+
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
8047
|
+
'aria-haspopup': 'listbox',
|
|
8048
|
+
'aria-controls': state.isOpen ? state.listboxId : undefined,
|
|
8049
|
+
'aria-activedescendant': activeDescendantId,
|
|
7757
8050
|
style: {
|
|
7758
|
-
display: 'flex',
|
|
7759
|
-
alignItems: 'end',
|
|
7760
|
-
flexWrap: 'wrap',
|
|
7761
8051
|
cursor: 'pointer',
|
|
7762
8052
|
position: 'relative',
|
|
7763
8053
|
},
|
|
@@ -7770,30 +8060,41 @@
|
|
|
7770
8060
|
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
7771
8061
|
readonly: true,
|
|
7772
8062
|
class: 'sr-only',
|
|
7773
|
-
style: {
|
|
8063
|
+
style: {
|
|
8064
|
+
position: 'absolute',
|
|
8065
|
+
width: '1px',
|
|
8066
|
+
height: '1px',
|
|
8067
|
+
margin: '-1px',
|
|
8068
|
+
padding: 0,
|
|
8069
|
+
border: 0,
|
|
8070
|
+
overflow: 'hidden',
|
|
8071
|
+
clip: 'rect(0 0 0 0)',
|
|
8072
|
+
clipPath: 'inset(50%)',
|
|
8073
|
+
whiteSpace: 'nowrap',
|
|
8074
|
+
},
|
|
7774
8075
|
}),
|
|
7775
8076
|
// Selected Options (chips)
|
|
7776
|
-
...selectedOptions.map((option) =>
|
|
7777
|
-
option,
|
|
8077
|
+
...selectedOptions.map((option) => SelectedChip({
|
|
8078
|
+
option: option,
|
|
7778
8079
|
onRemove: (id) => removeOption(id, attrs),
|
|
7779
8080
|
})),
|
|
7780
8081
|
// Placeholder when no options selected
|
|
7781
8082
|
selectedOptions.length === 0 &&
|
|
7782
8083
|
placeholder &&
|
|
7783
8084
|
m('span.placeholder', {
|
|
8085
|
+
class: 'mm-layout-grow',
|
|
7784
8086
|
style: {
|
|
7785
8087
|
color: 'var(--mm-text-hint, #9e9e9e)',
|
|
7786
|
-
flexGrow: 1,
|
|
7787
8088
|
padding: '8px 0',
|
|
7788
8089
|
},
|
|
7789
8090
|
}, placeholder),
|
|
7790
8091
|
// Spacer to push caret to the right
|
|
7791
|
-
m('span.spacer'
|
|
8092
|
+
m('span.spacer.mm-layout-grow'),
|
|
7792
8093
|
m(MaterialIcon, {
|
|
7793
8094
|
name: 'caret',
|
|
7794
8095
|
direction: state.isOpen ? 'up' : 'down',
|
|
7795
|
-
class: 'caret',
|
|
7796
|
-
style: {
|
|
8096
|
+
class: 'caret mm-layout-ml-auto',
|
|
8097
|
+
style: { cursor: 'pointer' },
|
|
7797
8098
|
}),
|
|
7798
8099
|
]),
|
|
7799
8100
|
// Label
|
|
@@ -7805,6 +8106,8 @@
|
|
|
7805
8106
|
// Dropdown Menu
|
|
7806
8107
|
state.isOpen &&
|
|
7807
8108
|
m('ul.dropdown-content.select-dropdown', {
|
|
8109
|
+
id: state.listboxId,
|
|
8110
|
+
role: 'listbox',
|
|
7808
8111
|
oncreate: ({ dom }) => {
|
|
7809
8112
|
state.dropdownRef = dom;
|
|
7810
8113
|
},
|
|
@@ -7829,23 +8132,39 @@
|
|
|
7829
8132
|
oninput: (e) => {
|
|
7830
8133
|
state.searchTerm = e.target.value;
|
|
7831
8134
|
state.focusedIndex = -1; // Reset focus when typing
|
|
8135
|
+
if (loadOptions) {
|
|
8136
|
+
void loadAsyncOptions(attrs, state.searchTerm);
|
|
8137
|
+
}
|
|
7832
8138
|
},
|
|
7833
8139
|
onkeydown: async (e) => {
|
|
7834
|
-
const
|
|
7835
|
-
if (
|
|
8140
|
+
const action = handleKeyDown(e, displayedOptions.length, !!showAddNew);
|
|
8141
|
+
if (action === 'open' && loadOptions) {
|
|
8142
|
+
await loadAsyncOptions(attrs, state.searchTerm);
|
|
8143
|
+
}
|
|
8144
|
+
else if (action === 'selectAction' && oncreateNewOption) {
|
|
7836
8145
|
await createAndSelectOption(attrs);
|
|
7837
8146
|
}
|
|
7838
|
-
else if (
|
|
8147
|
+
else if (action === 'selectFocused' && state.focusedIndex < displayedOptions.length) {
|
|
7839
8148
|
toggleOption(displayedOptions[state.focusedIndex], attrs);
|
|
7840
8149
|
}
|
|
7841
8150
|
},
|
|
7842
8151
|
class: 'search-select-input',
|
|
8152
|
+
'aria-autocomplete': 'list',
|
|
8153
|
+
'aria-controls': state.listboxId,
|
|
7843
8154
|
}),
|
|
7844
8155
|
]),
|
|
7845
|
-
//
|
|
7846
|
-
...(
|
|
7847
|
-
? [m('li.search-select-
|
|
8156
|
+
// Async loading status
|
|
8157
|
+
...(viewState === 'loading'
|
|
8158
|
+
? [m('li.search-select-loading-info', { role: 'status', 'aria-live': 'polite' }, texts.loadingOptions)]
|
|
8159
|
+
: []),
|
|
8160
|
+
// Async loading error
|
|
8161
|
+
...(viewState === 'error' && state.loadError
|
|
8162
|
+
? [
|
|
8163
|
+
m('li.search-select-error-info', { role: 'status', 'aria-live': 'assertive' }, `${texts.loadingError}: ${state.loadError}`),
|
|
8164
|
+
]
|
|
7848
8165
|
: []),
|
|
8166
|
+
// No options found message or list of options
|
|
8167
|
+
...(viewState === 'empty' && !showAddNew ? [m('li.search-select-no-options', texts.noOptionsFound)] : []),
|
|
7849
8168
|
// Truncation message
|
|
7850
8169
|
...(isTruncated
|
|
7851
8170
|
? [
|
|
@@ -7878,6 +8197,9 @@
|
|
|
7878
8197
|
...(showAddNew
|
|
7879
8198
|
? [
|
|
7880
8199
|
m('li', {
|
|
8200
|
+
id: `${state.listboxId}-action`,
|
|
8201
|
+
role: 'option',
|
|
8202
|
+
'aria-selected': 'false',
|
|
7881
8203
|
onclick: async () => {
|
|
7882
8204
|
await createAndSelectOption(attrs);
|
|
7883
8205
|
},
|
|
@@ -7889,9 +8211,10 @@
|
|
|
7889
8211
|
]
|
|
7890
8212
|
: []),
|
|
7891
8213
|
// List of filtered options
|
|
7892
|
-
...displayedOptions.map((option, index) =>
|
|
8214
|
+
...displayedOptions.map((option, index) => DropdownOption({
|
|
7893
8215
|
option,
|
|
7894
8216
|
index,
|
|
8217
|
+
optionId: getComboboxOptionId(state.id, index),
|
|
7895
8218
|
selectedIds,
|
|
7896
8219
|
isFocused: state.focusedIndex === index,
|
|
7897
8220
|
onToggle: (opt) => toggleOption(opt, attrs),
|
|
@@ -7904,6 +8227,10 @@
|
|
|
7904
8227
|
]);
|
|
7905
8228
|
},
|
|
7906
8229
|
};
|
|
8230
|
+
if (maybeVnode === null || maybeVnode === void 0 ? void 0 : maybeVnode.state) {
|
|
8231
|
+
maybeVnode.state.__searchSelectInstance = componentInstance;
|
|
8232
|
+
}
|
|
8233
|
+
return componentInstance;
|
|
7907
8234
|
};
|
|
7908
8235
|
|
|
7909
8236
|
const defaultI18n$2 = {
|
|
@@ -9277,6 +9604,53 @@
|
|
|
9277
9604
|
};
|
|
9278
9605
|
};
|
|
9279
9606
|
|
|
9607
|
+
/** A semantic native fieldset for related controls. */
|
|
9608
|
+
const Fieldset = () => ({
|
|
9609
|
+
view: ({ attrs, children }) => {
|
|
9610
|
+
const { legend, description, required, disabled, error, className } = attrs, params = __rest(attrs, ["legend", "description", "required", "disabled", "error", "className"]);
|
|
9611
|
+
const descriptionId = params.id ? `${params.id}-description` : undefined;
|
|
9612
|
+
const errorId = params.id ? `${params.id}-error` : undefined;
|
|
9613
|
+
const describedBy = [description && descriptionId, error && errorId].filter(Boolean).join(' ') || undefined;
|
|
9614
|
+
return m('fieldset.mm-fieldset', Object.assign(Object.assign({}, params), { className, disabled, 'aria-describedby': describedBy }), [
|
|
9615
|
+
m('legend.mm-fieldset__legend', [legend, required && m('span.mm-fieldset__required[aria-hidden=true]', ' *')]),
|
|
9616
|
+
description && m('p.mm-fieldset__description', { id: descriptionId }, description),
|
|
9617
|
+
children,
|
|
9618
|
+
error && m('p.mm-fieldset__error[role=alert]', { id: errorId }, error),
|
|
9619
|
+
]);
|
|
9620
|
+
},
|
|
9621
|
+
});
|
|
9622
|
+
const focusErrorTarget = (fieldId) => {
|
|
9623
|
+
var _a;
|
|
9624
|
+
if (!fieldId || typeof document === 'undefined')
|
|
9625
|
+
return;
|
|
9626
|
+
(_a = document.getElementById(fieldId)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
9627
|
+
};
|
|
9628
|
+
/** A visual form section with an optional accessible validation summary. */
|
|
9629
|
+
const FormSection = () => ({
|
|
9630
|
+
view: ({ attrs, children }) => {
|
|
9631
|
+
const { title, description, errors = [], summaryTitle = 'Please correct the following errors', className } = attrs, params = __rest(attrs, ["title", "description", "errors", "summaryTitle", "className"]);
|
|
9632
|
+
return m('section.mm-form-section', Object.assign(Object.assign({}, params), { className }), [
|
|
9633
|
+
(title || description) &&
|
|
9634
|
+
m('.mm-form-section__header', [
|
|
9635
|
+
title && m('h3.mm-form-section__title', title),
|
|
9636
|
+
description && m('p.mm-form-section__description', description),
|
|
9637
|
+
]),
|
|
9638
|
+
errors.length > 0 &&
|
|
9639
|
+
m('div.mm-validation-summary[role=alert][aria-live=assertive]', [
|
|
9640
|
+
m('p.mm-validation-summary__title', summaryTitle),
|
|
9641
|
+
m('ul.mm-validation-summary__list', errors.map((error, index) => {
|
|
9642
|
+
var _a, _b, _c;
|
|
9643
|
+
const href = (_a = error.href) !== null && _a !== void 0 ? _a : (error.fieldId ? `#${error.fieldId}` : undefined);
|
|
9644
|
+
return m('li', { key: `${(_c = (_b = error.fieldId) !== null && _b !== void 0 ? _b : href) !== null && _c !== void 0 ? _c : 'error'}-${index}` }, href
|
|
9645
|
+
? m('a', { href, onclick: () => focusErrorTarget(error.fieldId) }, error.message)
|
|
9646
|
+
: error.message);
|
|
9647
|
+
})),
|
|
9648
|
+
]),
|
|
9649
|
+
children,
|
|
9650
|
+
]);
|
|
9651
|
+
},
|
|
9652
|
+
});
|
|
9653
|
+
|
|
9280
9654
|
// List of MaterialIcon SVG icons that are available
|
|
9281
9655
|
const materialIconSvgNames = [
|
|
9282
9656
|
'caret',
|
|
@@ -9299,6 +9673,7 @@
|
|
|
9299
9673
|
const renderIcon = (icon, style) => {
|
|
9300
9674
|
if (!icon)
|
|
9301
9675
|
return null;
|
|
9676
|
+
const objectStyle = typeof style === 'string' ? undefined : style;
|
|
9302
9677
|
if (typeof icon === 'string') {
|
|
9303
9678
|
// Check if this is a MaterialIcon SVG name
|
|
9304
9679
|
if (materialIconSvgNames.includes(icon)) {
|
|
@@ -9315,7 +9690,7 @@
|
|
|
9315
9690
|
// Image URL
|
|
9316
9691
|
return m('img', {
|
|
9317
9692
|
src: icon.content,
|
|
9318
|
-
style: Object.assign(Object.assign({},
|
|
9693
|
+
style: Object.assign(Object.assign({}, objectStyle), { width: '24px', height: '24px', objectFit: 'contain' }),
|
|
9319
9694
|
});
|
|
9320
9695
|
}
|
|
9321
9696
|
return null;
|
|
@@ -9326,8 +9701,10 @@
|
|
|
9326
9701
|
const SidenavHeaderFooterItem = () => {
|
|
9327
9702
|
return {
|
|
9328
9703
|
view: ({ attrs }) => {
|
|
9329
|
-
const { text, icon, onclick, href, className = '', _isExpanded = true, _position = 'left' } = attrs;
|
|
9704
|
+
const { text, icon, onclick, href, className = '', title, tooltip, tooltipWhenCollapsedOnly = true, _isExpanded = true, _position = 'left', } = attrs;
|
|
9330
9705
|
const isRightAligned = _position === 'right';
|
|
9706
|
+
const tooltipText = title || tooltip || text;
|
|
9707
|
+
const shouldShowTooltip = tooltipWhenCollapsedOnly ? !_isExpanded : true;
|
|
9331
9708
|
const handleClick = (e) => {
|
|
9332
9709
|
if (onclick) {
|
|
9333
9710
|
e.preventDefault();
|
|
@@ -9336,24 +9713,25 @@
|
|
|
9336
9713
|
};
|
|
9337
9714
|
const content = isRightAligned
|
|
9338
9715
|
? [
|
|
9339
|
-
_isExpanded &&
|
|
9340
|
-
m('span.sidenav-item-text', { style: { flex: '1', 'text-align': 'left', 'margin-right': '8px' } }, text),
|
|
9716
|
+
_isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-mr-8.left-align', text),
|
|
9341
9717
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9342
9718
|
]
|
|
9343
9719
|
: [
|
|
9344
9720
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9345
|
-
_isExpanded && m('span.sidenav-item-text
|
|
9721
|
+
_isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-ml-8', text),
|
|
9346
9722
|
];
|
|
9347
9723
|
const linkStyle = {
|
|
9348
|
-
display: 'flex',
|
|
9349
|
-
'align-items': 'center',
|
|
9350
9724
|
padding: _isExpanded ? '12px 16px' : '12px 18px',
|
|
9351
9725
|
'justify-content': _isExpanded ? (isRightAligned ? 'flex-end' : 'flex-start') : 'center',
|
|
9352
9726
|
};
|
|
9727
|
+
const linkClass = 'mm-layout-row mm-layout-row--center';
|
|
9353
9728
|
return m('li', { class: className }, m('a', {
|
|
9354
9729
|
href: href || '#!',
|
|
9355
9730
|
onclick: handleClick,
|
|
9731
|
+
class: linkClass,
|
|
9356
9732
|
style: linkStyle,
|
|
9733
|
+
title: shouldShowTooltip ? tooltipText : undefined,
|
|
9734
|
+
'aria-label': shouldShowTooltip ? tooltipText : undefined,
|
|
9357
9735
|
}, content));
|
|
9358
9736
|
},
|
|
9359
9737
|
};
|
|
@@ -9375,9 +9753,9 @@
|
|
|
9375
9753
|
m.redraw();
|
|
9376
9754
|
}
|
|
9377
9755
|
};
|
|
9378
|
-
const setBodyOverflow = (isOpen, mode) => {
|
|
9756
|
+
const setBodyOverflow = (isOpen, mode, fixed) => {
|
|
9379
9757
|
if (typeof document !== 'undefined') {
|
|
9380
|
-
document.body.style.overflow = isOpen && mode === 'overlay' ? 'hidden' : '';
|
|
9758
|
+
document.body.style.overflow = isOpen && mode === 'overlay' && !fixed ? 'hidden' : '';
|
|
9381
9759
|
}
|
|
9382
9760
|
};
|
|
9383
9761
|
const toggleExpanded = (attrs) => {
|
|
@@ -9413,7 +9791,7 @@
|
|
|
9413
9791
|
if (wasOpen !== isOpen) {
|
|
9414
9792
|
state.isOpen = isOpen;
|
|
9415
9793
|
state.isAnimating = true;
|
|
9416
|
-
setBodyOverflow(isOpen, attrs.mode || 'overlay');
|
|
9794
|
+
setBodyOverflow(isOpen, attrs.mode || 'overlay', attrs.fixed || false);
|
|
9417
9795
|
// Clear animation state after animation completes
|
|
9418
9796
|
setTimeout(() => {
|
|
9419
9797
|
state.isAnimating = false;
|
|
@@ -9423,7 +9801,7 @@
|
|
|
9423
9801
|
},
|
|
9424
9802
|
onremove: ({ attrs }) => {
|
|
9425
9803
|
// Clean up
|
|
9426
|
-
setBodyOverflow(false, attrs.mode || 'overlay');
|
|
9804
|
+
setBodyOverflow(false, attrs.mode || 'overlay', attrs.fixed || false);
|
|
9427
9805
|
if (typeof document !== 'undefined' && attrs.closeOnEscape !== false) {
|
|
9428
9806
|
document.removeEventListener('keydown', (e) => handleEscapeKey(e, attrs));
|
|
9429
9807
|
}
|
|
@@ -9452,7 +9830,9 @@
|
|
|
9452
9830
|
class: [
|
|
9453
9831
|
position === 'right' ? 'right-aligned' : '',
|
|
9454
9832
|
fixed ? 'sidenav-fixed' : '',
|
|
9833
|
+
mode === 'push' ? 'sidenav-push' : '',
|
|
9455
9834
|
expandable && !isExpanded ? 'sidenav-collapsed' : '',
|
|
9835
|
+
'mm-layout-stack',
|
|
9456
9836
|
className,
|
|
9457
9837
|
]
|
|
9458
9838
|
.filter(Boolean)
|
|
@@ -9464,13 +9844,22 @@
|
|
|
9464
9844
|
'transition-property': 'transform, width',
|
|
9465
9845
|
},
|
|
9466
9846
|
}, [
|
|
9847
|
+
// Header content slot (rendered first, before hamburger)
|
|
9848
|
+
attrs.headerContent &&
|
|
9849
|
+
m('li.sidenav-header-slot', {
|
|
9850
|
+
style: {
|
|
9851
|
+
'flex-shrink': 0,
|
|
9852
|
+
'list-style': 'none',
|
|
9853
|
+
height: 'auto',
|
|
9854
|
+
'line-height': 'normal',
|
|
9855
|
+
padding: 0,
|
|
9856
|
+
},
|
|
9857
|
+
}, attrs.headerContent),
|
|
9467
9858
|
// Hamburger toggle button (inside sidenav, at the top)
|
|
9468
9859
|
showHamburger &&
|
|
9469
9860
|
m('li.sidenav-hamburger-item', {
|
|
9861
|
+
class: `mm-layout-row mm-layout-row--center ${position === 'right' ? 'mm-layout-row--justify-end' : 'mm-layout-row--justify-start'}`,
|
|
9470
9862
|
style: {
|
|
9471
|
-
display: 'flex',
|
|
9472
|
-
'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
|
|
9473
|
-
'align-items': 'center',
|
|
9474
9863
|
padding: '12px 16px',
|
|
9475
9864
|
cursor: 'pointer',
|
|
9476
9865
|
'border-bottom': '1px solid rgba(0,0,0,0.1)',
|
|
@@ -9486,10 +9875,8 @@
|
|
|
9486
9875
|
// Expand/collapse toggle button (if expandable, right below hamburger)
|
|
9487
9876
|
expandable &&
|
|
9488
9877
|
m('li.sidenav-expand-toggle', {
|
|
9878
|
+
class: `mm-layout-row mm-layout-row--center ${position === 'right' ? 'mm-layout-row--justify-end' : 'mm-layout-row--justify-start'}`,
|
|
9489
9879
|
style: {
|
|
9490
|
-
display: 'flex',
|
|
9491
|
-
'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
|
|
9492
|
-
'align-items': 'center',
|
|
9493
9880
|
padding: '12px 16px',
|
|
9494
9881
|
cursor: 'pointer',
|
|
9495
9882
|
'border-bottom': '1px solid rgba(0,0,0,0.1)',
|
|
@@ -9517,7 +9904,19 @@
|
|
|
9517
9904
|
: children,
|
|
9518
9905
|
// Footer item (if provided, appears at the bottom)
|
|
9519
9906
|
attrs.footer &&
|
|
9520
|
-
m(SidenavHeaderFooterItem, Object.assign(Object.assign({}, attrs.footer), { _isExpanded: isExpanded, _position: position, className: 'sidenav-footer-item' })),
|
|
9907
|
+
m(SidenavHeaderFooterItem, Object.assign(Object.assign({}, attrs.footer), { _isExpanded: isExpanded, _position: position, className: ['sidenav-footer-item', attrs.footer.className].filter(Boolean).join(' ') })),
|
|
9908
|
+
// Footer content slot (rendered last, pushed to bottom via margin-top: auto)
|
|
9909
|
+
attrs.footerContent &&
|
|
9910
|
+
m('li.sidenav-footer-slot', {
|
|
9911
|
+
style: {
|
|
9912
|
+
'margin-top': 'auto',
|
|
9913
|
+
'flex-shrink': 0,
|
|
9914
|
+
'list-style': 'none',
|
|
9915
|
+
height: 'auto',
|
|
9916
|
+
'line-height': 'normal',
|
|
9917
|
+
padding: 0,
|
|
9918
|
+
},
|
|
9919
|
+
}, attrs.footerContent),
|
|
9521
9920
|
]),
|
|
9522
9921
|
];
|
|
9523
9922
|
},
|
|
@@ -9550,7 +9949,7 @@
|
|
|
9550
9949
|
const submenuContent = isRightAligned
|
|
9551
9950
|
? [
|
|
9552
9951
|
// Right-aligned: text on left, icons on right
|
|
9553
|
-
isExpanded && m('span
|
|
9952
|
+
isExpanded && m('span.mm-layout-grow.left-align', text),
|
|
9554
9953
|
icon && isExpanded && renderIcon(icon, { 'font-size': '18px' }),
|
|
9555
9954
|
indicatorIcon,
|
|
9556
9955
|
]
|
|
@@ -9558,18 +9957,22 @@
|
|
|
9558
9957
|
// Left-aligned: indicator on left, text and icon on right
|
|
9559
9958
|
indicatorIcon,
|
|
9560
9959
|
icon && isExpanded && renderIcon(icon, { 'font-size': '18px', 'margin-left': indicatorIcon ? '8px' : '0' }),
|
|
9561
|
-
isExpanded && m('span', {
|
|
9960
|
+
isExpanded && m('span', { class: icon || indicatorIcon ? 'mm-layout-ml-8' : undefined }, text),
|
|
9562
9961
|
];
|
|
9563
9962
|
return m('li.sidenav-subitem', {
|
|
9564
|
-
class:
|
|
9963
|
+
class: [
|
|
9964
|
+
selected ? 'selected' : '',
|
|
9965
|
+
'mm-layout-row',
|
|
9966
|
+
'mm-layout-row--center',
|
|
9967
|
+
'mm-layout-gap-sm',
|
|
9968
|
+
isRightAligned ? 'mm-layout-row--justify-between' : 'mm-layout-row--justify-start',
|
|
9969
|
+
]
|
|
9970
|
+
.filter(Boolean)
|
|
9971
|
+
.join(' ') || undefined,
|
|
9565
9972
|
style: {
|
|
9566
9973
|
padding: isExpanded ? '0 16px 0 48px' : '0 16px',
|
|
9567
9974
|
cursor: 'pointer',
|
|
9568
|
-
display: 'flex',
|
|
9569
|
-
'align-items': 'center',
|
|
9570
|
-
gap: '8px',
|
|
9571
9975
|
'font-size': '0.9em',
|
|
9572
|
-
'justify-content': isRightAligned ? 'space-between' : 'flex-start',
|
|
9573
9976
|
height: '48px',
|
|
9574
9977
|
'min-height': '48px',
|
|
9575
9978
|
},
|
|
@@ -9586,7 +9989,7 @@
|
|
|
9586
9989
|
let isSubmenuOpen = false;
|
|
9587
9990
|
return {
|
|
9588
9991
|
view: ({ attrs, children }) => {
|
|
9589
|
-
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, submenu = [], submenuMode = 'checkbox', } = attrs;
|
|
9992
|
+
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, submenu = [], submenuMode = 'checkbox', title, tooltip, tooltipWhenCollapsedOnly = true, } = attrs;
|
|
9590
9993
|
if (divider) {
|
|
9591
9994
|
return m('li.divider');
|
|
9592
9995
|
}
|
|
@@ -9611,39 +10014,45 @@
|
|
|
9611
10014
|
const isExpanded = attrs._isExpanded !== false;
|
|
9612
10015
|
const position = attrs._position || 'left';
|
|
9613
10016
|
const isRightAligned = position === 'right';
|
|
10017
|
+
const tooltipText = title || tooltip || text;
|
|
10018
|
+
const shouldShowTooltip = tooltipWhenCollapsedOnly ? !isExpanded : true;
|
|
9614
10019
|
// In expanded mode, icons are at the outside edge
|
|
9615
10020
|
// In collapsed mode, icons are centered
|
|
9616
10021
|
const content = isRightAligned
|
|
9617
10022
|
? [
|
|
9618
10023
|
// Right-aligned: text on left, icon on right
|
|
9619
|
-
isExpanded &&
|
|
9620
|
-
m('span.sidenav-item-text', { style: { flex: '1', 'text-align': 'left', 'margin-right': '8px' } }, text || children),
|
|
10024
|
+
isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-mr-8.left-align', text || children),
|
|
9621
10025
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9622
10026
|
]
|
|
9623
10027
|
: [
|
|
9624
10028
|
// Left-aligned: icon on left, text on right
|
|
9625
10029
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9626
|
-
isExpanded && m('span.sidenav-item-text
|
|
10030
|
+
isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-ml-8', text || children),
|
|
9627
10031
|
];
|
|
9628
10032
|
const linkStyle = {
|
|
9629
|
-
display: 'flex',
|
|
9630
|
-
'align-items': 'center',
|
|
9631
10033
|
padding: isExpanded ? '12px 16px' : '12px 18px',
|
|
9632
10034
|
'justify-content': isExpanded ? (isRightAligned ? 'flex-end' : 'flex-start') : 'center',
|
|
9633
10035
|
};
|
|
10036
|
+
const linkClass = 'mm-layout-row mm-layout-row--center';
|
|
9634
10037
|
const mainItem = href && !disabled
|
|
9635
10038
|
? m('li', { class: itemClasses }, [
|
|
9636
10039
|
m('a', {
|
|
9637
10040
|
href,
|
|
9638
10041
|
onclick: handleMainClick,
|
|
10042
|
+
class: linkClass,
|
|
9639
10043
|
style: linkStyle,
|
|
10044
|
+
title: shouldShowTooltip ? tooltipText : undefined,
|
|
10045
|
+
'aria-label': shouldShowTooltip ? tooltipText : undefined,
|
|
9640
10046
|
}, content),
|
|
9641
10047
|
])
|
|
9642
10048
|
: m('li', { class: itemClasses }, [
|
|
9643
10049
|
m('a', {
|
|
9644
10050
|
onclick: handleMainClick,
|
|
9645
10051
|
href: '#!',
|
|
10052
|
+
class: linkClass,
|
|
9646
10053
|
style: linkStyle,
|
|
10054
|
+
title: shouldShowTooltip ? tooltipText : undefined,
|
|
10055
|
+
'aria-label': shouldShowTooltip ? tooltipText : undefined,
|
|
9647
10056
|
}, content),
|
|
9648
10057
|
]);
|
|
9649
10058
|
// Return main item with submenu if applicable
|
|
@@ -10159,8 +10568,9 @@
|
|
|
10159
10568
|
const childIsFocused = ((_e = attrs.treeState) === null || _e === void 0 ? void 0 : _e.focusedNodeId) === child.id;
|
|
10160
10569
|
// Calculate if this child is last in branch
|
|
10161
10570
|
const childPath = [...(attrs.currentPath || []), childIndex];
|
|
10162
|
-
const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data)
|
|
10163
|
-
isNodeLastInBranch(childPath, attrs.treeAttrs.data)
|
|
10571
|
+
const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data)
|
|
10572
|
+
? isNodeLastInBranch(childPath, attrs.treeAttrs.data)
|
|
10573
|
+
: false;
|
|
10164
10574
|
return m(TreeNodeComponent, {
|
|
10165
10575
|
key: child.id,
|
|
10166
10576
|
node: child,
|
|
@@ -10293,10 +10703,7 @@
|
|
|
10293
10703
|
view: ({ attrs }) => {
|
|
10294
10704
|
const { data, className, style, id, selectionMode = 'single', showConnectors = true } = attrs;
|
|
10295
10705
|
return m('div.tree-view', {
|
|
10296
|
-
class: [
|
|
10297
|
-
className,
|
|
10298
|
-
showConnectors && 'show-connectors'
|
|
10299
|
-
].filter(Boolean).join(' ') || undefined,
|
|
10706
|
+
class: [className, showConnectors && 'show-connectors'].filter(Boolean).join(' ') || undefined,
|
|
10300
10707
|
style,
|
|
10301
10708
|
id,
|
|
10302
10709
|
role: selectionMode === 'multiple' ? 'listbox' : 'tree',
|
|
@@ -10994,6 +11401,7 @@
|
|
|
10994
11401
|
};
|
|
10995
11402
|
};
|
|
10996
11403
|
|
|
11404
|
+
const MOBILE_LAYOUT_BREAKPOINT = 600;
|
|
10997
11405
|
/** Create a LikertScale component */
|
|
10998
11406
|
const LikertScale = () => {
|
|
10999
11407
|
const state = {
|
|
@@ -11056,6 +11464,22 @@
|
|
|
11056
11464
|
return 'likert-scale--responsive';
|
|
11057
11465
|
}
|
|
11058
11466
|
};
|
|
11467
|
+
const isVerticalLayout = (layout) => {
|
|
11468
|
+
if (layout === 'vertical')
|
|
11469
|
+
return true;
|
|
11470
|
+
if (layout === 'horizontal')
|
|
11471
|
+
return false;
|
|
11472
|
+
return typeof window !== 'undefined' ? window.innerWidth <= MOBILE_LAYOUT_BREAKPOINT : false;
|
|
11473
|
+
};
|
|
11474
|
+
const getInlineAnchorLabel = (value, min, max, middleValue, startLabel, middleLabel, endLabel) => {
|
|
11475
|
+
if (value === min && startLabel)
|
|
11476
|
+
return startLabel;
|
|
11477
|
+
if (middleLabel && middleValue !== undefined && value === middleValue)
|
|
11478
|
+
return middleLabel;
|
|
11479
|
+
if (value === max && endLabel)
|
|
11480
|
+
return endLabel;
|
|
11481
|
+
return undefined;
|
|
11482
|
+
};
|
|
11059
11483
|
const handleChange = (attrs, newValue) => {
|
|
11060
11484
|
var _a;
|
|
11061
11485
|
if (attrs.readonly || attrs.disabled)
|
|
@@ -11102,7 +11526,7 @@
|
|
|
11102
11526
|
const LikertScaleItem = () => {
|
|
11103
11527
|
return {
|
|
11104
11528
|
view: ({ attrs }) => {
|
|
11105
|
-
const { value, currentValue, showNumber, showTooltip, tooltipLabel, groupId, name, disabled, readonly, onchange, } = attrs;
|
|
11529
|
+
const { value, currentValue, showNumber, showTooltip, tooltipLabel, groupId, name, disabled, readonly, anchorLabel, onchange, } = attrs;
|
|
11106
11530
|
const radioId = `${groupId}-${value}`;
|
|
11107
11531
|
const isChecked = currentValue === value;
|
|
11108
11532
|
return m('.likert-scale__item.no-select', {
|
|
@@ -11129,6 +11553,7 @@
|
|
|
11129
11553
|
m('label.likert-scale__label', {
|
|
11130
11554
|
for: radioId,
|
|
11131
11555
|
}),
|
|
11556
|
+
anchorLabel && m('.likert-scale__item-anchor', anchorLabel),
|
|
11132
11557
|
// Tooltip (optional)
|
|
11133
11558
|
showTooltip && tooltipLabel && m('.likert-scale__tooltip', tooltipLabel),
|
|
11134
11559
|
]);
|
|
@@ -11152,6 +11577,8 @@
|
|
|
11152
11577
|
const { min = 1, max = 5, step = 1, size = 'medium', density = 'standard', layout = 'responsive', className = '', style = {}, readonly = false, disabled = false, id = state.id, name, label, description, isMandatory, startLabel, middleLabel, endLabel, showNumbers = false, showTooltips = false, tooltipLabels, alignLabels = false } = attrs, ariaAttrs = __rest(attrs, ["min", "max", "step", "size", "density", "layout", "className", "style", "readonly", "disabled", "id", "name", "label", "description", "isMandatory", "startLabel", "middleLabel", "endLabel", "showNumbers", "showTooltips", "tooltipLabels", "alignLabels"]);
|
|
11153
11578
|
const currentValue = getCurrentValue(attrs);
|
|
11154
11579
|
const itemCount = Math.floor((max - min) / step) + 1;
|
|
11580
|
+
const useInlineAnchors = isVerticalLayout(layout);
|
|
11581
|
+
const middleValue = middleLabel ? min + Math.floor((itemCount - 1) / 2) * step : undefined;
|
|
11155
11582
|
// Generate scale values
|
|
11156
11583
|
const scaleValues = Array.from({ length: itemCount }, (_, i) => min + i * step);
|
|
11157
11584
|
return m('.likert-scale', {
|
|
@@ -11200,6 +11627,9 @@
|
|
|
11200
11627
|
showNumber: showNumbers,
|
|
11201
11628
|
showTooltip: showTooltips,
|
|
11202
11629
|
tooltipLabel: tooltipLabels === null || tooltipLabels === void 0 ? void 0 : tooltipLabels[value - min],
|
|
11630
|
+
anchorLabel: useInlineAnchors
|
|
11631
|
+
? getInlineAnchorLabel(value, min, max, middleValue, startLabel, middleLabel, endLabel)
|
|
11632
|
+
: undefined,
|
|
11203
11633
|
groupId: state.groupId,
|
|
11204
11634
|
name,
|
|
11205
11635
|
disabled,
|
|
@@ -11207,7 +11637,8 @@
|
|
|
11207
11637
|
onchange: (v) => handleChange(attrs, v),
|
|
11208
11638
|
}))),
|
|
11209
11639
|
// Scale anchors
|
|
11210
|
-
|
|
11640
|
+
!useInlineAnchors &&
|
|
11641
|
+
(startLabel || middleLabel || endLabel) &&
|
|
11211
11642
|
m('.likert-scale__anchors', [
|
|
11212
11643
|
startLabel && m('.likert-scale__anchor.likert-scale__anchor--start', startLabel),
|
|
11213
11644
|
middleLabel && m('.likert-scale__anchor.likert-scale__anchor--middle', middleLabel),
|
|
@@ -11392,6 +11823,9 @@
|
|
|
11392
11823
|
const { mode = 'indeterminate', value = 0, max = 100, size = 'medium', color = 'teal', colorIntensity, label, showPercentage = false, className = '', style = {}, id = state.id, 'aria-label': ariaLabel, 'aria-valuemin': ariaValueMin = 0, 'aria-valuemax': ariaValueMax = max, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = attrs, params = __rest(attrs, ["mode", "value", "max", "size", "color", "colorIntensity", "label", "showPercentage", "className", "style", "id", 'aria-label', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']);
|
|
11393
11824
|
const isDeterminate = mode === 'determinate';
|
|
11394
11825
|
const sizePixels = SIZE_MAP[size];
|
|
11826
|
+
const styleValue = typeof style === 'string'
|
|
11827
|
+
? `width:${sizePixels}px;height:${sizePixels}px;${style}`
|
|
11828
|
+
: Object.assign({ width: `${sizePixels}px`, height: `${sizePixels}px` }, style);
|
|
11395
11829
|
const { radius, circumference, strokeDashoffset, percentage } = isDeterminate
|
|
11396
11830
|
? calculateStrokeProperties(sizePixels, value, max)
|
|
11397
11831
|
: { radius: 0, circumference: 0, strokeDashoffset: 0, percentage: 0 };
|
|
@@ -11418,7 +11852,7 @@
|
|
|
11418
11852
|
: {
|
|
11419
11853
|
'aria-valuetext': ariaValueText || label || 'Loading',
|
|
11420
11854
|
};
|
|
11421
|
-
return m('.circular-progress', Object.assign(Object.assign(Object.assign({}, params), { className: classNames, style:
|
|
11855
|
+
return m('.circular-progress', Object.assign(Object.assign(Object.assign({}, params), { className: classNames, style: styleValue, id, role: 'progressbar', 'aria-label': ariaLabel || (isDeterminate ? `Progress: ${Math.round(percentage)}%` : 'Loading') }), ariaAttrs), [
|
|
11422
11856
|
// SVG circle
|
|
11423
11857
|
m('svg.circular-progress__svg', {
|
|
11424
11858
|
viewBox: `0 0 ${sizePixels} ${sizePixels}`,
|
|
@@ -11483,13 +11917,7 @@
|
|
|
11483
11917
|
// Determine label content
|
|
11484
11918
|
const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
|
|
11485
11919
|
// Build class names
|
|
11486
|
-
const classNames = [
|
|
11487
|
-
'linear-progress',
|
|
11488
|
-
getColorClass(color, colorIntensity),
|
|
11489
|
-
className,
|
|
11490
|
-
]
|
|
11491
|
-
.filter(Boolean)
|
|
11492
|
-
.join(' ');
|
|
11920
|
+
const classNames = ['linear-progress', getColorClass(color, colorIntensity), className].filter(Boolean).join(' ');
|
|
11493
11921
|
// ARIA attributes
|
|
11494
11922
|
const ariaAttrs = isDeterminate
|
|
11495
11923
|
? {
|
|
@@ -11566,10 +11994,12 @@
|
|
|
11566
11994
|
exports.DoubleRangeSlider = DoubleRangeSlider;
|
|
11567
11995
|
exports.Dropdown = Dropdown;
|
|
11568
11996
|
exports.EmailInput = EmailInput;
|
|
11997
|
+
exports.Fieldset = Fieldset;
|
|
11569
11998
|
exports.FileInput = FileInput;
|
|
11570
11999
|
exports.FileUpload = FileUpload;
|
|
11571
12000
|
exports.FlatButton = FlatButton;
|
|
11572
12001
|
exports.FloatingActionButton = FloatingActionButton;
|
|
12002
|
+
exports.FormSection = FormSection;
|
|
11573
12003
|
exports.HelperText = HelperText;
|
|
11574
12004
|
exports.Icon = Icon;
|
|
11575
12005
|
exports.IconButton = IconButton;
|
|
@@ -11621,6 +12051,7 @@
|
|
|
11621
12051
|
exports.Timeline = Timeline;
|
|
11622
12052
|
exports.Toast = Toast;
|
|
11623
12053
|
exports.ToastComponent = ToastComponent;
|
|
12054
|
+
exports.ToggleButton = ToggleButton;
|
|
11624
12055
|
exports.ToggleGroup = ToggleGroup;
|
|
11625
12056
|
exports.Tooltip = Tooltip;
|
|
11626
12057
|
exports.TooltipComponent = TooltipComponent;
|
|
@@ -11629,10 +12060,14 @@
|
|
|
11629
12060
|
exports.Wizard = Wizard;
|
|
11630
12061
|
exports.addLeadingZero = addLeadingZero;
|
|
11631
12062
|
exports.clearPortal = clearPortal;
|
|
12063
|
+
exports.createAsyncComboboxState = createAsyncComboboxState;
|
|
11632
12064
|
exports.createBreadcrumb = createBreadcrumb;
|
|
11633
12065
|
exports.formatTime = formatTime;
|
|
11634
12066
|
exports.generateHourOptions = generateHourOptions;
|
|
11635
12067
|
exports.generateMinuteOptions = generateMinuteOptions;
|
|
12068
|
+
exports.getComboboxKeyResult = getComboboxKeyResult;
|
|
12069
|
+
exports.getComboboxOptionId = getComboboxOptionId;
|
|
12070
|
+
exports.getComboboxViewState = getComboboxViewState;
|
|
11636
12071
|
exports.getDropdownStyles = getDropdownStyles;
|
|
11637
12072
|
exports.getPortalContainer = getPortalContainer;
|
|
11638
12073
|
exports.initPushpins = initPushpins;
|
|
@@ -11641,17 +12076,25 @@
|
|
|
11641
12076
|
exports.isTimeDisabled = isTimeDisabled;
|
|
11642
12077
|
exports.isValidationError = isValidationError;
|
|
11643
12078
|
exports.isValidationSuccess = isValidationSuccess;
|
|
12079
|
+
exports.normalizeSelection = normalizeSelection;
|
|
11644
12080
|
exports.padLeft = padLeft;
|
|
11645
12081
|
exports.parseTime = parseTime;
|
|
11646
12082
|
exports.range = range;
|
|
12083
|
+
exports.rejectAsyncComboboxRequest = rejectAsyncComboboxRequest;
|
|
11647
12084
|
exports.releasePortalContainer = releasePortalContainer;
|
|
12085
|
+
exports.renderFieldChrome = renderFieldChrome;
|
|
11648
12086
|
exports.renderToPortal = renderToPortal;
|
|
12087
|
+
exports.resolveAsyncComboboxRequest = resolveAsyncComboboxRequest;
|
|
12088
|
+
exports.resolveControllableValue = resolveControllableValue;
|
|
11649
12089
|
exports.scrollToValue = scrollToValue;
|
|
11650
12090
|
exports.snapToNearestItem = snapToNearestItem;
|
|
11651
12091
|
exports.sortOptions = sortOptions;
|
|
12092
|
+
exports.startAsyncComboboxRequest = startAsyncComboboxRequest;
|
|
12093
|
+
exports.syncPortalContent = syncPortalContent;
|
|
11652
12094
|
exports.timeToMinutes = timeToMinutes;
|
|
11653
12095
|
exports.toast = toast;
|
|
11654
12096
|
exports.uniqueId = uniqueId;
|
|
11655
12097
|
exports.uuid4 = uuid4;
|
|
11656
12098
|
|
|
11657
12099
|
}));
|
|
12100
|
+
//# sourceMappingURL=index.umd.js.map
|