mithril-materialized 3.16.0 → 3.17.1
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 +92 -1
- 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 +127 -14
- 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 +340 -19
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.js +698 -296
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +711 -295
- package/dist/index.js.map +1 -0
- package/dist/index.min.css +2 -2
- package/dist/index.umd.js +711 -295
- 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 +6 -0
- 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 +16 -19
- 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/_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.esm.js
CHANGED
|
@@ -34,6 +34,35 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
34
34
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
// import './styles/input.css';
|
|
38
|
+
const Mandatory = { view: ({ attrs }) => m('span.mandatory', Object.assign({}, attrs), '*') };
|
|
39
|
+
/** Simple label element, used for most components. */
|
|
40
|
+
const Label = () => {
|
|
41
|
+
return {
|
|
42
|
+
view: (_a) => {
|
|
43
|
+
var _b = _a.attrs, { label, id, isMandatory, isActive, className, initialValue } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className", "initialValue"]);
|
|
44
|
+
return label
|
|
45
|
+
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id, oncreate: ({ dom }) => {
|
|
46
|
+
if (!initialValue)
|
|
47
|
+
return;
|
|
48
|
+
const labelEl = dom;
|
|
49
|
+
labelEl.classList.add('active');
|
|
50
|
+
} }), [m.trust(label), isMandatory ? m(Mandatory) : undefined])
|
|
51
|
+
: undefined;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
/** Create a helper text, often used for displaying a small help text. May be replaced by the validation message. */
|
|
56
|
+
const HelperText = () => {
|
|
57
|
+
return {
|
|
58
|
+
view: ({ attrs: { helperText, dataError, dataSuccess, className } }) => {
|
|
59
|
+
return helperText || dataError || dataSuccess
|
|
60
|
+
? m('span.helper-text.left', { className, 'data-error': dataError, 'data-success': dataSuccess }, dataError ? m.trust(dataError) : dataSuccess ? m.trust(dataSuccess) : helperText ? m.trust(helperText) : '')
|
|
61
|
+
: undefined;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
37
66
|
// Utility functions for the library
|
|
38
67
|
/**
|
|
39
68
|
* Create a unique ID
|
|
@@ -94,6 +123,42 @@ const sortOptions = (options, sortConfig) => {
|
|
|
94
123
|
* @returns
|
|
95
124
|
*/
|
|
96
125
|
const padLeft = (n, width = 2, z = '0') => String(n).padStart(width, z);
|
|
126
|
+
const normalizeSelection = (value) => {
|
|
127
|
+
if (value === undefined) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
return Array.isArray(value) ? value : [value];
|
|
131
|
+
};
|
|
132
|
+
const resolveControllableValue = ({ controlled, disabled, controlledValue, defaultValue, internalValue, fallbackValue, }) => {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
if (controlled) {
|
|
135
|
+
return controlledValue !== null && controlledValue !== void 0 ? controlledValue : fallbackValue;
|
|
136
|
+
}
|
|
137
|
+
if (disabled) {
|
|
138
|
+
return (_a = defaultValue !== null && defaultValue !== void 0 ? defaultValue : controlledValue) !== null && _a !== void 0 ? _a : fallbackValue;
|
|
139
|
+
}
|
|
140
|
+
return (_b = internalValue !== null && internalValue !== void 0 ? internalValue : defaultValue) !== null && _b !== void 0 ? _b : fallbackValue;
|
|
141
|
+
};
|
|
142
|
+
const renderFieldChrome = ({ label, id, isMandatory, isActive, initialValue, helperText, dataError, dataSuccess, }) => {
|
|
143
|
+
return [
|
|
144
|
+
label
|
|
145
|
+
? m(Label, {
|
|
146
|
+
label,
|
|
147
|
+
id,
|
|
148
|
+
isMandatory,
|
|
149
|
+
isActive,
|
|
150
|
+
initialValue,
|
|
151
|
+
})
|
|
152
|
+
: undefined,
|
|
153
|
+
helperText || dataError || dataSuccess
|
|
154
|
+
? m(HelperText, {
|
|
155
|
+
helperText,
|
|
156
|
+
dataError,
|
|
157
|
+
dataSuccess,
|
|
158
|
+
})
|
|
159
|
+
: undefined,
|
|
160
|
+
].filter(Boolean);
|
|
161
|
+
};
|
|
97
162
|
// Keep only essential dropdown positioning styles
|
|
98
163
|
const getDropdownStyles = (inputRef, overlap = false, options, isDropDown = false) => {
|
|
99
164
|
if (!inputRef) {
|
|
@@ -125,8 +190,10 @@ const getDropdownStyles = (inputRef, overlap = false, options, isDropDown = fals
|
|
|
125
190
|
groups.add(option.group);
|
|
126
191
|
}
|
|
127
192
|
});
|
|
128
|
-
//
|
|
129
|
-
|
|
193
|
+
// Match the select dropdown's CSS max-height. Positioning with the full
|
|
194
|
+
// option count would otherwise leave a gap above an input near the bottom
|
|
195
|
+
// of the viewport when the rendered menu is capped at 400px.
|
|
196
|
+
estimatedHeight = Math.min(totalOptions * itemHeight + groups.size * groupHeaderHeight, 400);
|
|
130
197
|
}
|
|
131
198
|
const spaceBelow = viewportHeight - rect.bottom;
|
|
132
199
|
const spaceAbove = rect.top;
|
|
@@ -142,33 +209,26 @@ const getDropdownStyles = (inputRef, overlap = false, options, isDropDown = fals
|
|
|
142
209
|
const needsScrolling = estimatedHeight > effectiveAvailableSpace;
|
|
143
210
|
// Calculate the actual height the dropdown will take
|
|
144
211
|
const actualHeight = needsScrolling ? effectiveAvailableSpace : estimatedHeight;
|
|
145
|
-
// Calculate positioning when dropdown should appear above
|
|
146
|
-
let topOffset;
|
|
147
|
-
if (shouldPositionAbove) {
|
|
148
|
-
// Calculate how much space we actually have from top of viewport to top of input
|
|
149
|
-
const availableSpaceFromViewportTop = rect.top;
|
|
150
|
-
// If dropdown fits comfortably above input, use normal positioning
|
|
151
|
-
if (actualHeight <= availableSpaceFromViewportTop) {
|
|
152
|
-
topOffset = 12 - actualHeight + (isDropDown ? itemHeight : 0); // Bottom of dropdown aligns with top of input
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
// If dropdown is too tall, position it at the very top of viewport
|
|
156
|
-
// This makes the dropdown use all available space from viewport top to input top
|
|
157
|
-
topOffset = -availableSpaceFromViewportTop + 5; // 5px margin from viewport top
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
topOffset = overlap ? 0 : '100%';
|
|
162
|
-
}
|
|
163
212
|
const styles = {
|
|
164
213
|
display: 'block',
|
|
165
214
|
opacity: 1,
|
|
166
215
|
position: 'absolute',
|
|
167
|
-
top: typeof topOffset === 'number' ? `${topOffset}px` : topOffset,
|
|
168
216
|
left: '0',
|
|
169
217
|
zIndex: 1000,
|
|
170
218
|
width: `${rect.width}px`,
|
|
171
219
|
};
|
|
220
|
+
if (shouldPositionAbove) {
|
|
221
|
+
// This menu is absolutely positioned inside the select wrapper. A negative
|
|
222
|
+
// `top` calculated from viewport coordinates becomes wrong when an ancestor
|
|
223
|
+
// scrolls (as ScenarioSpark's main area does). Anchor the menu to its local
|
|
224
|
+
// containing block instead; its height can then change without detaching it
|
|
225
|
+
// from the input.
|
|
226
|
+
styles.top = 'auto';
|
|
227
|
+
styles.bottom = `${isDropDown ? -18 : inputRef.offsetHeight - 12}px`;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
styles.top = overlap ? 0 : '100%';
|
|
231
|
+
}
|
|
172
232
|
// Only add scrolling constraints when necessary
|
|
173
233
|
if (needsScrolling) {
|
|
174
234
|
styles.maxHeight = `${actualHeight}px`;
|
|
@@ -235,9 +295,17 @@ const releasePortalContainer = (id) => {
|
|
|
235
295
|
* @param zIndex - Z-index for portal container (default: 1004)
|
|
236
296
|
*/
|
|
237
297
|
const renderToPortal = (containerId, vnode, zIndex = 1004) => {
|
|
238
|
-
const
|
|
298
|
+
const existingContainer = portalContainers.get(containerId);
|
|
299
|
+
const container = existingContainer ? existingContainer.element : getPortalContainer(containerId, zIndex);
|
|
239
300
|
m.render(container, vnode);
|
|
240
301
|
};
|
|
302
|
+
const syncPortalContent = ({ containerId, shouldRender, vnode, zIndex = 1004 }) => {
|
|
303
|
+
if (!shouldRender || vnode === null) {
|
|
304
|
+
clearPortal(containerId);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
renderToPortal(containerId, vnode, zIndex);
|
|
308
|
+
};
|
|
241
309
|
/**
|
|
242
310
|
* Clears portal content and releases container reference.
|
|
243
311
|
* If this is the last reference, the container will be removed from the DOM.
|
|
@@ -252,35 +320,6 @@ const clearPortal = (containerId) => {
|
|
|
252
320
|
}
|
|
253
321
|
};
|
|
254
322
|
|
|
255
|
-
// import './styles/input.css';
|
|
256
|
-
const Mandatory = { view: ({ attrs }) => m('span.mandatory', Object.assign({}, attrs), '*') };
|
|
257
|
-
/** Simple label element, used for most components. */
|
|
258
|
-
const Label = () => {
|
|
259
|
-
return {
|
|
260
|
-
view: (_a) => {
|
|
261
|
-
var _b = _a.attrs, { label, id, isMandatory, isActive, className, initialValue } = _b, params = __rest(_b, ["label", "id", "isMandatory", "isActive", "className", "initialValue"]);
|
|
262
|
-
return label
|
|
263
|
-
? m('label', Object.assign(Object.assign({}, params), { className: [className, isActive ? 'active' : ''].filter(Boolean).join(' ').trim() || undefined, for: id, oncreate: ({ dom }) => {
|
|
264
|
-
if (!initialValue)
|
|
265
|
-
return;
|
|
266
|
-
const labelEl = dom;
|
|
267
|
-
labelEl.classList.add('active');
|
|
268
|
-
} }), [m.trust(label), isMandatory ? m(Mandatory) : undefined])
|
|
269
|
-
: undefined;
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
};
|
|
273
|
-
/** Create a helper text, often used for displaying a small help text. May be replaced by the validation message. */
|
|
274
|
-
const HelperText = () => {
|
|
275
|
-
return {
|
|
276
|
-
view: ({ attrs: { helperText, dataError, dataSuccess, className } }) => {
|
|
277
|
-
return helperText || dataError || dataSuccess
|
|
278
|
-
? m('span.helper-text.left', { className, 'data-error': dataError, 'data-success': dataSuccess }, dataError ? m.trust(dataError) : dataSuccess ? m.trust(dataSuccess) : helperText ? m.trust(helperText) : '')
|
|
279
|
-
: undefined;
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
};
|
|
283
|
-
|
|
284
323
|
/** Component to auto complete your text input - Pure Mithril implementation */
|
|
285
324
|
const Autocomplete = () => {
|
|
286
325
|
const state = {
|
|
@@ -303,6 +342,28 @@ const Autocomplete = () => {
|
|
|
303
342
|
.slice(0, limit);
|
|
304
343
|
return filtered;
|
|
305
344
|
};
|
|
345
|
+
const highlightMatch = (text, query) => {
|
|
346
|
+
if (!query) {
|
|
347
|
+
return text;
|
|
348
|
+
}
|
|
349
|
+
const lowerText = text.toLowerCase();
|
|
350
|
+
const lowerQuery = query.toLowerCase();
|
|
351
|
+
const nodes = [];
|
|
352
|
+
let start = 0;
|
|
353
|
+
let index = lowerText.indexOf(lowerQuery, start);
|
|
354
|
+
while (index !== -1) {
|
|
355
|
+
if (index > start) {
|
|
356
|
+
nodes.push(text.slice(start, index));
|
|
357
|
+
}
|
|
358
|
+
nodes.push(m('span.highlight', text.slice(index, index + query.length)));
|
|
359
|
+
start = index + query.length;
|
|
360
|
+
index = lowerText.indexOf(lowerQuery, start);
|
|
361
|
+
}
|
|
362
|
+
if (start < text.length) {
|
|
363
|
+
nodes.push(text.slice(start));
|
|
364
|
+
}
|
|
365
|
+
return nodes.length > 0 ? nodes : text;
|
|
366
|
+
};
|
|
306
367
|
const selectSuggestion = (suggestion, attrs) => {
|
|
307
368
|
const controlled = isControlled(attrs);
|
|
308
369
|
// Update internal state for uncontrolled mode
|
|
@@ -408,7 +469,6 @@ const Autocomplete = () => {
|
|
|
408
469
|
Object.keys(data).some((key) => key.toLowerCase() === currentValue.toLowerCase());
|
|
409
470
|
// Only open dropdown if there are suggestions and no perfect match
|
|
410
471
|
state.isOpen = state.suggestions.length > 0 && currentValue.length >= minLength && !hasExactMatch;
|
|
411
|
-
const replacer = new RegExp(`(${currentValue})`, 'i');
|
|
412
472
|
return m('.input-field.autocomplete-wrapper', {
|
|
413
473
|
className: cn,
|
|
414
474
|
style,
|
|
@@ -489,9 +549,7 @@ const Autocomplete = () => {
|
|
|
489
549
|
},
|
|
490
550
|
}, suggestion.value.replace('icon:', ''))
|
|
491
551
|
: null,
|
|
492
|
-
m('span', suggestion.key
|
|
493
|
-
? m.trust(suggestion.key.replace(replacer, (i) => `<span class="highlight">${i}</span>`))
|
|
494
|
-
: ''),
|
|
552
|
+
m('span', suggestion.key ? highlightMatch(suggestion.key, currentValue) : ''),
|
|
495
553
|
]))),
|
|
496
554
|
m(Label, {
|
|
497
555
|
label,
|
|
@@ -718,7 +776,7 @@ WavesEffect.onTouchEnd = (e) => {
|
|
|
718
776
|
/**
|
|
719
777
|
* A factory to create new buttons.
|
|
720
778
|
*
|
|
721
|
-
* @example FlatButton = ButtonFactory('
|
|
779
|
+
* @example FlatButton = ButtonFactory('button.waves-effect.waves-teal.btn-flat');
|
|
722
780
|
*/
|
|
723
781
|
const ButtonFactory = (element, defaultClassNames, type = '') => {
|
|
724
782
|
return () => {
|
|
@@ -741,15 +799,21 @@ const ButtonFactory = (element, defaultClassNames, type = '') => {
|
|
|
741
799
|
ontouchend: WavesEffect.onTouchEnd,
|
|
742
800
|
}
|
|
743
801
|
: {};
|
|
744
|
-
|
|
802
|
+
const tagName = element.split(/[.#]/)[0] || element;
|
|
803
|
+
const elementSuffix = element.slice(tagName.length);
|
|
804
|
+
const isLink = Boolean(attrs.href);
|
|
805
|
+
const renderedElement = `${isLink ? 'a' : 'button'}${elementSuffix}`;
|
|
806
|
+
return m(renderedElement, Object.assign(Object.assign(Object.assign({}, params), wavesHandlers), { className: cn, 'data-position': tooltip ? position : undefined, 'data-tooltip': tooltip || undefined,
|
|
807
|
+
// Links retain native navigation semantics and must not have a button type.
|
|
808
|
+
type: isLink ? undefined : buttonType }), iconName ? m(Icon, { iconName, className: iconClass !== undefined ? iconClass : 'left' }) : undefined, label ? label : undefined, children);
|
|
745
809
|
},
|
|
746
810
|
};
|
|
747
811
|
};
|
|
748
812
|
};
|
|
749
|
-
const Button = ButtonFactory('
|
|
750
|
-
const LargeButton = ButtonFactory('
|
|
751
|
-
const SmallButton = ButtonFactory('
|
|
752
|
-
const FlatButton = ButtonFactory('
|
|
813
|
+
const Button = ButtonFactory('button', 'waves-effect waves-light btn', 'button');
|
|
814
|
+
const LargeButton = ButtonFactory('button', 'waves-effect waves-light btn-large', 'button');
|
|
815
|
+
const SmallButton = ButtonFactory('button', 'waves-effect waves-light btn-small', 'button');
|
|
816
|
+
const FlatButton = ButtonFactory('button', 'waves-effect waves-teal btn-flat', 'button');
|
|
753
817
|
const IconButton = ButtonFactory('button', 'btn-flat btn-icon waves-effect waves-teal', 'button');
|
|
754
818
|
const RoundIconButton = ButtonFactory('button', 'btn-floating btn-large waves-effect waves-light', 'button');
|
|
755
819
|
const SubmitButton = ButtonFactory('button', 'btn waves-effect waves-light', 'submit');
|
|
@@ -806,7 +870,7 @@ const ConfirmButton = () => {
|
|
|
806
870
|
// Add square styling for icon-only confirming state
|
|
807
871
|
const buttonStyle = !label && isConfirming
|
|
808
872
|
? 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;
|
|
809
|
-
return m(ButtonComponent, Object.assign(Object.assign({}, props), { style: buttonStyle, className: `${props.className || ''} ${cn}`, iconName: currentIconName, iconClass: label ?
|
|
873
|
+
return m(ButtonComponent, Object.assign(Object.assign({}, props), { style: buttonStyle, className: `${props.className || ''} ${cn}`, iconName: currentIconName, iconClass: label ? iconClass || 'left' : '', label, onclick: handleClick }));
|
|
810
874
|
},
|
|
811
875
|
};
|
|
812
876
|
};
|
|
@@ -1273,11 +1337,19 @@ const MaterialIcon = () => {
|
|
|
1273
1337
|
};
|
|
1274
1338
|
const rotation = (_a = rotationMap[direction]) !== null && _a !== void 0 ? _a : 0;
|
|
1275
1339
|
const transform = rotation ? `rotate(${rotation}deg)` : undefined;
|
|
1340
|
+
const baseStyle = {
|
|
1341
|
+
display: 'inline-block',
|
|
1342
|
+
verticalAlign: 'middle',
|
|
1343
|
+
transform,
|
|
1344
|
+
};
|
|
1345
|
+
const combinedStyle = typeof style === 'string'
|
|
1346
|
+
? `display:inline-block;vertical-align:middle;${transform ? `transform:${transform};` : ''}${style}`
|
|
1347
|
+
: Object.assign(Object.assign({}, baseStyle), style);
|
|
1276
1348
|
const icon = iconPaths[name];
|
|
1277
1349
|
if (!icon || !Array.isArray(icon)) {
|
|
1278
1350
|
return m(Icon, Object.assign(Object.assign({}, props), { iconName: name }));
|
|
1279
1351
|
}
|
|
1280
|
-
return m('svg', Object.assign(Object.assign({}, props), { style:
|
|
1352
|
+
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', {
|
|
1281
1353
|
d,
|
|
1282
1354
|
fill: d.includes('M0 0h24v24H0z') ? 'none' : 'currentColor',
|
|
1283
1355
|
})));
|
|
@@ -1676,6 +1748,106 @@ const Collapsible = () => {
|
|
|
1676
1748
|
};
|
|
1677
1749
|
};
|
|
1678
1750
|
|
|
1751
|
+
const isHandledKey = (key) => key === 'ArrowDown' || key === 'ArrowUp' || key === 'Enter' || key === ' ' || key === 'Escape';
|
|
1752
|
+
const getTotalRows = (optionCount, includeActionRow) => Math.max(0, optionCount) + (includeActionRow ? 1 : 0);
|
|
1753
|
+
const getComboboxKeyResult = ({ key, isOpen, focusedIndex, optionCount, includeActionRow, }) => {
|
|
1754
|
+
const totalRows = getTotalRows(optionCount, includeActionRow);
|
|
1755
|
+
if (!isHandledKey(key)) {
|
|
1756
|
+
return { isOpen, focusedIndex, action: 'none', preventDefault: false };
|
|
1757
|
+
}
|
|
1758
|
+
if (key === 'Escape') {
|
|
1759
|
+
return { isOpen: false, focusedIndex: -1, action: 'close', preventDefault: true };
|
|
1760
|
+
}
|
|
1761
|
+
if (key === 'ArrowDown') {
|
|
1762
|
+
if (!isOpen) {
|
|
1763
|
+
return {
|
|
1764
|
+
isOpen: true,
|
|
1765
|
+
focusedIndex: totalRows > 0 ? 0 : -1,
|
|
1766
|
+
action: 'open',
|
|
1767
|
+
preventDefault: true,
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
return {
|
|
1771
|
+
isOpen: true,
|
|
1772
|
+
focusedIndex: totalRows > 0 ? Math.min(focusedIndex + 1, totalRows - 1) : -1,
|
|
1773
|
+
action: 'none',
|
|
1774
|
+
preventDefault: true,
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
if (key === 'ArrowUp') {
|
|
1778
|
+
if (!isOpen) {
|
|
1779
|
+
return {
|
|
1780
|
+
isOpen: true,
|
|
1781
|
+
focusedIndex: totalRows > 0 ? 0 : -1,
|
|
1782
|
+
action: 'open',
|
|
1783
|
+
preventDefault: true,
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
return {
|
|
1787
|
+
isOpen: true,
|
|
1788
|
+
focusedIndex: totalRows > 0 ? Math.max(focusedIndex - 1, 0) : -1,
|
|
1789
|
+
action: 'none',
|
|
1790
|
+
preventDefault: true,
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
if (!isOpen) {
|
|
1794
|
+
return {
|
|
1795
|
+
isOpen: true,
|
|
1796
|
+
focusedIndex: totalRows > 0 ? 0 : -1,
|
|
1797
|
+
action: 'open',
|
|
1798
|
+
preventDefault: true,
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
if (focusedIndex < 0) {
|
|
1802
|
+
return { isOpen, focusedIndex, action: 'none', preventDefault: true };
|
|
1803
|
+
}
|
|
1804
|
+
const isActionRow = includeActionRow && focusedIndex === optionCount;
|
|
1805
|
+
return {
|
|
1806
|
+
isOpen,
|
|
1807
|
+
focusedIndex,
|
|
1808
|
+
action: isActionRow ? 'selectAction' : 'selectFocused',
|
|
1809
|
+
preventDefault: true,
|
|
1810
|
+
};
|
|
1811
|
+
};
|
|
1812
|
+
const getComboboxOptionId = (baseId, optionIndex) => `${baseId}-option-${optionIndex}`;
|
|
1813
|
+
const createAsyncComboboxState = (initialOptions = []) => ({
|
|
1814
|
+
options: initialOptions,
|
|
1815
|
+
isLoading: false,
|
|
1816
|
+
error: null,
|
|
1817
|
+
latestRequestId: 0,
|
|
1818
|
+
});
|
|
1819
|
+
const startAsyncComboboxRequest = (state) => {
|
|
1820
|
+
const requestId = state.latestRequestId + 1;
|
|
1821
|
+
return {
|
|
1822
|
+
requestId,
|
|
1823
|
+
nextState: Object.assign(Object.assign({}, state), { isLoading: true, error: null, latestRequestId: requestId }),
|
|
1824
|
+
};
|
|
1825
|
+
};
|
|
1826
|
+
const resolveAsyncComboboxRequest = (state, requestId, options) => {
|
|
1827
|
+
if (requestId !== state.latestRequestId) {
|
|
1828
|
+
return state;
|
|
1829
|
+
}
|
|
1830
|
+
return Object.assign(Object.assign({}, state), { options, isLoading: false, error: null });
|
|
1831
|
+
};
|
|
1832
|
+
const rejectAsyncComboboxRequest = (state, requestId, errorMessage) => {
|
|
1833
|
+
if (requestId !== state.latestRequestId) {
|
|
1834
|
+
return state;
|
|
1835
|
+
}
|
|
1836
|
+
return Object.assign(Object.assign({}, state), { options: [], isLoading: false, error: errorMessage });
|
|
1837
|
+
};
|
|
1838
|
+
const getComboboxViewState = ({ isLoading, error, optionCount, }) => {
|
|
1839
|
+
if (isLoading) {
|
|
1840
|
+
return 'loading';
|
|
1841
|
+
}
|
|
1842
|
+
if (error) {
|
|
1843
|
+
return 'error';
|
|
1844
|
+
}
|
|
1845
|
+
if (optionCount === 0) {
|
|
1846
|
+
return 'empty';
|
|
1847
|
+
}
|
|
1848
|
+
return 'ready';
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1679
1851
|
var CollectionMode;
|
|
1680
1852
|
(function (CollectionMode) {
|
|
1681
1853
|
CollectionMode[CollectionMode["BASIC"] = 0] = "BASIC";
|
|
@@ -3285,6 +3457,10 @@ const DoubleRangeSlider = {
|
|
|
3285
3457
|
},
|
|
3286
3458
|
};
|
|
3287
3459
|
|
|
3460
|
+
const isReadonly = (attrs) => {
|
|
3461
|
+
const legacyReadonly = attrs.readonly;
|
|
3462
|
+
return Boolean(attrs.readOnly || legacyReadonly);
|
|
3463
|
+
};
|
|
3288
3464
|
/** Character counter component that tracks text length against maxLength */
|
|
3289
3465
|
const CharacterCounter = () => {
|
|
3290
3466
|
return {
|
|
@@ -3363,7 +3539,7 @@ const TextArea = () => {
|
|
|
3363
3539
|
return {
|
|
3364
3540
|
oninit: ({ attrs }) => {
|
|
3365
3541
|
const controlled = isControlled(attrs);
|
|
3366
|
-
const isNonInteractive = attrs
|
|
3542
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3367
3543
|
// Warn developer for improper controlled usage
|
|
3368
3544
|
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
3369
3545
|
console.warn(`TextArea received 'value' prop without 'oninput' or 'onchange' handler. ` +
|
|
@@ -3380,7 +3556,7 @@ const TextArea = () => {
|
|
|
3380
3556
|
var _a, _b, _c, _d;
|
|
3381
3557
|
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"]);
|
|
3382
3558
|
const controlled = isControlled(attrs);
|
|
3383
|
-
const isNonInteractive = attrs
|
|
3559
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3384
3560
|
let currentValue;
|
|
3385
3561
|
if (controlled) {
|
|
3386
3562
|
currentValue = value || '';
|
|
@@ -3496,14 +3672,12 @@ const TextArea = () => {
|
|
|
3496
3672
|
onkeypress(ev, ev.target.value);
|
|
3497
3673
|
}
|
|
3498
3674
|
: undefined })),
|
|
3499
|
-
|
|
3675
|
+
...renderFieldChrome({
|
|
3500
3676
|
label,
|
|
3501
3677
|
id,
|
|
3502
3678
|
isMandatory,
|
|
3503
3679
|
isActive: currentValue || placeholder || state.active,
|
|
3504
3680
|
initialValue: currentValue !== '',
|
|
3505
|
-
}),
|
|
3506
|
-
m(HelperText, {
|
|
3507
3681
|
helperText,
|
|
3508
3682
|
dataError: state.hasInteracted && attrs.dataError ? attrs.dataError : undefined,
|
|
3509
3683
|
dataSuccess: state.hasInteracted && attrs.dataSuccess ? attrs.dataSuccess : undefined,
|
|
@@ -3583,7 +3757,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3583
3757
|
return {
|
|
3584
3758
|
oninit: ({ attrs }) => {
|
|
3585
3759
|
const controlled = isControlled(attrs);
|
|
3586
|
-
const isNonInteractive = attrs
|
|
3760
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3587
3761
|
// Warn developer for improper controlled usage
|
|
3588
3762
|
if (attrs.value !== undefined && !controlled && !isNonInteractive) {
|
|
3589
3763
|
console.warn(`${type} input with label '${attrs.label}' received 'value' prop without 'oninput' handler. ` +
|
|
@@ -3623,7 +3797,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3623
3797
|
}
|
|
3624
3798
|
const isNumeric = ['number', 'range'].includes(type);
|
|
3625
3799
|
const controlled = isControlled(attrs);
|
|
3626
|
-
const isNonInteractive = attrs
|
|
3800
|
+
const isNonInteractive = isReadonly(attrs) || attrs.disabled;
|
|
3627
3801
|
let value;
|
|
3628
3802
|
if (controlled) {
|
|
3629
3803
|
value = attrs.value;
|
|
@@ -3642,9 +3816,28 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3642
3816
|
const rangeType = type === 'range' && !attrs.minmax;
|
|
3643
3817
|
// Only add validate class if input is interactive and validation is needed
|
|
3644
3818
|
const shouldValidate = !isNonInteractive && (validate || type === 'email' || type === 'url' || isNumeric);
|
|
3819
|
+
const inputClass = [
|
|
3820
|
+
type === 'number' ? 'number-input' : '',
|
|
3821
|
+
type === 'range' && attrs.vertical ? 'range-slider vertical' : '',
|
|
3822
|
+
shouldValidate ? 'validate' : '',
|
|
3823
|
+
]
|
|
3824
|
+
.filter(Boolean)
|
|
3825
|
+
.join(' ');
|
|
3826
|
+
const stepNumberInput = (direction) => {
|
|
3827
|
+
const input = state.inputElement;
|
|
3828
|
+
if (!input)
|
|
3829
|
+
return;
|
|
3830
|
+
if (direction === 'up') {
|
|
3831
|
+
input.stepUp();
|
|
3832
|
+
}
|
|
3833
|
+
else {
|
|
3834
|
+
input.stepDown();
|
|
3835
|
+
}
|
|
3836
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3837
|
+
};
|
|
3645
3838
|
return m('.input-field', { className: cn, style }, [
|
|
3646
3839
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3647
|
-
m('input', Object.assign(Object.assign({ class:
|
|
3840
|
+
m('input', Object.assign(Object.assign({ class: inputClass || undefined }, params), { type, tabindex: 0, id,
|
|
3648
3841
|
placeholder, value: controlled ? value : undefined, style: type === 'range' && attrs.vertical
|
|
3649
3842
|
? {
|
|
3650
3843
|
height: attrs.height || '200px',
|
|
@@ -3726,7 +3919,7 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3726
3919
|
const target = e.target;
|
|
3727
3920
|
state.hasInteracted = true;
|
|
3728
3921
|
// Skip validation for readonly/disabled inputs
|
|
3729
|
-
if (attrs
|
|
3922
|
+
if (isReadonly(attrs) || attrs.disabled) {
|
|
3730
3923
|
// Call original onblur if provided
|
|
3731
3924
|
if (attrs.onblur) {
|
|
3732
3925
|
attrs.onblur(e);
|
|
@@ -3816,6 +4009,22 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3816
4009
|
onchange(getValue(state.inputElement));
|
|
3817
4010
|
}
|
|
3818
4011
|
} })),
|
|
4012
|
+
type === 'number' && !isNonInteractive
|
|
4013
|
+
? m('.number-input-controls', [
|
|
4014
|
+
m('button.number-input-control.number-input-control-up[type=button]', {
|
|
4015
|
+
'aria-label': `Increase ${label || 'value'}`,
|
|
4016
|
+
title: 'Increase value',
|
|
4017
|
+
onmousedown: (event) => event.preventDefault(),
|
|
4018
|
+
onclick: () => stepNumberInput('up'),
|
|
4019
|
+
}, m(MaterialIcon, { name: 'chevron', direction: 'up' })),
|
|
4020
|
+
m('button.number-input-control.number-input-control-down[type=button]', {
|
|
4021
|
+
'aria-label': `Decrease ${label || 'value'}`,
|
|
4022
|
+
title: 'Decrease value',
|
|
4023
|
+
onmousedown: (event) => event.preventDefault(),
|
|
4024
|
+
onclick: () => stepNumberInput('down'),
|
|
4025
|
+
}, m(MaterialIcon, { name: 'chevron' })),
|
|
4026
|
+
])
|
|
4027
|
+
: undefined,
|
|
3819
4028
|
// Clear button - only for text inputs with canClear enabled and has content
|
|
3820
4029
|
canClear && type === 'text' && ((_f = state.inputElement) === null || _f === void 0 ? void 0 : _f.value)
|
|
3821
4030
|
? m(MaterialIcon, {
|
|
@@ -3828,14 +4037,12 @@ const InputField = (type, defaultClass = '') => () => {
|
|
|
3828
4037
|
},
|
|
3829
4038
|
})
|
|
3830
4039
|
: undefined,
|
|
3831
|
-
|
|
4040
|
+
...renderFieldChrome({
|
|
3832
4041
|
label,
|
|
3833
4042
|
id,
|
|
3834
4043
|
isMandatory,
|
|
3835
4044
|
isActive,
|
|
3836
4045
|
initialValue: value !== undefined && value !== '',
|
|
3837
|
-
}),
|
|
3838
|
-
m(HelperText, {
|
|
3839
4046
|
helperText,
|
|
3840
4047
|
dataError: state.hasInteracted && !state.isValid ? dataError : undefined,
|
|
3841
4048
|
dataSuccess: state.hasInteracted && state.isValid ? dataSuccess : undefined,
|
|
@@ -3935,7 +4142,7 @@ const InputCheckbox = () => {
|
|
|
3935
4142
|
view: ({ attrs: { className = 'col s12', onchange, label, checked, disabled, description, style, inputId } }) => {
|
|
3936
4143
|
if (!checkboxId)
|
|
3937
4144
|
checkboxId = inputId || uniqueId();
|
|
3938
|
-
return m('div', { className, style }, m('label', { for: checkboxId }, [
|
|
4145
|
+
return m('div', { className, style }, m('label', { for: checkboxId, style: { position: 'relative', display: 'inline-block' } }, [
|
|
3939
4146
|
m('input[type=checkbox][tabindex=0]', {
|
|
3940
4147
|
className: disabled ? 'disabled' : undefined,
|
|
3941
4148
|
id: checkboxId,
|
|
@@ -4700,17 +4907,6 @@ const Dropdown = () => {
|
|
|
4700
4907
|
const updatePortalDropdown = (items, selectedLabel, onSelectItem, maxHeight) => {
|
|
4701
4908
|
if (!state.isInsideModal)
|
|
4702
4909
|
return;
|
|
4703
|
-
// Clean up existing portal
|
|
4704
|
-
const existingPortal = document.getElementById(`${state.id}-dropdown`);
|
|
4705
|
-
if (existingPortal) {
|
|
4706
|
-
existingPortal.remove();
|
|
4707
|
-
}
|
|
4708
|
-
if (!state.isOpen || !state.inputRef)
|
|
4709
|
-
return;
|
|
4710
|
-
// Create portal element
|
|
4711
|
-
const portalElement = document.createElement('div');
|
|
4712
|
-
portalElement.id = `${state.id}-dropdown`;
|
|
4713
|
-
document.body.appendChild(portalElement);
|
|
4714
4910
|
// Create dropdown content
|
|
4715
4911
|
const availableItems = items.filter((item) => !item.divider && !item.disabled);
|
|
4716
4912
|
const dropdownContent = items.map((item) => {
|
|
@@ -4724,15 +4920,14 @@ const Dropdown = () => {
|
|
|
4724
4920
|
class: `${isSelected ? 'selected' : ''} ${isFocused ? 'focused' : ''}${item.disabled ? ' disabled' : ''}`,
|
|
4725
4921
|
onclick: item.disabled ? undefined : () => onSelectItem(item),
|
|
4726
4922
|
}, m('span', {
|
|
4923
|
+
class: 'mm-layout-row mm-layout-row--center',
|
|
4727
4924
|
style: {
|
|
4728
|
-
display: 'flex',
|
|
4729
|
-
alignItems: 'center',
|
|
4730
4925
|
padding: '14px 16px',
|
|
4731
4926
|
},
|
|
4732
4927
|
}, [
|
|
4733
4928
|
item.iconName
|
|
4734
4929
|
? m('i.material-icons', {
|
|
4735
|
-
|
|
4930
|
+
class: 'mm-layout-item-icon',
|
|
4736
4931
|
}, item.iconName)
|
|
4737
4932
|
: undefined,
|
|
4738
4933
|
item.label,
|
|
@@ -4749,8 +4944,12 @@ const Dropdown = () => {
|
|
|
4749
4944
|
state.dropdownRef = null;
|
|
4750
4945
|
},
|
|
4751
4946
|
}, dropdownContent);
|
|
4752
|
-
|
|
4753
|
-
|
|
4947
|
+
syncPortalContent({
|
|
4948
|
+
containerId: `${state.id}-dropdown`,
|
|
4949
|
+
shouldRender: state.isOpen && !!state.inputRef,
|
|
4950
|
+
vnode: dropdownVnode,
|
|
4951
|
+
zIndex: 10000,
|
|
4952
|
+
});
|
|
4754
4953
|
};
|
|
4755
4954
|
return {
|
|
4756
4955
|
oninit: ({ attrs }) => {
|
|
@@ -4771,15 +4970,19 @@ const Dropdown = () => {
|
|
|
4771
4970
|
// Cleanup global listener
|
|
4772
4971
|
document.removeEventListener('click', closeDropdown);
|
|
4773
4972
|
// Cleanup portal
|
|
4774
|
-
|
|
4775
|
-
if (portalElement) {
|
|
4776
|
-
portalElement.remove();
|
|
4777
|
-
}
|
|
4973
|
+
syncPortalContent({ containerId: `${state.id}-dropdown`, shouldRender: false, vnode: null });
|
|
4778
4974
|
},
|
|
4779
4975
|
view: ({ attrs }) => {
|
|
4780
4976
|
const { checkedId, key, label, onchange, disabled = false, items, iconName, helperText, style, className = 'col s12', } = attrs;
|
|
4781
4977
|
const controlled = isControlled(attrs);
|
|
4782
|
-
const currentCheckedId =
|
|
4978
|
+
const currentCheckedId = resolveControllableValue({
|
|
4979
|
+
controlled,
|
|
4980
|
+
disabled,
|
|
4981
|
+
controlledValue: checkedId,
|
|
4982
|
+
defaultValue: attrs.defaultCheckedId,
|
|
4983
|
+
internalValue: state.internalCheckedId,
|
|
4984
|
+
fallbackValue: undefined,
|
|
4985
|
+
});
|
|
4783
4986
|
const handleSelection = (value) => {
|
|
4784
4987
|
// Update internal state for uncontrolled mode
|
|
4785
4988
|
if (!controlled) {
|
|
@@ -4811,6 +5014,7 @@ const Dropdown = () => {
|
|
|
4811
5014
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
4812
5015
|
m(HelperText, { helperText }),
|
|
4813
5016
|
m('.select-wrapper', {
|
|
5017
|
+
class: 'mm-layout-row mm-layout-row--center',
|
|
4814
5018
|
onkeydown: disabled
|
|
4815
5019
|
? undefined
|
|
4816
5020
|
: (e) => {
|
|
@@ -4878,15 +5082,14 @@ const Dropdown = () => {
|
|
|
4878
5082
|
handleSelection(value);
|
|
4879
5083
|
},
|
|
4880
5084
|
}, m('span', {
|
|
5085
|
+
class: 'mm-layout-row mm-layout-row--center',
|
|
4881
5086
|
style: {
|
|
4882
|
-
display: 'flex',
|
|
4883
|
-
alignItems: 'center',
|
|
4884
5087
|
padding: '14px 16px',
|
|
4885
5088
|
},
|
|
4886
5089
|
}, [
|
|
4887
5090
|
item.iconName
|
|
4888
5091
|
? m('i.material-icons', {
|
|
4889
|
-
|
|
5092
|
+
class: 'mm-layout-item-icon',
|
|
4890
5093
|
}, item.iconName)
|
|
4891
5094
|
: undefined,
|
|
4892
5095
|
item.label,
|
|
@@ -6843,9 +7046,9 @@ const initPushpins = (selector = '.pushpin', options = {}) => {
|
|
|
6843
7046
|
};
|
|
6844
7047
|
|
|
6845
7048
|
const RadioButton = () => ({
|
|
6846
|
-
view: ({ attrs: { id, groupId, label, onchange, className = 'col s12', checked, disabled, inputId } }) => {
|
|
7049
|
+
view: ({ attrs: { id, groupId, label, onchange, className = 'col s12', checked, disabled, inputId, allowHtml } }) => {
|
|
6847
7050
|
const radioId = inputId || `${groupId}-${id}`;
|
|
6848
|
-
return m('p', { className }, m('label', { for: radioId }, [
|
|
7051
|
+
return m('p', { className }, m('label', { for: radioId, style: { position: 'relative', display: 'inline-block' } }, [
|
|
6849
7052
|
m('input[type=radio][tabindex=0]', {
|
|
6850
7053
|
id: radioId,
|
|
6851
7054
|
name: groupId,
|
|
@@ -6853,7 +7056,7 @@ const RadioButton = () => ({
|
|
|
6853
7056
|
checked,
|
|
6854
7057
|
onclick: onchange ? () => onchange(id) : undefined,
|
|
6855
7058
|
}),
|
|
6856
|
-
m('span', m.trust(label)),
|
|
7059
|
+
m('span', allowHtml ? m.trust(label) : label),
|
|
6857
7060
|
]));
|
|
6858
7061
|
},
|
|
6859
7062
|
});
|
|
@@ -6880,8 +7083,9 @@ const RadioButtons = () => {
|
|
|
6880
7083
|
}
|
|
6881
7084
|
},
|
|
6882
7085
|
view: ({ attrs }) => {
|
|
6883
|
-
var _a, _b;
|
|
6884
|
-
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout =
|
|
7086
|
+
var _a, _b, _c;
|
|
7087
|
+
const { checkedId, newRow, className = 'col s12', label = '', disabled, description, options, isMandatory, checkboxClass, layout, direction, allowHtml = false, onchange, } = attrs;
|
|
7088
|
+
const resolvedLayout = (_a = layout !== null && layout !== void 0 ? layout : direction) !== null && _a !== void 0 ? _a : 'vertical';
|
|
6885
7089
|
const { groupId, componentId } = state;
|
|
6886
7090
|
const controlled = isControlled(attrs);
|
|
6887
7091
|
// Get current checked ID from props or internal state
|
|
@@ -6891,11 +7095,11 @@ const RadioButtons = () => {
|
|
|
6891
7095
|
}
|
|
6892
7096
|
else if (disabled) {
|
|
6893
7097
|
// Non-interactive components: prefer defaultCheckedId, fallback to checkedId
|
|
6894
|
-
currentCheckedId = (
|
|
7098
|
+
currentCheckedId = (_b = attrs.defaultCheckedId) !== null && _b !== void 0 ? _b : checkedId;
|
|
6895
7099
|
}
|
|
6896
7100
|
else {
|
|
6897
7101
|
// Interactive uncontrolled: use internal state
|
|
6898
|
-
currentCheckedId = (
|
|
7102
|
+
currentCheckedId = (_c = state.internalCheckedId) !== null && _c !== void 0 ? _c : attrs.defaultCheckedId;
|
|
6899
7103
|
}
|
|
6900
7104
|
const handleChange = (id) => {
|
|
6901
7105
|
// Update internal state for uncontrolled mode
|
|
@@ -6910,16 +7114,16 @@ const RadioButtons = () => {
|
|
|
6910
7114
|
const cn = [newRow ? 'clear' : '', className].filter(Boolean).join(' ').trim() || undefined;
|
|
6911
7115
|
const radioItems = options.map((r) => ({
|
|
6912
7116
|
component: (RadioButton),
|
|
6913
|
-
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}
|
|
7117
|
+
props: Object.assign(Object.assign({}, r), { onchange: handleChange, groupId, disabled: disabled || r.disabled, className: checkboxClass, checked: r.id === currentCheckedId, inputId: `${componentId}-${r.id}`, allowHtml }),
|
|
6914
7118
|
key: r.id,
|
|
6915
7119
|
}));
|
|
6916
7120
|
const optionsContent = m(OptionsList, {
|
|
6917
7121
|
options: radioItems,
|
|
6918
|
-
layout,
|
|
7122
|
+
layout: resolvedLayout,
|
|
6919
7123
|
});
|
|
6920
7124
|
return m('div', { id: componentId, className: cn }, [
|
|
6921
7125
|
label && m('h5.form-group-label', label + (isMandatory ? ' *' : '')),
|
|
6922
|
-
description && m('p.helper-text', m.trust(description)),
|
|
7126
|
+
description && m('p.helper-text', allowHtml ? m.trust(description) : description),
|
|
6923
7127
|
m('form', { action: '#' }, optionsContent),
|
|
6924
7128
|
]);
|
|
6925
7129
|
},
|
|
@@ -7056,6 +7260,26 @@ const Select = () => {
|
|
|
7056
7260
|
opacity: 1,
|
|
7057
7261
|
};
|
|
7058
7262
|
};
|
|
7263
|
+
const formatSelectionSummary = (attrs, selectedLabels, selectedIds, placeholder) => {
|
|
7264
|
+
const mode = attrs.summaryMode || 'labels';
|
|
7265
|
+
const selectedCount = selectedIds.length;
|
|
7266
|
+
const totalCount = attrs.options.filter((option) => !option.disabled).length;
|
|
7267
|
+
if (mode === 'labels') {
|
|
7268
|
+
return selectedLabels.length > 0 ? selectedLabels.join(', ') : placeholder;
|
|
7269
|
+
}
|
|
7270
|
+
const defaultCountLabel = `${selectedCount}/${totalCount} selected`;
|
|
7271
|
+
const formattedCount = attrs.countLabel ? attrs.countLabel(selectedCount, totalCount) : defaultCountLabel;
|
|
7272
|
+
if (mode === 'count') {
|
|
7273
|
+
return selectedCount > 0 ? formattedCount : placeholder;
|
|
7274
|
+
}
|
|
7275
|
+
if (selectedCount === 0) {
|
|
7276
|
+
return attrs.noneSelectedLabel || placeholder;
|
|
7277
|
+
}
|
|
7278
|
+
if (totalCount > 0 && selectedCount >= totalCount) {
|
|
7279
|
+
return attrs.allSelectedLabel || 'All selected';
|
|
7280
|
+
}
|
|
7281
|
+
return formattedCount;
|
|
7282
|
+
};
|
|
7059
7283
|
const renderDropdownContent = (attrs, selectedIds, multiple, placeholder) => [
|
|
7060
7284
|
placeholder && m('li.disabled', { tabindex: 0 }, m('span', placeholder)),
|
|
7061
7285
|
// Render ungrouped options first
|
|
@@ -7124,17 +7348,6 @@ const Select = () => {
|
|
|
7124
7348
|
const updatePortalDropdown = (attrs, selectedIds, multiple, placeholder) => {
|
|
7125
7349
|
if (!state.isInsideModal)
|
|
7126
7350
|
return;
|
|
7127
|
-
// Clean up existing portal
|
|
7128
|
-
const existingPortal = document.getElementById(state.dropdownId);
|
|
7129
|
-
if (existingPortal) {
|
|
7130
|
-
existingPortal.remove();
|
|
7131
|
-
}
|
|
7132
|
-
if (!state.isOpen || !state.inputRef)
|
|
7133
|
-
return;
|
|
7134
|
-
// Create portal element
|
|
7135
|
-
const portalElement = document.createElement('div');
|
|
7136
|
-
portalElement.id = state.dropdownId;
|
|
7137
|
-
document.body.appendChild(portalElement);
|
|
7138
7351
|
// Create dropdown with proper positioning
|
|
7139
7352
|
const dropdownVnode = m('ul.dropdown-content.select-dropdown', {
|
|
7140
7353
|
tabindex: 0,
|
|
@@ -7146,8 +7359,12 @@ const Select = () => {
|
|
|
7146
7359
|
state.dropdownRef = null;
|
|
7147
7360
|
},
|
|
7148
7361
|
}, renderDropdownContent(attrs, selectedIds, multiple, placeholder));
|
|
7149
|
-
|
|
7150
|
-
|
|
7362
|
+
syncPortalContent({
|
|
7363
|
+
containerId: state.dropdownId,
|
|
7364
|
+
shouldRender: state.isOpen && !!state.inputRef,
|
|
7365
|
+
vnode: dropdownVnode,
|
|
7366
|
+
zIndex: 10000,
|
|
7367
|
+
});
|
|
7151
7368
|
};
|
|
7152
7369
|
return {
|
|
7153
7370
|
oninit: ({ attrs }) => {
|
|
@@ -7180,35 +7397,30 @@ const Select = () => {
|
|
|
7180
7397
|
document.removeEventListener('click', closeDropdown);
|
|
7181
7398
|
// Cleanup portaled dropdown if it exists
|
|
7182
7399
|
if (state.isInsideModal && state.dropdownRef) {
|
|
7183
|
-
|
|
7184
|
-
if (portalElement && portalElement.parentNode) {
|
|
7185
|
-
portalElement.parentNode.removeChild(portalElement);
|
|
7186
|
-
}
|
|
7400
|
+
syncPortalContent({ containerId: state.dropdownId, shouldRender: false, vnode: null });
|
|
7187
7401
|
}
|
|
7188
7402
|
},
|
|
7189
7403
|
view: ({ attrs }) => {
|
|
7190
|
-
var _a;
|
|
7191
7404
|
const controlled = isControlled(attrs);
|
|
7192
|
-
const { newRow, className = 'col s12', key, options = [], multiple = false, label, helperText, placeholder = '', isMandatory, iconName, style, disabled, } = attrs;
|
|
7405
|
+
const { newRow, className = 'col s12', key, options = [], multiple = false, label, helperText, placeholder = '', isMandatory, iconName, appearance = 'standard', style, disabled, } = attrs;
|
|
7193
7406
|
state.isMultiple = multiple;
|
|
7194
7407
|
// Get selected IDs from props or internal state
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
selectedIds = state.internalSelectedIds;
|
|
7208
|
-
}
|
|
7209
|
-
const finalClassName = newRow ? `${className} clear` : className;
|
|
7408
|
+
const selectedIds = resolveControllableValue({
|
|
7409
|
+
controlled,
|
|
7410
|
+
disabled,
|
|
7411
|
+
controlledValue: normalizeSelection(attrs.checkedId),
|
|
7412
|
+
defaultValue: normalizeSelection(attrs.defaultCheckedId),
|
|
7413
|
+
internalValue: state.internalSelectedIds,
|
|
7414
|
+
fallbackValue: [],
|
|
7415
|
+
});
|
|
7416
|
+
const layoutClassName = newRow ? `${className} clear` : className;
|
|
7417
|
+
const appearanceClassName = appearance === 'outlined' ? 'select-appearance-outlined' : '';
|
|
7418
|
+
const finalClassName = [layoutClassName, appearanceClassName].filter(Boolean).join(' ');
|
|
7419
|
+
const shouldInlineLabel = appearance === 'outlined' && !!label;
|
|
7210
7420
|
const selectedOptionsUnsorted = options.filter((opt) => isSelected(opt.id, selectedIds));
|
|
7211
7421
|
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
7422
|
+
const selectedLabels = selectedOptions.map((o) => o.label || o.id.toString());
|
|
7423
|
+
const triggerValue = formatSelectionSummary(attrs, selectedLabels, selectedIds, placeholder);
|
|
7212
7424
|
// Update portal dropdown when inside modal
|
|
7213
7425
|
if (state.isInsideModal) {
|
|
7214
7426
|
updatePortalDropdown(attrs, selectedIds, multiple, placeholder);
|
|
@@ -7228,9 +7440,15 @@ const Select = () => {
|
|
|
7228
7440
|
'aria-controls': state.dropdownId,
|
|
7229
7441
|
role: 'combobox',
|
|
7230
7442
|
}, [
|
|
7443
|
+
shouldInlineLabel &&
|
|
7444
|
+
m('span.select-inline-label', [
|
|
7445
|
+
label,
|
|
7446
|
+
isMandatory && m('span.mandatory', { style: { marginLeft: '2px' } }, '*'),
|
|
7447
|
+
]),
|
|
7231
7448
|
m('input[type=text][readonly=true].select-dropdown.dropdown-trigger', {
|
|
7232
7449
|
id: state.id,
|
|
7233
|
-
value:
|
|
7450
|
+
value: triggerValue,
|
|
7451
|
+
'aria-label': label,
|
|
7234
7452
|
oncreate: ({ dom }) => {
|
|
7235
7453
|
state.inputRef = dom;
|
|
7236
7454
|
},
|
|
@@ -7262,14 +7480,12 @@ const Select = () => {
|
|
|
7262
7480
|
}),
|
|
7263
7481
|
]),
|
|
7264
7482
|
// Label
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
// Helper text
|
|
7272
|
-
helperText && m(HelperText, { helperText }),
|
|
7483
|
+
...renderFieldChrome({
|
|
7484
|
+
label: shouldInlineLabel ? undefined : label,
|
|
7485
|
+
id: state.id,
|
|
7486
|
+
isMandatory,
|
|
7487
|
+
helperText,
|
|
7488
|
+
}),
|
|
7273
7489
|
]);
|
|
7274
7490
|
},
|
|
7275
7491
|
};
|
|
@@ -7488,59 +7704,65 @@ const Tabs = () => {
|
|
|
7488
7704
|
};
|
|
7489
7705
|
};
|
|
7490
7706
|
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7707
|
+
const SelectedChip = ({ option, onRemove, }) => m('.chip', [
|
|
7708
|
+
option.label || option.id.toString(),
|
|
7709
|
+
m(MaterialIcon, {
|
|
7710
|
+
name: 'close',
|
|
7711
|
+
className: 'close',
|
|
7712
|
+
onclick: (e) => {
|
|
7713
|
+
e.stopPropagation();
|
|
7714
|
+
onRemove(option.id);
|
|
7715
|
+
},
|
|
7716
|
+
}),
|
|
7717
|
+
]);
|
|
7718
|
+
const DropdownOption = ({ option, index, optionId, selectedIds, isFocused, onToggle, onMouseOver, showCheckbox, }) => {
|
|
7719
|
+
const optionLabel = option.label || option.id.toString();
|
|
7720
|
+
return m('li', {
|
|
7721
|
+
id: optionId,
|
|
7722
|
+
role: 'option',
|
|
7723
|
+
'aria-selected': selectedIds.includes(option.id) ? 'true' : 'false',
|
|
7724
|
+
class: `${option.disabled ? 'disabled' : ''} ${isFocused ? 'active' : ''}`.trim(),
|
|
7725
|
+
onmouseover: () => {
|
|
7726
|
+
if (!option.disabled) {
|
|
7727
|
+
onMouseOver(index);
|
|
7728
|
+
}
|
|
7729
|
+
},
|
|
7730
|
+
}, m('label', {
|
|
7731
|
+
class: 'search-select-option-label',
|
|
7732
|
+
onclick: (e) => {
|
|
7733
|
+
// A single-select row has no native checkbox to emit change.
|
|
7734
|
+
if (!showCheckbox) {
|
|
7735
|
+
e.preventDefault();
|
|
7736
|
+
onToggle(option);
|
|
7737
|
+
}
|
|
7738
|
+
},
|
|
7739
|
+
}, [
|
|
7740
|
+
showCheckbox &&
|
|
7741
|
+
m('input', {
|
|
7742
|
+
type: 'checkbox',
|
|
7743
|
+
checked: selectedIds.includes(option.id),
|
|
7744
|
+
disabled: option.disabled,
|
|
7745
|
+
onchange: (e) => {
|
|
7516
7746
|
e.stopPropagation();
|
|
7517
7747
|
onToggle(option);
|
|
7518
7748
|
},
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
onMouseOver(index);
|
|
7523
|
-
}
|
|
7524
|
-
},
|
|
7525
|
-
}, m('label', { for: checkboxId, class: 'search-select-option-label' }, [
|
|
7526
|
-
showCheckbox &&
|
|
7527
|
-
m('input', {
|
|
7528
|
-
type: 'checkbox',
|
|
7529
|
-
id: checkboxId,
|
|
7530
|
-
checked: selectedIds.includes(option.id),
|
|
7531
|
-
}),
|
|
7532
|
-
m('span', optionLabel),
|
|
7533
|
-
]));
|
|
7534
|
-
},
|
|
7535
|
-
};
|
|
7749
|
+
}),
|
|
7750
|
+
m('span', optionLabel),
|
|
7751
|
+
]));
|
|
7536
7752
|
};
|
|
7537
7753
|
/**
|
|
7538
7754
|
* Mithril Factory Component for Multi-Select Dropdown with search
|
|
7539
7755
|
*/
|
|
7540
|
-
const SearchSelect = () => {
|
|
7756
|
+
const SearchSelect = (maybeVnode) => {
|
|
7757
|
+
var _a;
|
|
7758
|
+
const cachedInstance = (_a = maybeVnode === null || maybeVnode === void 0 ? void 0 : maybeVnode.state) === null || _a === void 0 ? void 0 : _a.__searchSelectInstance;
|
|
7759
|
+
if (cachedInstance) {
|
|
7760
|
+
return cachedInstance;
|
|
7761
|
+
}
|
|
7541
7762
|
// State initialization
|
|
7542
7763
|
const state = {
|
|
7543
7764
|
id: '',
|
|
7765
|
+
listboxId: '',
|
|
7544
7766
|
isOpen: false,
|
|
7545
7767
|
searchTerm: '',
|
|
7546
7768
|
inputRef: null,
|
|
@@ -7548,17 +7770,38 @@ const SearchSelect = () => {
|
|
|
7548
7770
|
focusedIndex: -1,
|
|
7549
7771
|
internalSelectedIds: [],
|
|
7550
7772
|
createdOptions: [],
|
|
7551
|
-
|
|
7773
|
+
asyncOptions: [],
|
|
7774
|
+
isLoading: false,
|
|
7775
|
+
loadError: null,
|
|
7776
|
+
latestRequestId: 0,
|
|
7777
|
+
};
|
|
7778
|
+
const updateAsyncState = (nextState) => {
|
|
7779
|
+
state.asyncOptions = nextState.options;
|
|
7780
|
+
state.isLoading = nextState.isLoading;
|
|
7781
|
+
state.loadError = nextState.error;
|
|
7782
|
+
state.latestRequestId = nextState.latestRequestId;
|
|
7783
|
+
};
|
|
7784
|
+
const readAsyncState = () => ({
|
|
7785
|
+
options: state.asyncOptions,
|
|
7786
|
+
isLoading: state.isLoading,
|
|
7787
|
+
error: state.loadError,
|
|
7788
|
+
latestRequestId: state.latestRequestId,
|
|
7789
|
+
});
|
|
7552
7790
|
const isControlled = (attrs) => attrs.checkedId !== undefined && typeof attrs.onchange === 'function';
|
|
7553
7791
|
const componentId = uniqueId();
|
|
7554
7792
|
const searchInputId = `${componentId}-search`;
|
|
7555
7793
|
// Handle click outside
|
|
7556
7794
|
const handleClickOutside = (e) => {
|
|
7557
7795
|
const target = e.target;
|
|
7796
|
+
const targetElement = e.target instanceof Element ? e.target : null;
|
|
7558
7797
|
if (state.dropdownRef && state.dropdownRef.contains(target)) {
|
|
7559
7798
|
// Click inside dropdown, do nothing
|
|
7560
7799
|
return;
|
|
7561
7800
|
}
|
|
7801
|
+
if (targetElement && targetElement.closest('.chips-container')) {
|
|
7802
|
+
// Click on trigger, do nothing
|
|
7803
|
+
return;
|
|
7804
|
+
}
|
|
7562
7805
|
if (state.inputRef && state.inputRef.contains(target)) {
|
|
7563
7806
|
// Click on trigger handled by onclick event
|
|
7564
7807
|
return;
|
|
@@ -7569,40 +7812,51 @@ const SearchSelect = () => {
|
|
|
7569
7812
|
}
|
|
7570
7813
|
m.redraw();
|
|
7571
7814
|
};
|
|
7572
|
-
// Handle keyboard navigation
|
|
7573
|
-
const handleKeyDown = (e,
|
|
7574
|
-
|
|
7815
|
+
// Handle keyboard navigation through shared combobox primitive.
|
|
7816
|
+
const handleKeyDown = (e, optionCount, includeActionRow) => {
|
|
7817
|
+
const result = getComboboxKeyResult({
|
|
7818
|
+
key: e.key,
|
|
7819
|
+
isOpen: state.isOpen,
|
|
7820
|
+
focusedIndex: state.focusedIndex,
|
|
7821
|
+
optionCount,
|
|
7822
|
+
includeActionRow,
|
|
7823
|
+
});
|
|
7824
|
+
if (result.preventDefault) {
|
|
7825
|
+
e.preventDefault();
|
|
7826
|
+
}
|
|
7827
|
+
state.isOpen = result.isOpen;
|
|
7828
|
+
state.focusedIndex = result.focusedIndex;
|
|
7829
|
+
return result.action;
|
|
7830
|
+
};
|
|
7831
|
+
const loadAsyncOptions = async (attrs, query) => {
|
|
7832
|
+
if (!attrs.loadOptions) {
|
|
7575
7833
|
return;
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
if (state.focusedIndex >= 0) {
|
|
7589
|
-
if (showAddNew && state.focusedIndex === filteredOptions.length) {
|
|
7590
|
-
// Handle add new option
|
|
7591
|
-
return 'addNew';
|
|
7592
|
-
}
|
|
7593
|
-
else if (state.focusedIndex < filteredOptions.length) {
|
|
7594
|
-
// This will be handled in the view method where attrs are available
|
|
7595
|
-
return 'selectOption';
|
|
7596
|
-
}
|
|
7597
|
-
}
|
|
7598
|
-
break;
|
|
7599
|
-
case 'Escape':
|
|
7600
|
-
e.preventDefault();
|
|
7601
|
-
state.isOpen = false;
|
|
7834
|
+
}
|
|
7835
|
+
const started = startAsyncComboboxRequest(readAsyncState());
|
|
7836
|
+
updateAsyncState(started.nextState);
|
|
7837
|
+
m.redraw();
|
|
7838
|
+
try {
|
|
7839
|
+
const loadedOptions = await attrs.loadOptions(query);
|
|
7840
|
+
const resolved = resolveAsyncComboboxRequest(readAsyncState(), started.requestId, loadedOptions);
|
|
7841
|
+
updateAsyncState(resolved);
|
|
7842
|
+
if (started.requestId !== state.latestRequestId) {
|
|
7843
|
+
return;
|
|
7844
|
+
}
|
|
7845
|
+
if (state.focusedIndex >= loadedOptions.length) {
|
|
7602
7846
|
state.focusedIndex = -1;
|
|
7603
|
-
|
|
7847
|
+
}
|
|
7848
|
+
}
|
|
7849
|
+
catch (error) {
|
|
7850
|
+
const rejected = rejectAsyncComboboxRequest(readAsyncState(), started.requestId, error instanceof Error ? error.message : 'Unable to load options');
|
|
7851
|
+
updateAsyncState(rejected);
|
|
7852
|
+
if (started.requestId !== state.latestRequestId) {
|
|
7853
|
+
return;
|
|
7854
|
+
}
|
|
7855
|
+
state.focusedIndex = -1;
|
|
7856
|
+
}
|
|
7857
|
+
finally {
|
|
7858
|
+
m.redraw();
|
|
7604
7859
|
}
|
|
7605
|
-
return null;
|
|
7606
7860
|
};
|
|
7607
7861
|
// Create new option and add to state
|
|
7608
7862
|
const createAndSelectOption = async (attrs) => {
|
|
@@ -7683,9 +7937,10 @@ const SearchSelect = () => {
|
|
|
7683
7937
|
attrs.onchange(newIds);
|
|
7684
7938
|
}
|
|
7685
7939
|
};
|
|
7686
|
-
|
|
7940
|
+
const componentInstance = {
|
|
7687
7941
|
oninit: ({ attrs }) => {
|
|
7688
7942
|
state.id = attrs.id || uniqueId();
|
|
7943
|
+
state.listboxId = `${state.id}-listbox`;
|
|
7689
7944
|
// Initialize internal state for uncontrolled mode
|
|
7690
7945
|
if (!isControlled(attrs)) {
|
|
7691
7946
|
const defaultIds = attrs.defaultCheckedId !== undefined
|
|
@@ -7712,20 +7967,26 @@ const SearchSelect = () => {
|
|
|
7712
7967
|
: [attrs.checkedId]
|
|
7713
7968
|
: []
|
|
7714
7969
|
: state.internalSelectedIds;
|
|
7715
|
-
const { options = [], oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
|
|
7970
|
+
const { options = [], loadOptions, oncreateNewOption, className, placeholder, searchPlaceholder = 'Search options...', noOptionsFound = 'No options found', label, i18n = {}, maxDisplayedOptions, maxSelectedOptions, maxHeight, } = attrs;
|
|
7716
7971
|
// Use i18n values if provided, otherwise use defaults
|
|
7717
7972
|
const texts = {
|
|
7718
7973
|
noOptionsFound: i18n.noOptionsFound || noOptionsFound,
|
|
7974
|
+
loadingOptions: i18n.loadingOptions || 'Loading options...',
|
|
7975
|
+
loadingError: i18n.loadingError || 'Unable to load options',
|
|
7719
7976
|
addNewPrefix: i18n.addNewPrefix || '+',
|
|
7720
7977
|
showingXofY: i18n.showingXofY || 'Showing {shown} of {total} options',
|
|
7721
7978
|
maxSelectionsReached: i18n.maxSelectionsReached || 'Maximum {max} selections reached',
|
|
7722
7979
|
};
|
|
7723
7980
|
// Check if max selections is reached
|
|
7724
7981
|
const isMaxSelectionsReached = maxSelectedOptions && selectedIds.length >= maxSelectedOptions;
|
|
7725
|
-
//
|
|
7726
|
-
const
|
|
7982
|
+
// In async mode, the active list is sourced remotely.
|
|
7983
|
+
const sourceOptions = loadOptions ? state.asyncOptions : options;
|
|
7984
|
+
// Merge active options with internally created options
|
|
7985
|
+
const allOptions = [...sourceOptions, ...state.createdOptions];
|
|
7986
|
+
// Keep selected label lookups stable across static, async, and created sets.
|
|
7987
|
+
const lookupOptions = [...options, ...state.asyncOptions, ...state.createdOptions];
|
|
7727
7988
|
// Get selected options for display
|
|
7728
|
-
const selectedOptionsUnsorted =
|
|
7989
|
+
const selectedOptionsUnsorted = lookupOptions.filter((opt) => selectedIds.includes(opt.id));
|
|
7729
7990
|
const selectedOptions = sortOptions(selectedOptionsUnsorted, attrs.sortSelected);
|
|
7730
7991
|
// Safely filter options
|
|
7731
7992
|
const filteredOptions = allOptions.filter((option) => (option.label || option.id.toString()).toLowerCase().includes((state.searchTerm || '').toLowerCase()) &&
|
|
@@ -7738,6 +7999,14 @@ const SearchSelect = () => {
|
|
|
7738
7999
|
const showAddNew = oncreateNewOption &&
|
|
7739
8000
|
state.searchTerm &&
|
|
7740
8001
|
!displayedOptions.some((o) => (o.label || o.id.toString()).toLowerCase() === state.searchTerm.toLowerCase());
|
|
8002
|
+
const activeDescendantId = state.isOpen && state.focusedIndex >= 0 && state.focusedIndex < displayedOptions.length
|
|
8003
|
+
? getComboboxOptionId(state.id, state.focusedIndex)
|
|
8004
|
+
: undefined;
|
|
8005
|
+
const viewState = getComboboxViewState({
|
|
8006
|
+
isLoading: state.isLoading,
|
|
8007
|
+
error: state.loadError,
|
|
8008
|
+
optionCount: displayedOptions.length,
|
|
8009
|
+
});
|
|
7741
8010
|
// Render the dropdown
|
|
7742
8011
|
return m('.input-field.multi-select-dropdown', { className }, [
|
|
7743
8012
|
m('.chips.chips-initial.chips-container', {
|
|
@@ -7748,14 +8017,34 @@ const SearchSelect = () => {
|
|
|
7748
8017
|
// console.log('SearchSelect clicked', state.isOpen, e); // Debug log
|
|
7749
8018
|
e.preventDefault();
|
|
7750
8019
|
e.stopPropagation();
|
|
8020
|
+
const wasOpen = state.isOpen;
|
|
7751
8021
|
state.isOpen = !state.isOpen;
|
|
8022
|
+
if (!wasOpen && state.isOpen && loadOptions) {
|
|
8023
|
+
void loadAsyncOptions(attrs, state.searchTerm);
|
|
8024
|
+
}
|
|
7752
8025
|
// console.log('SearchSelect state changed to', state.isOpen); // Debug log
|
|
7753
8026
|
},
|
|
7754
|
-
|
|
8027
|
+
onkeydown: async (e) => {
|
|
8028
|
+
const action = handleKeyDown(e, displayedOptions.length, !!showAddNew);
|
|
8029
|
+
if (action === 'open' && loadOptions) {
|
|
8030
|
+
await loadAsyncOptions(attrs, state.searchTerm);
|
|
8031
|
+
return;
|
|
8032
|
+
}
|
|
8033
|
+
if (action === 'selectAction' && oncreateNewOption) {
|
|
8034
|
+
await createAndSelectOption(attrs);
|
|
8035
|
+
}
|
|
8036
|
+
if (action === 'selectFocused' && state.focusedIndex < displayedOptions.length) {
|
|
8037
|
+
toggleOption(displayedOptions[state.focusedIndex], attrs);
|
|
8038
|
+
}
|
|
8039
|
+
},
|
|
8040
|
+
class: 'chips chips-container mm-layout-row mm-layout-row--wrap mm-layout-row--align-end',
|
|
8041
|
+
role: 'combobox',
|
|
8042
|
+
tabindex: 0,
|
|
8043
|
+
'aria-expanded': state.isOpen ? 'true' : 'false',
|
|
8044
|
+
'aria-haspopup': 'listbox',
|
|
8045
|
+
'aria-controls': state.isOpen ? state.listboxId : undefined,
|
|
8046
|
+
'aria-activedescendant': activeDescendantId,
|
|
7755
8047
|
style: {
|
|
7756
|
-
display: 'flex',
|
|
7757
|
-
alignItems: 'end',
|
|
7758
|
-
flexWrap: 'wrap',
|
|
7759
8048
|
cursor: 'pointer',
|
|
7760
8049
|
position: 'relative',
|
|
7761
8050
|
},
|
|
@@ -7768,30 +8057,41 @@ const SearchSelect = () => {
|
|
|
7768
8057
|
value: selectedOptions.map((o) => o.label || o.id.toString()).join(', '),
|
|
7769
8058
|
readonly: true,
|
|
7770
8059
|
class: 'sr-only',
|
|
7771
|
-
style: {
|
|
8060
|
+
style: {
|
|
8061
|
+
position: 'absolute',
|
|
8062
|
+
width: '1px',
|
|
8063
|
+
height: '1px',
|
|
8064
|
+
margin: '-1px',
|
|
8065
|
+
padding: 0,
|
|
8066
|
+
border: 0,
|
|
8067
|
+
overflow: 'hidden',
|
|
8068
|
+
clip: 'rect(0 0 0 0)',
|
|
8069
|
+
clipPath: 'inset(50%)',
|
|
8070
|
+
whiteSpace: 'nowrap',
|
|
8071
|
+
},
|
|
7772
8072
|
}),
|
|
7773
8073
|
// Selected Options (chips)
|
|
7774
|
-
...selectedOptions.map((option) =>
|
|
7775
|
-
option,
|
|
8074
|
+
...selectedOptions.map((option) => SelectedChip({
|
|
8075
|
+
option: option,
|
|
7776
8076
|
onRemove: (id) => removeOption(id, attrs),
|
|
7777
8077
|
})),
|
|
7778
8078
|
// Placeholder when no options selected
|
|
7779
8079
|
selectedOptions.length === 0 &&
|
|
7780
8080
|
placeholder &&
|
|
7781
8081
|
m('span.placeholder', {
|
|
8082
|
+
class: 'mm-layout-grow',
|
|
7782
8083
|
style: {
|
|
7783
8084
|
color: 'var(--mm-text-hint, #9e9e9e)',
|
|
7784
|
-
flexGrow: 1,
|
|
7785
8085
|
padding: '8px 0',
|
|
7786
8086
|
},
|
|
7787
8087
|
}, placeholder),
|
|
7788
8088
|
// Spacer to push caret to the right
|
|
7789
|
-
m('span.spacer'
|
|
8089
|
+
m('span.spacer.mm-layout-grow'),
|
|
7790
8090
|
m(MaterialIcon, {
|
|
7791
8091
|
name: 'caret',
|
|
7792
8092
|
direction: state.isOpen ? 'up' : 'down',
|
|
7793
|
-
class: 'caret',
|
|
7794
|
-
style: {
|
|
8093
|
+
class: 'caret mm-layout-ml-auto',
|
|
8094
|
+
style: { cursor: 'pointer' },
|
|
7795
8095
|
}),
|
|
7796
8096
|
]),
|
|
7797
8097
|
// Label
|
|
@@ -7803,6 +8103,8 @@ const SearchSelect = () => {
|
|
|
7803
8103
|
// Dropdown Menu
|
|
7804
8104
|
state.isOpen &&
|
|
7805
8105
|
m('ul.dropdown-content.select-dropdown', {
|
|
8106
|
+
id: state.listboxId,
|
|
8107
|
+
role: 'listbox',
|
|
7806
8108
|
oncreate: ({ dom }) => {
|
|
7807
8109
|
state.dropdownRef = dom;
|
|
7808
8110
|
},
|
|
@@ -7827,23 +8129,39 @@ const SearchSelect = () => {
|
|
|
7827
8129
|
oninput: (e) => {
|
|
7828
8130
|
state.searchTerm = e.target.value;
|
|
7829
8131
|
state.focusedIndex = -1; // Reset focus when typing
|
|
8132
|
+
if (loadOptions) {
|
|
8133
|
+
void loadAsyncOptions(attrs, state.searchTerm);
|
|
8134
|
+
}
|
|
7830
8135
|
},
|
|
7831
8136
|
onkeydown: async (e) => {
|
|
7832
|
-
const
|
|
7833
|
-
if (
|
|
8137
|
+
const action = handleKeyDown(e, displayedOptions.length, !!showAddNew);
|
|
8138
|
+
if (action === 'open' && loadOptions) {
|
|
8139
|
+
await loadAsyncOptions(attrs, state.searchTerm);
|
|
8140
|
+
}
|
|
8141
|
+
else if (action === 'selectAction' && oncreateNewOption) {
|
|
7834
8142
|
await createAndSelectOption(attrs);
|
|
7835
8143
|
}
|
|
7836
|
-
else if (
|
|
8144
|
+
else if (action === 'selectFocused' && state.focusedIndex < displayedOptions.length) {
|
|
7837
8145
|
toggleOption(displayedOptions[state.focusedIndex], attrs);
|
|
7838
8146
|
}
|
|
7839
8147
|
},
|
|
7840
8148
|
class: 'search-select-input',
|
|
8149
|
+
'aria-autocomplete': 'list',
|
|
8150
|
+
'aria-controls': state.listboxId,
|
|
7841
8151
|
}),
|
|
7842
8152
|
]),
|
|
7843
|
-
//
|
|
7844
|
-
...(
|
|
7845
|
-
? [m('li.search-select-
|
|
8153
|
+
// Async loading status
|
|
8154
|
+
...(viewState === 'loading'
|
|
8155
|
+
? [m('li.search-select-loading-info', { role: 'status', 'aria-live': 'polite' }, texts.loadingOptions)]
|
|
8156
|
+
: []),
|
|
8157
|
+
// Async loading error
|
|
8158
|
+
...(viewState === 'error' && state.loadError
|
|
8159
|
+
? [
|
|
8160
|
+
m('li.search-select-error-info', { role: 'status', 'aria-live': 'assertive' }, `${texts.loadingError}: ${state.loadError}`),
|
|
8161
|
+
]
|
|
7846
8162
|
: []),
|
|
8163
|
+
// No options found message or list of options
|
|
8164
|
+
...(viewState === 'empty' && !showAddNew ? [m('li.search-select-no-options', texts.noOptionsFound)] : []),
|
|
7847
8165
|
// Truncation message
|
|
7848
8166
|
...(isTruncated
|
|
7849
8167
|
? [
|
|
@@ -7876,6 +8194,9 @@ const SearchSelect = () => {
|
|
|
7876
8194
|
...(showAddNew
|
|
7877
8195
|
? [
|
|
7878
8196
|
m('li', {
|
|
8197
|
+
id: `${state.listboxId}-action`,
|
|
8198
|
+
role: 'option',
|
|
8199
|
+
'aria-selected': 'false',
|
|
7879
8200
|
onclick: async () => {
|
|
7880
8201
|
await createAndSelectOption(attrs);
|
|
7881
8202
|
},
|
|
@@ -7887,9 +8208,10 @@ const SearchSelect = () => {
|
|
|
7887
8208
|
]
|
|
7888
8209
|
: []),
|
|
7889
8210
|
// List of filtered options
|
|
7890
|
-
...displayedOptions.map((option, index) =>
|
|
8211
|
+
...displayedOptions.map((option, index) => DropdownOption({
|
|
7891
8212
|
option,
|
|
7892
8213
|
index,
|
|
8214
|
+
optionId: getComboboxOptionId(state.id, index),
|
|
7893
8215
|
selectedIds,
|
|
7894
8216
|
isFocused: state.focusedIndex === index,
|
|
7895
8217
|
onToggle: (opt) => toggleOption(opt, attrs),
|
|
@@ -7902,6 +8224,10 @@ const SearchSelect = () => {
|
|
|
7902
8224
|
]);
|
|
7903
8225
|
},
|
|
7904
8226
|
};
|
|
8227
|
+
if (maybeVnode === null || maybeVnode === void 0 ? void 0 : maybeVnode.state) {
|
|
8228
|
+
maybeVnode.state.__searchSelectInstance = componentInstance;
|
|
8229
|
+
}
|
|
8230
|
+
return componentInstance;
|
|
7905
8231
|
};
|
|
7906
8232
|
|
|
7907
8233
|
const defaultI18n$2 = {
|
|
@@ -9275,6 +9601,53 @@ const FileUpload = () => {
|
|
|
9275
9601
|
};
|
|
9276
9602
|
};
|
|
9277
9603
|
|
|
9604
|
+
/** A semantic native fieldset for related controls. */
|
|
9605
|
+
const Fieldset = () => ({
|
|
9606
|
+
view: ({ attrs, children }) => {
|
|
9607
|
+
const { legend, description, required, disabled, error, className } = attrs, params = __rest(attrs, ["legend", "description", "required", "disabled", "error", "className"]);
|
|
9608
|
+
const descriptionId = params.id ? `${params.id}-description` : undefined;
|
|
9609
|
+
const errorId = params.id ? `${params.id}-error` : undefined;
|
|
9610
|
+
const describedBy = [description && descriptionId, error && errorId].filter(Boolean).join(' ') || undefined;
|
|
9611
|
+
return m('fieldset.mm-fieldset', Object.assign(Object.assign({}, params), { className, disabled, 'aria-describedby': describedBy }), [
|
|
9612
|
+
m('legend.mm-fieldset__legend', [legend, required && m('span.mm-fieldset__required[aria-hidden=true]', ' *')]),
|
|
9613
|
+
description && m('p.mm-fieldset__description', { id: descriptionId }, description),
|
|
9614
|
+
children,
|
|
9615
|
+
error && m('p.mm-fieldset__error[role=alert]', { id: errorId }, error),
|
|
9616
|
+
]);
|
|
9617
|
+
},
|
|
9618
|
+
});
|
|
9619
|
+
const focusErrorTarget = (fieldId) => {
|
|
9620
|
+
var _a;
|
|
9621
|
+
if (!fieldId || typeof document === 'undefined')
|
|
9622
|
+
return;
|
|
9623
|
+
(_a = document.getElementById(fieldId)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
9624
|
+
};
|
|
9625
|
+
/** A visual form section with an optional accessible validation summary. */
|
|
9626
|
+
const FormSection = () => ({
|
|
9627
|
+
view: ({ attrs, children }) => {
|
|
9628
|
+
const { title, description, errors = [], summaryTitle = 'Please correct the following errors', className } = attrs, params = __rest(attrs, ["title", "description", "errors", "summaryTitle", "className"]);
|
|
9629
|
+
return m('section.mm-form-section', Object.assign(Object.assign({}, params), { className }), [
|
|
9630
|
+
(title || description) &&
|
|
9631
|
+
m('.mm-form-section__header', [
|
|
9632
|
+
title && m('h3.mm-form-section__title', title),
|
|
9633
|
+
description && m('p.mm-form-section__description', description),
|
|
9634
|
+
]),
|
|
9635
|
+
errors.length > 0 &&
|
|
9636
|
+
m('div.mm-validation-summary[role=alert][aria-live=assertive]', [
|
|
9637
|
+
m('p.mm-validation-summary__title', summaryTitle),
|
|
9638
|
+
m('ul.mm-validation-summary__list', errors.map((error, index) => {
|
|
9639
|
+
var _a, _b, _c;
|
|
9640
|
+
const href = (_a = error.href) !== null && _a !== void 0 ? _a : (error.fieldId ? `#${error.fieldId}` : undefined);
|
|
9641
|
+
return m('li', { key: `${(_c = (_b = error.fieldId) !== null && _b !== void 0 ? _b : href) !== null && _c !== void 0 ? _c : 'error'}-${index}` }, href
|
|
9642
|
+
? m('a', { href, onclick: () => focusErrorTarget(error.fieldId) }, error.message)
|
|
9643
|
+
: error.message);
|
|
9644
|
+
})),
|
|
9645
|
+
]),
|
|
9646
|
+
children,
|
|
9647
|
+
]);
|
|
9648
|
+
},
|
|
9649
|
+
});
|
|
9650
|
+
|
|
9278
9651
|
// List of MaterialIcon SVG icons that are available
|
|
9279
9652
|
const materialIconSvgNames = [
|
|
9280
9653
|
'caret',
|
|
@@ -9297,6 +9670,7 @@ const materialIconSvgNames = [
|
|
|
9297
9670
|
const renderIcon = (icon, style) => {
|
|
9298
9671
|
if (!icon)
|
|
9299
9672
|
return null;
|
|
9673
|
+
const objectStyle = typeof style === 'string' ? undefined : style;
|
|
9300
9674
|
if (typeof icon === 'string') {
|
|
9301
9675
|
// Check if this is a MaterialIcon SVG name
|
|
9302
9676
|
if (materialIconSvgNames.includes(icon)) {
|
|
@@ -9313,7 +9687,7 @@ const renderIcon = (icon, style) => {
|
|
|
9313
9687
|
// Image URL
|
|
9314
9688
|
return m('img', {
|
|
9315
9689
|
src: icon.content,
|
|
9316
|
-
style: Object.assign(Object.assign({},
|
|
9690
|
+
style: Object.assign(Object.assign({}, objectStyle), { width: '24px', height: '24px', objectFit: 'contain' }),
|
|
9317
9691
|
});
|
|
9318
9692
|
}
|
|
9319
9693
|
return null;
|
|
@@ -9324,8 +9698,10 @@ const renderIcon = (icon, style) => {
|
|
|
9324
9698
|
const SidenavHeaderFooterItem = () => {
|
|
9325
9699
|
return {
|
|
9326
9700
|
view: ({ attrs }) => {
|
|
9327
|
-
const { text, icon, onclick, href, className = '', _isExpanded = true, _position = 'left' } = attrs;
|
|
9701
|
+
const { text, icon, onclick, href, className = '', title, tooltip, tooltipWhenCollapsedOnly = true, _isExpanded = true, _position = 'left', } = attrs;
|
|
9328
9702
|
const isRightAligned = _position === 'right';
|
|
9703
|
+
const tooltipText = title || tooltip || text;
|
|
9704
|
+
const shouldShowTooltip = tooltipWhenCollapsedOnly ? !_isExpanded : true;
|
|
9329
9705
|
const handleClick = (e) => {
|
|
9330
9706
|
if (onclick) {
|
|
9331
9707
|
e.preventDefault();
|
|
@@ -9334,24 +9710,25 @@ const SidenavHeaderFooterItem = () => {
|
|
|
9334
9710
|
};
|
|
9335
9711
|
const content = isRightAligned
|
|
9336
9712
|
? [
|
|
9337
|
-
_isExpanded &&
|
|
9338
|
-
m('span.sidenav-item-text', { style: { flex: '1', 'text-align': 'left', 'margin-right': '8px' } }, text),
|
|
9713
|
+
_isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-mr-8.left-align', text),
|
|
9339
9714
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9340
9715
|
]
|
|
9341
9716
|
: [
|
|
9342
9717
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9343
|
-
_isExpanded && m('span.sidenav-item-text
|
|
9718
|
+
_isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-ml-8', text),
|
|
9344
9719
|
];
|
|
9345
9720
|
const linkStyle = {
|
|
9346
|
-
display: 'flex',
|
|
9347
|
-
'align-items': 'center',
|
|
9348
9721
|
padding: _isExpanded ? '12px 16px' : '12px 18px',
|
|
9349
9722
|
'justify-content': _isExpanded ? (isRightAligned ? 'flex-end' : 'flex-start') : 'center',
|
|
9350
9723
|
};
|
|
9724
|
+
const linkClass = 'mm-layout-row mm-layout-row--center';
|
|
9351
9725
|
return m('li', { class: className }, m('a', {
|
|
9352
9726
|
href: href || '#!',
|
|
9353
9727
|
onclick: handleClick,
|
|
9728
|
+
class: linkClass,
|
|
9354
9729
|
style: linkStyle,
|
|
9730
|
+
title: shouldShowTooltip ? tooltipText : undefined,
|
|
9731
|
+
'aria-label': shouldShowTooltip ? tooltipText : undefined,
|
|
9355
9732
|
}, content));
|
|
9356
9733
|
},
|
|
9357
9734
|
};
|
|
@@ -9452,6 +9829,7 @@ const Sidenav = () => {
|
|
|
9452
9829
|
fixed ? 'sidenav-fixed' : '',
|
|
9453
9830
|
mode === 'push' ? 'sidenav-push' : '',
|
|
9454
9831
|
expandable && !isExpanded ? 'sidenav-collapsed' : '',
|
|
9832
|
+
'mm-layout-stack',
|
|
9455
9833
|
className,
|
|
9456
9834
|
]
|
|
9457
9835
|
.filter(Boolean)
|
|
@@ -9461,8 +9839,6 @@ const Sidenav = () => {
|
|
|
9461
9839
|
transform: isOpen ? 'translateX(0)' : position === 'left' ? 'translateX(-105%)' : 'translateX(105%)',
|
|
9462
9840
|
'transition-duration': `${animationDuration}ms`,
|
|
9463
9841
|
'transition-property': 'transform, width',
|
|
9464
|
-
display: 'flex',
|
|
9465
|
-
'flex-direction': 'column',
|
|
9466
9842
|
},
|
|
9467
9843
|
}, [
|
|
9468
9844
|
// Header content slot (rendered first, before hamburger)
|
|
@@ -9479,10 +9855,8 @@ const Sidenav = () => {
|
|
|
9479
9855
|
// Hamburger toggle button (inside sidenav, at the top)
|
|
9480
9856
|
showHamburger &&
|
|
9481
9857
|
m('li.sidenav-hamburger-item', {
|
|
9858
|
+
class: `mm-layout-row mm-layout-row--center ${position === 'right' ? 'mm-layout-row--justify-end' : 'mm-layout-row--justify-start'}`,
|
|
9482
9859
|
style: {
|
|
9483
|
-
display: 'flex',
|
|
9484
|
-
'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
|
|
9485
|
-
'align-items': 'center',
|
|
9486
9860
|
padding: '12px 16px',
|
|
9487
9861
|
cursor: 'pointer',
|
|
9488
9862
|
'border-bottom': '1px solid rgba(0,0,0,0.1)',
|
|
@@ -9498,10 +9872,8 @@ const Sidenav = () => {
|
|
|
9498
9872
|
// Expand/collapse toggle button (if expandable, right below hamburger)
|
|
9499
9873
|
expandable &&
|
|
9500
9874
|
m('li.sidenav-expand-toggle', {
|
|
9875
|
+
class: `mm-layout-row mm-layout-row--center ${position === 'right' ? 'mm-layout-row--justify-end' : 'mm-layout-row--justify-start'}`,
|
|
9501
9876
|
style: {
|
|
9502
|
-
display: 'flex',
|
|
9503
|
-
'justify-content': position === 'right' ? 'flex-end' : 'flex-start',
|
|
9504
|
-
'align-items': 'center',
|
|
9505
9877
|
padding: '12px 16px',
|
|
9506
9878
|
cursor: 'pointer',
|
|
9507
9879
|
'border-bottom': '1px solid rgba(0,0,0,0.1)',
|
|
@@ -9574,7 +9946,7 @@ const NavbarSubItem = () => {
|
|
|
9574
9946
|
const submenuContent = isRightAligned
|
|
9575
9947
|
? [
|
|
9576
9948
|
// Right-aligned: text on left, icons on right
|
|
9577
|
-
isExpanded && m('span
|
|
9949
|
+
isExpanded && m('span.mm-layout-grow.left-align', text),
|
|
9578
9950
|
icon && isExpanded && renderIcon(icon, { 'font-size': '18px' }),
|
|
9579
9951
|
indicatorIcon,
|
|
9580
9952
|
]
|
|
@@ -9582,18 +9954,22 @@ const NavbarSubItem = () => {
|
|
|
9582
9954
|
// Left-aligned: indicator on left, text and icon on right
|
|
9583
9955
|
indicatorIcon,
|
|
9584
9956
|
icon && isExpanded && renderIcon(icon, { 'font-size': '18px', 'margin-left': indicatorIcon ? '8px' : '0' }),
|
|
9585
|
-
isExpanded && m('span', {
|
|
9957
|
+
isExpanded && m('span', { class: icon || indicatorIcon ? 'mm-layout-ml-8' : undefined }, text),
|
|
9586
9958
|
];
|
|
9587
9959
|
return m('li.sidenav-subitem', {
|
|
9588
|
-
class:
|
|
9960
|
+
class: [
|
|
9961
|
+
selected ? 'selected' : '',
|
|
9962
|
+
'mm-layout-row',
|
|
9963
|
+
'mm-layout-row--center',
|
|
9964
|
+
'mm-layout-gap-sm',
|
|
9965
|
+
isRightAligned ? 'mm-layout-row--justify-between' : 'mm-layout-row--justify-start',
|
|
9966
|
+
]
|
|
9967
|
+
.filter(Boolean)
|
|
9968
|
+
.join(' ') || undefined,
|
|
9589
9969
|
style: {
|
|
9590
9970
|
padding: isExpanded ? '0 16px 0 48px' : '0 16px',
|
|
9591
9971
|
cursor: 'pointer',
|
|
9592
|
-
display: 'flex',
|
|
9593
|
-
'align-items': 'center',
|
|
9594
|
-
gap: '8px',
|
|
9595
9972
|
'font-size': '0.9em',
|
|
9596
|
-
'justify-content': isRightAligned ? 'space-between' : 'flex-start',
|
|
9597
9973
|
height: '48px',
|
|
9598
9974
|
'min-height': '48px',
|
|
9599
9975
|
},
|
|
@@ -9610,7 +9986,7 @@ const SidenavItem = () => {
|
|
|
9610
9986
|
let isSubmenuOpen = false;
|
|
9611
9987
|
return {
|
|
9612
9988
|
view: ({ attrs, children }) => {
|
|
9613
|
-
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, submenu = [], submenuMode = 'checkbox', } = attrs;
|
|
9989
|
+
const { text, icon, active = false, disabled = false, onclick, href, className = '', divider = false, subheader = false, submenu = [], submenuMode = 'checkbox', title, tooltip, tooltipWhenCollapsedOnly = true, } = attrs;
|
|
9614
9990
|
if (divider) {
|
|
9615
9991
|
return m('li.divider');
|
|
9616
9992
|
}
|
|
@@ -9635,39 +10011,45 @@ const SidenavItem = () => {
|
|
|
9635
10011
|
const isExpanded = attrs._isExpanded !== false;
|
|
9636
10012
|
const position = attrs._position || 'left';
|
|
9637
10013
|
const isRightAligned = position === 'right';
|
|
10014
|
+
const tooltipText = title || tooltip || text;
|
|
10015
|
+
const shouldShowTooltip = tooltipWhenCollapsedOnly ? !isExpanded : true;
|
|
9638
10016
|
// In expanded mode, icons are at the outside edge
|
|
9639
10017
|
// In collapsed mode, icons are centered
|
|
9640
10018
|
const content = isRightAligned
|
|
9641
10019
|
? [
|
|
9642
10020
|
// Right-aligned: text on left, icon on right
|
|
9643
|
-
isExpanded &&
|
|
9644
|
-
m('span.sidenav-item-text', { style: { flex: '1', 'text-align': 'left', 'margin-right': '8px' } }, text || children),
|
|
10021
|
+
isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-mr-8.left-align', text || children),
|
|
9645
10022
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9646
10023
|
]
|
|
9647
10024
|
: [
|
|
9648
10025
|
// Left-aligned: icon on left, text on right
|
|
9649
10026
|
renderIcon(icon, { 'min-width': '24px', width: '24px' }),
|
|
9650
|
-
isExpanded && m('span.sidenav-item-text
|
|
10027
|
+
isExpanded && m('span.sidenav-item-text.mm-layout-grow.mm-layout-ml-8', text || children),
|
|
9651
10028
|
];
|
|
9652
10029
|
const linkStyle = {
|
|
9653
|
-
display: 'flex',
|
|
9654
|
-
'align-items': 'center',
|
|
9655
10030
|
padding: isExpanded ? '12px 16px' : '12px 18px',
|
|
9656
10031
|
'justify-content': isExpanded ? (isRightAligned ? 'flex-end' : 'flex-start') : 'center',
|
|
9657
10032
|
};
|
|
10033
|
+
const linkClass = 'mm-layout-row mm-layout-row--center';
|
|
9658
10034
|
const mainItem = href && !disabled
|
|
9659
10035
|
? m('li', { class: itemClasses }, [
|
|
9660
10036
|
m('a', {
|
|
9661
10037
|
href,
|
|
9662
10038
|
onclick: handleMainClick,
|
|
10039
|
+
class: linkClass,
|
|
9663
10040
|
style: linkStyle,
|
|
10041
|
+
title: shouldShowTooltip ? tooltipText : undefined,
|
|
10042
|
+
'aria-label': shouldShowTooltip ? tooltipText : undefined,
|
|
9664
10043
|
}, content),
|
|
9665
10044
|
])
|
|
9666
10045
|
: m('li', { class: itemClasses }, [
|
|
9667
10046
|
m('a', {
|
|
9668
10047
|
onclick: handleMainClick,
|
|
9669
10048
|
href: '#!',
|
|
10049
|
+
class: linkClass,
|
|
9670
10050
|
style: linkStyle,
|
|
10051
|
+
title: shouldShowTooltip ? tooltipText : undefined,
|
|
10052
|
+
'aria-label': shouldShowTooltip ? tooltipText : undefined,
|
|
9671
10053
|
}, content),
|
|
9672
10054
|
]);
|
|
9673
10055
|
// Return main item with submenu if applicable
|
|
@@ -10183,8 +10565,9 @@ const TreeNodeComponent = () => {
|
|
|
10183
10565
|
const childIsFocused = ((_e = attrs.treeState) === null || _e === void 0 ? void 0 : _e.focusedNodeId) === child.id;
|
|
10184
10566
|
// Calculate if this child is last in branch
|
|
10185
10567
|
const childPath = [...(attrs.currentPath || []), childIndex];
|
|
10186
|
-
const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data)
|
|
10187
|
-
isNodeLastInBranch(childPath, attrs.treeAttrs.data)
|
|
10568
|
+
const childIsLastInBranch = ((_f = attrs.treeAttrs) === null || _f === void 0 ? void 0 : _f.data)
|
|
10569
|
+
? isNodeLastInBranch(childPath, attrs.treeAttrs.data)
|
|
10570
|
+
: false;
|
|
10188
10571
|
return m(TreeNodeComponent, {
|
|
10189
10572
|
key: child.id,
|
|
10190
10573
|
node: child,
|
|
@@ -10317,10 +10700,7 @@ const TreeView = () => {
|
|
|
10317
10700
|
view: ({ attrs }) => {
|
|
10318
10701
|
const { data, className, style, id, selectionMode = 'single', showConnectors = true } = attrs;
|
|
10319
10702
|
return m('div.tree-view', {
|
|
10320
|
-
class: [
|
|
10321
|
-
className,
|
|
10322
|
-
showConnectors && 'show-connectors'
|
|
10323
|
-
].filter(Boolean).join(' ') || undefined,
|
|
10703
|
+
class: [className, showConnectors && 'show-connectors'].filter(Boolean).join(' ') || undefined,
|
|
10324
10704
|
style,
|
|
10325
10705
|
id,
|
|
10326
10706
|
role: selectionMode === 'multiple' ? 'listbox' : 'tree',
|
|
@@ -11018,6 +11398,7 @@ const Rating = () => {
|
|
|
11018
11398
|
};
|
|
11019
11399
|
};
|
|
11020
11400
|
|
|
11401
|
+
const MOBILE_LAYOUT_BREAKPOINT = 600;
|
|
11021
11402
|
/** Create a LikertScale component */
|
|
11022
11403
|
const LikertScale = () => {
|
|
11023
11404
|
const state = {
|
|
@@ -11080,6 +11461,22 @@ const LikertScale = () => {
|
|
|
11080
11461
|
return 'likert-scale--responsive';
|
|
11081
11462
|
}
|
|
11082
11463
|
};
|
|
11464
|
+
const isVerticalLayout = (layout) => {
|
|
11465
|
+
if (layout === 'vertical')
|
|
11466
|
+
return true;
|
|
11467
|
+
if (layout === 'horizontal')
|
|
11468
|
+
return false;
|
|
11469
|
+
return typeof window !== 'undefined' ? window.innerWidth <= MOBILE_LAYOUT_BREAKPOINT : false;
|
|
11470
|
+
};
|
|
11471
|
+
const getInlineAnchorLabel = (value, min, max, middleValue, startLabel, middleLabel, endLabel) => {
|
|
11472
|
+
if (value === min && startLabel)
|
|
11473
|
+
return startLabel;
|
|
11474
|
+
if (middleLabel && middleValue !== undefined && value === middleValue)
|
|
11475
|
+
return middleLabel;
|
|
11476
|
+
if (value === max && endLabel)
|
|
11477
|
+
return endLabel;
|
|
11478
|
+
return undefined;
|
|
11479
|
+
};
|
|
11083
11480
|
const handleChange = (attrs, newValue) => {
|
|
11084
11481
|
var _a;
|
|
11085
11482
|
if (attrs.readonly || attrs.disabled)
|
|
@@ -11126,7 +11523,7 @@ const LikertScale = () => {
|
|
|
11126
11523
|
const LikertScaleItem = () => {
|
|
11127
11524
|
return {
|
|
11128
11525
|
view: ({ attrs }) => {
|
|
11129
|
-
const { value, currentValue, showNumber, showTooltip, tooltipLabel, groupId, name, disabled, readonly, onchange, } = attrs;
|
|
11526
|
+
const { value, currentValue, showNumber, showTooltip, tooltipLabel, groupId, name, disabled, readonly, anchorLabel, onchange, } = attrs;
|
|
11130
11527
|
const radioId = `${groupId}-${value}`;
|
|
11131
11528
|
const isChecked = currentValue === value;
|
|
11132
11529
|
return m('.likert-scale__item.no-select', {
|
|
@@ -11153,6 +11550,7 @@ const LikertScale = () => {
|
|
|
11153
11550
|
m('label.likert-scale__label', {
|
|
11154
11551
|
for: radioId,
|
|
11155
11552
|
}),
|
|
11553
|
+
anchorLabel && m('.likert-scale__item-anchor', anchorLabel),
|
|
11156
11554
|
// Tooltip (optional)
|
|
11157
11555
|
showTooltip && tooltipLabel && m('.likert-scale__tooltip', tooltipLabel),
|
|
11158
11556
|
]);
|
|
@@ -11176,6 +11574,8 @@ const LikertScale = () => {
|
|
|
11176
11574
|
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"]);
|
|
11177
11575
|
const currentValue = getCurrentValue(attrs);
|
|
11178
11576
|
const itemCount = Math.floor((max - min) / step) + 1;
|
|
11577
|
+
const useInlineAnchors = isVerticalLayout(layout);
|
|
11578
|
+
const middleValue = middleLabel ? min + Math.floor((itemCount - 1) / 2) * step : undefined;
|
|
11179
11579
|
// Generate scale values
|
|
11180
11580
|
const scaleValues = Array.from({ length: itemCount }, (_, i) => min + i * step);
|
|
11181
11581
|
return m('.likert-scale', {
|
|
@@ -11224,6 +11624,9 @@ const LikertScale = () => {
|
|
|
11224
11624
|
showNumber: showNumbers,
|
|
11225
11625
|
showTooltip: showTooltips,
|
|
11226
11626
|
tooltipLabel: tooltipLabels === null || tooltipLabels === void 0 ? void 0 : tooltipLabels[value - min],
|
|
11627
|
+
anchorLabel: useInlineAnchors
|
|
11628
|
+
? getInlineAnchorLabel(value, min, max, middleValue, startLabel, middleLabel, endLabel)
|
|
11629
|
+
: undefined,
|
|
11227
11630
|
groupId: state.groupId,
|
|
11228
11631
|
name,
|
|
11229
11632
|
disabled,
|
|
@@ -11231,7 +11634,8 @@ const LikertScale = () => {
|
|
|
11231
11634
|
onchange: (v) => handleChange(attrs, v),
|
|
11232
11635
|
}))),
|
|
11233
11636
|
// Scale anchors
|
|
11234
|
-
|
|
11637
|
+
!useInlineAnchors &&
|
|
11638
|
+
(startLabel || middleLabel || endLabel) &&
|
|
11235
11639
|
m('.likert-scale__anchors', [
|
|
11236
11640
|
startLabel && m('.likert-scale__anchor.likert-scale__anchor--start', startLabel),
|
|
11237
11641
|
middleLabel && m('.likert-scale__anchor.likert-scale__anchor--middle', middleLabel),
|
|
@@ -11416,6 +11820,9 @@ const CircularProgress = () => {
|
|
|
11416
11820
|
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']);
|
|
11417
11821
|
const isDeterminate = mode === 'determinate';
|
|
11418
11822
|
const sizePixels = SIZE_MAP[size];
|
|
11823
|
+
const styleValue = typeof style === 'string'
|
|
11824
|
+
? `width:${sizePixels}px;height:${sizePixels}px;${style}`
|
|
11825
|
+
: Object.assign({ width: `${sizePixels}px`, height: `${sizePixels}px` }, style);
|
|
11419
11826
|
const { radius, circumference, strokeDashoffset, percentage } = isDeterminate
|
|
11420
11827
|
? calculateStrokeProperties(sizePixels, value, max)
|
|
11421
11828
|
: { radius: 0, circumference: 0, strokeDashoffset: 0, percentage: 0 };
|
|
@@ -11442,7 +11849,7 @@ const CircularProgress = () => {
|
|
|
11442
11849
|
: {
|
|
11443
11850
|
'aria-valuetext': ariaValueText || label || 'Loading',
|
|
11444
11851
|
};
|
|
11445
|
-
return m('.circular-progress', Object.assign(Object.assign(Object.assign({}, params), { className: classNames, style:
|
|
11852
|
+
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), [
|
|
11446
11853
|
// SVG circle
|
|
11447
11854
|
m('svg.circular-progress__svg', {
|
|
11448
11855
|
viewBox: `0 0 ${sizePixels} ${sizePixels}`,
|
|
@@ -11507,13 +11914,7 @@ const LinearProgress = () => {
|
|
|
11507
11914
|
// Determine label content
|
|
11508
11915
|
const labelContent = label !== undefined ? label : showPercentage && isDeterminate ? `${Math.round(percentage)}%` : '';
|
|
11509
11916
|
// Build class names
|
|
11510
|
-
const classNames = [
|
|
11511
|
-
'linear-progress',
|
|
11512
|
-
getColorClass(color, colorIntensity),
|
|
11513
|
-
className,
|
|
11514
|
-
]
|
|
11515
|
-
.filter(Boolean)
|
|
11516
|
-
.join(' ');
|
|
11917
|
+
const classNames = ['linear-progress', getColorClass(color, colorIntensity), className].filter(Boolean).join(' ');
|
|
11517
11918
|
// ARIA attributes
|
|
11518
11919
|
const ariaAttrs = isDeterminate
|
|
11519
11920
|
? {
|
|
@@ -11566,4 +11967,5 @@ const isValidationError = (result) => !isValidationSuccess(result);
|
|
|
11566
11967
|
// ============================================================================
|
|
11567
11968
|
// All types are already exported via individual export declarations above
|
|
11568
11969
|
|
|
11569
|
-
export { AnalogClock, AnchorItem, Autocomplete, Badge, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CircularProgress, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, ConfirmButton, DataTable, DatePicker, DigitalClock, DoubleRangeSlider, Dropdown, EmailInput, FileInput, FileUpload, FlatButton, FloatingActionButton, HelperText, Icon, IconButton, ImageList, InputCheckbox, Label, LargeButton, LikertScale, LinearProgress, ListItem, Mandatory, Masonry, MaterialBox, MaterialIcon, ModalPanel, NumberInput, Options, OptionsList, Pagination, PaginationControls, Parallax, PasswordInput, Pushpin, PushpinComponent, RadioButton, RadioButtons, RangeInput, Rating, RoundIconButton, SearchSelect, SecondaryContent, Select, Sidenav, SidenavItem, SidenavManager, SingleRangeSlider, SmallButton, Stepper, SubmitButton, Switch, Tabs, TextArea, TextInput, ThemeManager, ThemeSwitcher, ThemeToggle, TimePicker, TimeRangePicker, Timeline, Toast, ToastComponent, ToggleGroup, Tooltip, TooltipComponent, TreeView, UrlInput, Wizard, addLeadingZero, clearPortal, createBreadcrumb, formatTime, generateHourOptions, generateMinuteOptions, getDropdownStyles, getPortalContainer, initPushpins, initTooltips, isNumeric, isTimeDisabled, isValidationError, isValidationSuccess, padLeft, parseTime, range, releasePortalContainer, renderToPortal, scrollToValue, snapToNearestItem, sortOptions, timeToMinutes, toast, uniqueId, uuid4 };
|
|
11970
|
+
export { AnalogClock, AnchorItem, Autocomplete, Badge, Breadcrumb, BreadcrumbManager, Button, ButtonFactory, Carousel, CharacterCounter, Chips, CircularProgress, CodeBlock, Collapsible, CollapsibleItem, Collection, CollectionMode, ColorInput, ConfirmButton, DataTable, DatePicker, DigitalClock, DoubleRangeSlider, Dropdown, EmailInput, Fieldset, FileInput, FileUpload, FlatButton, FloatingActionButton, FormSection, HelperText, Icon, IconButton, ImageList, InputCheckbox, Label, LargeButton, LikertScale, LinearProgress, ListItem, Mandatory, Masonry, MaterialBox, MaterialIcon, ModalPanel, NumberInput, Options, OptionsList, Pagination, PaginationControls, Parallax, PasswordInput, Pushpin, PushpinComponent, RadioButton, RadioButtons, RangeInput, Rating, RoundIconButton, SearchSelect, SecondaryContent, Select, Sidenav, SidenavItem, SidenavManager, SingleRangeSlider, SmallButton, Stepper, SubmitButton, Switch, Tabs, TextArea, TextInput, ThemeManager, ThemeSwitcher, ThemeToggle, TimePicker, TimeRangePicker, Timeline, Toast, ToastComponent, ToggleButton, ToggleGroup, Tooltip, TooltipComponent, TreeView, UrlInput, Wizard, addLeadingZero, clearPortal, createAsyncComboboxState, createBreadcrumb, formatTime, generateHourOptions, generateMinuteOptions, getComboboxKeyResult, getComboboxOptionId, getComboboxViewState, getDropdownStyles, getPortalContainer, initPushpins, initTooltips, isNumeric, isTimeDisabled, isValidationError, isValidationSuccess, normalizeSelection, padLeft, parseTime, range, rejectAsyncComboboxRequest, releasePortalContainer, renderFieldChrome, renderToPortal, resolveAsyncComboboxRequest, resolveControllableValue, scrollToValue, snapToNearestItem, sortOptions, startAsyncComboboxRequest, syncPortalContent, timeToMinutes, toast, uniqueId, uuid4 };
|
|
11971
|
+
//# sourceMappingURL=index.esm.js.map
|