mithril-materialized 3.5.5 → 3.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +299 -118
- package/dist/index.js +302 -117
- package/dist/index.umd.js +302 -117
- package/dist/modal.d.ts +4 -2
- package/dist/timepicker.d.ts +1 -0
- package/dist/utils.d.ts +32 -0
- package/package.json +12 -13
package/dist/index.umd.js
CHANGED
|
@@ -163,6 +163,76 @@
|
|
|
163
163
|
* @example: console.log(range(5, 10)); // [5, 6, 7, 8, 9, 10]
|
|
164
164
|
*/
|
|
165
165
|
const range = (a, b) => Array.from({ length: b - a + 1 }, (_, i) => a + i);
|
|
166
|
+
// Global registry for portal containers
|
|
167
|
+
const portalContainers = new Map();
|
|
168
|
+
/**
|
|
169
|
+
* Creates or retrieves a portal container appended to document.body.
|
|
170
|
+
* Uses reference counting to manage container lifecycle.
|
|
171
|
+
*
|
|
172
|
+
* @param id - Unique identifier for the portal container
|
|
173
|
+
* @param zIndex - Z-index for the portal container (default: 1004, above modals at 1003)
|
|
174
|
+
* @returns The portal container element
|
|
175
|
+
*/
|
|
176
|
+
const getPortalContainer = (id, zIndex = 1004) => {
|
|
177
|
+
let container = portalContainers.get(id);
|
|
178
|
+
if (!container) {
|
|
179
|
+
const element = document.createElement('div');
|
|
180
|
+
element.id = id;
|
|
181
|
+
element.style.position = 'fixed';
|
|
182
|
+
element.style.top = '0';
|
|
183
|
+
element.style.left = '0';
|
|
184
|
+
element.style.width = '100%';
|
|
185
|
+
element.style.height = '100%';
|
|
186
|
+
element.style.pointerEvents = 'none'; // Allow clicks through to underlying elements
|
|
187
|
+
element.style.zIndex = zIndex.toString();
|
|
188
|
+
document.body.appendChild(element);
|
|
189
|
+
container = { element, refCount: 0 };
|
|
190
|
+
portalContainers.set(id, container);
|
|
191
|
+
}
|
|
192
|
+
container.refCount++;
|
|
193
|
+
return container.element;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Decrements reference count and removes portal container if no longer needed.
|
|
197
|
+
*
|
|
198
|
+
* @param id - Portal container identifier
|
|
199
|
+
*/
|
|
200
|
+
const releasePortalContainer = (id) => {
|
|
201
|
+
const container = portalContainers.get(id);
|
|
202
|
+
if (container) {
|
|
203
|
+
container.refCount--;
|
|
204
|
+
if (container.refCount <= 0) {
|
|
205
|
+
container.element.remove();
|
|
206
|
+
portalContainers.delete(id);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Renders a Mithril vnode into a portal container using m.render().
|
|
212
|
+
* This allows components to render outside their parent DOM hierarchy,
|
|
213
|
+
* useful for modals and pickers that need to escape stacking contexts.
|
|
214
|
+
*
|
|
215
|
+
* @param containerId - Portal container identifier
|
|
216
|
+
* @param vnode - Mithril vnode to render
|
|
217
|
+
* @param zIndex - Z-index for portal container (default: 1004)
|
|
218
|
+
*/
|
|
219
|
+
const renderToPortal = (containerId, vnode, zIndex = 1004) => {
|
|
220
|
+
const container = getPortalContainer(containerId, zIndex);
|
|
221
|
+
m.render(container, vnode);
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Clears portal content and releases container reference.
|
|
225
|
+
* If this is the last reference, the container will be removed from the DOM.
|
|
226
|
+
*
|
|
227
|
+
* @param containerId - Portal container identifier
|
|
228
|
+
*/
|
|
229
|
+
const clearPortal = (containerId) => {
|
|
230
|
+
const container = portalContainers.get(containerId);
|
|
231
|
+
if (container) {
|
|
232
|
+
m.render(container.element, null);
|
|
233
|
+
releasePortalContainer(containerId);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
166
236
|
|
|
167
237
|
// import './styles/input.css';
|
|
168
238
|
const Mandatory = { view: ({ attrs }) => m('span.mandatory', Object.assign({}, attrs), '*') };
|
|
@@ -2191,6 +2261,129 @@
|
|
|
2191
2261
|
}
|
|
2192
2262
|
m.redraw();
|
|
2193
2263
|
};
|
|
2264
|
+
const handleKeyDown = (e) => {
|
|
2265
|
+
if (e.key === 'Escape' && state.isOpen) {
|
|
2266
|
+
state.isOpen = false;
|
|
2267
|
+
const options = mergeOptions({});
|
|
2268
|
+
if (options.onClose)
|
|
2269
|
+
options.onClose();
|
|
2270
|
+
clearPortal(state.portalContainerId);
|
|
2271
|
+
m.redraw();
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
const renderPickerToPortal = (attrs) => {
|
|
2275
|
+
const options = mergeOptions(attrs);
|
|
2276
|
+
const pickerModal = m('.datepicker-modal-wrapper', {
|
|
2277
|
+
style: {
|
|
2278
|
+
position: 'fixed',
|
|
2279
|
+
top: '0',
|
|
2280
|
+
left: '0',
|
|
2281
|
+
width: '100%',
|
|
2282
|
+
height: '100%',
|
|
2283
|
+
pointerEvents: 'auto',
|
|
2284
|
+
display: 'flex',
|
|
2285
|
+
alignItems: 'center',
|
|
2286
|
+
justifyContent: 'center',
|
|
2287
|
+
},
|
|
2288
|
+
}, [
|
|
2289
|
+
// Modal overlay
|
|
2290
|
+
m('.modal-overlay', {
|
|
2291
|
+
style: {
|
|
2292
|
+
position: 'absolute',
|
|
2293
|
+
top: '0',
|
|
2294
|
+
left: '0',
|
|
2295
|
+
width: '100%',
|
|
2296
|
+
height: '100%',
|
|
2297
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
2298
|
+
zIndex: '1002',
|
|
2299
|
+
},
|
|
2300
|
+
onclick: () => {
|
|
2301
|
+
state.isOpen = false;
|
|
2302
|
+
if (options.onClose)
|
|
2303
|
+
options.onClose();
|
|
2304
|
+
m.redraw();
|
|
2305
|
+
},
|
|
2306
|
+
}),
|
|
2307
|
+
// Modal content
|
|
2308
|
+
m('.modal.datepicker-modal.open', {
|
|
2309
|
+
id: `modal-${state.id}`,
|
|
2310
|
+
tabindex: 0,
|
|
2311
|
+
style: {
|
|
2312
|
+
position: 'relative',
|
|
2313
|
+
zIndex: '1003',
|
|
2314
|
+
display: 'block',
|
|
2315
|
+
opacity: 1,
|
|
2316
|
+
top: 'auto',
|
|
2317
|
+
transform: 'scaleX(1) scaleY(1)',
|
|
2318
|
+
margin: '0 auto',
|
|
2319
|
+
},
|
|
2320
|
+
}, [
|
|
2321
|
+
m('.modal-content.datepicker-container', {
|
|
2322
|
+
onclick: (e) => {
|
|
2323
|
+
const target = e.target;
|
|
2324
|
+
if (!target.closest('.select-wrapper') && !target.closest('.dropdown-content')) {
|
|
2325
|
+
state.monthDropdownOpen = false;
|
|
2326
|
+
state.yearDropdownOpen = false;
|
|
2327
|
+
}
|
|
2328
|
+
},
|
|
2329
|
+
}, [
|
|
2330
|
+
m(DateDisplay, { options }),
|
|
2331
|
+
m('.datepicker-calendar-container', [
|
|
2332
|
+
m('.datepicker-calendar', [
|
|
2333
|
+
m(DateControls, {
|
|
2334
|
+
options,
|
|
2335
|
+
randId: `datepicker-title-${Math.random().toString(36).slice(2)}`,
|
|
2336
|
+
}),
|
|
2337
|
+
m(Calendar, {
|
|
2338
|
+
year: state.calendars[0].year,
|
|
2339
|
+
month: state.calendars[0].month,
|
|
2340
|
+
options,
|
|
2341
|
+
}),
|
|
2342
|
+
]),
|
|
2343
|
+
m('.datepicker-footer', [
|
|
2344
|
+
options.showClearBtn &&
|
|
2345
|
+
m('button.btn-flat.datepicker-clear.waves-effect', {
|
|
2346
|
+
type: 'button',
|
|
2347
|
+
onclick: () => {
|
|
2348
|
+
setDate(null, false, options);
|
|
2349
|
+
state.isOpen = false;
|
|
2350
|
+
},
|
|
2351
|
+
}, options.i18n.clear),
|
|
2352
|
+
m('button.btn-flat.datepicker-cancel.waves-effect', {
|
|
2353
|
+
type: 'button',
|
|
2354
|
+
onclick: () => {
|
|
2355
|
+
state.isOpen = false;
|
|
2356
|
+
if (options.onClose)
|
|
2357
|
+
options.onClose();
|
|
2358
|
+
},
|
|
2359
|
+
}, options.i18n.cancel),
|
|
2360
|
+
m('button.btn-flat.datepicker-done.waves-effect', {
|
|
2361
|
+
type: 'button',
|
|
2362
|
+
onclick: () => {
|
|
2363
|
+
state.isOpen = false;
|
|
2364
|
+
if (options.dateRange) {
|
|
2365
|
+
if (state.startDate && state.endDate && attrs.onchange) {
|
|
2366
|
+
const startStr = formatDate(state.startDate, 'yyyy-mm-dd', options);
|
|
2367
|
+
const endStr = formatDate(state.endDate, 'yyyy-mm-dd', options);
|
|
2368
|
+
attrs.onchange(`${startStr} - ${endStr}`);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
else {
|
|
2372
|
+
if (state.date && attrs.onchange) {
|
|
2373
|
+
attrs.onchange(toString(state.date, 'yyyy-mm-dd'));
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
if (options.onClose)
|
|
2377
|
+
options.onClose();
|
|
2378
|
+
},
|
|
2379
|
+
}, options.i18n.done),
|
|
2380
|
+
]),
|
|
2381
|
+
]),
|
|
2382
|
+
]),
|
|
2383
|
+
]),
|
|
2384
|
+
]);
|
|
2385
|
+
renderToPortal(state.portalContainerId, pickerModal, 1004);
|
|
2386
|
+
};
|
|
2194
2387
|
return {
|
|
2195
2388
|
oninit: (vnode) => {
|
|
2196
2389
|
const attrs = vnode.attrs;
|
|
@@ -2206,6 +2399,7 @@
|
|
|
2206
2399
|
calendars: [{ month: 0, year: 0 }],
|
|
2207
2400
|
monthDropdownOpen: false,
|
|
2208
2401
|
yearDropdownOpen: false,
|
|
2402
|
+
portalContainerId: `datepicker-portal-${uniqueId()}`,
|
|
2209
2403
|
formats: {
|
|
2210
2404
|
d: () => { var _a; return ((_a = state.date) === null || _a === void 0 ? void 0 : _a.getDate()) || 0; },
|
|
2211
2405
|
dd: () => {
|
|
@@ -2259,10 +2453,26 @@
|
|
|
2259
2453
|
}
|
|
2260
2454
|
// Add document click listener to close dropdowns
|
|
2261
2455
|
document.addEventListener('click', handleDocumentClick);
|
|
2456
|
+
// Add ESC key listener
|
|
2457
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
2262
2458
|
},
|
|
2263
2459
|
onremove: () => {
|
|
2264
|
-
// Clean up event
|
|
2460
|
+
// Clean up event listeners
|
|
2265
2461
|
document.removeEventListener('click', handleDocumentClick);
|
|
2462
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
2463
|
+
// Clean up portal if picker was open
|
|
2464
|
+
if (state.isOpen) {
|
|
2465
|
+
clearPortal(state.portalContainerId);
|
|
2466
|
+
}
|
|
2467
|
+
},
|
|
2468
|
+
onupdate: (vnode) => {
|
|
2469
|
+
// Render to portal when picker is open, clear when closed
|
|
2470
|
+
if (state.isOpen) {
|
|
2471
|
+
renderPickerToPortal(vnode.attrs);
|
|
2472
|
+
}
|
|
2473
|
+
else {
|
|
2474
|
+
clearPortal(state.portalContainerId);
|
|
2475
|
+
}
|
|
2266
2476
|
},
|
|
2267
2477
|
view: (vnode) => {
|
|
2268
2478
|
const attrs = vnode.attrs;
|
|
@@ -2378,93 +2588,7 @@
|
|
|
2378
2588
|
}, label || dateLabel),
|
|
2379
2589
|
// Helper text
|
|
2380
2590
|
helperText && m('span.helper-text', helperText),
|
|
2381
|
-
// Modal
|
|
2382
|
-
state.isOpen && [
|
|
2383
|
-
m('.modal.datepicker-modal.open', {
|
|
2384
|
-
id: `modal-${state.id}`,
|
|
2385
|
-
tabindex: 0,
|
|
2386
|
-
style: {
|
|
2387
|
-
zIndex: 1003,
|
|
2388
|
-
display: 'block',
|
|
2389
|
-
opacity: 1,
|
|
2390
|
-
top: '10%',
|
|
2391
|
-
transform: 'scaleX(1) scaleY(1)',
|
|
2392
|
-
},
|
|
2393
|
-
}, [
|
|
2394
|
-
m('.modal-content.datepicker-container', {
|
|
2395
|
-
onclick: (e) => {
|
|
2396
|
-
// Close dropdowns when clicking anywhere in the modal content
|
|
2397
|
-
const target = e.target;
|
|
2398
|
-
if (!target.closest('.select-wrapper') && !target.closest('.dropdown-content')) {
|
|
2399
|
-
state.monthDropdownOpen = false;
|
|
2400
|
-
state.yearDropdownOpen = false;
|
|
2401
|
-
}
|
|
2402
|
-
},
|
|
2403
|
-
}, [
|
|
2404
|
-
m(DateDisplay, { options }),
|
|
2405
|
-
m('.datepicker-calendar-container', [
|
|
2406
|
-
m('.datepicker-calendar', [
|
|
2407
|
-
m(DateControls, { options, randId: `datepicker-title-${Math.random().toString(36).slice(2)}` }),
|
|
2408
|
-
m(Calendar, { year: state.calendars[0].year, month: state.calendars[0].month, options }),
|
|
2409
|
-
]),
|
|
2410
|
-
m('.datepicker-footer', [
|
|
2411
|
-
options.showClearBtn &&
|
|
2412
|
-
m('button.btn-flat.datepicker-clear.waves-effect', {
|
|
2413
|
-
type: 'button',
|
|
2414
|
-
style: '',
|
|
2415
|
-
onclick: () => {
|
|
2416
|
-
setDate(null, false, options);
|
|
2417
|
-
state.isOpen = false;
|
|
2418
|
-
},
|
|
2419
|
-
}, options.i18n.clear),
|
|
2420
|
-
m('button.btn-flat.datepicker-cancel.waves-effect', {
|
|
2421
|
-
type: 'button',
|
|
2422
|
-
onclick: () => {
|
|
2423
|
-
state.isOpen = false;
|
|
2424
|
-
if (options.onClose)
|
|
2425
|
-
options.onClose();
|
|
2426
|
-
},
|
|
2427
|
-
}, options.i18n.cancel),
|
|
2428
|
-
m('button.btn-flat.datepicker-done.waves-effect', {
|
|
2429
|
-
type: 'button',
|
|
2430
|
-
onclick: () => {
|
|
2431
|
-
state.isOpen = false;
|
|
2432
|
-
if (options.dateRange) {
|
|
2433
|
-
// Range mode
|
|
2434
|
-
if (state.startDate && state.endDate && onchange) {
|
|
2435
|
-
const startStr = toString(state.startDate, 'yyyy-mm-dd');
|
|
2436
|
-
const endStr = toString(state.endDate, 'yyyy-mm-dd');
|
|
2437
|
-
onchange(`${startStr} - ${endStr}`);
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
else {
|
|
2441
|
-
// Single date mode
|
|
2442
|
-
if (state.date && onchange) {
|
|
2443
|
-
onchange(toString(state.date, 'yyyy-mm-dd')); // Always return ISO format
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
if (options.onClose)
|
|
2447
|
-
options.onClose();
|
|
2448
|
-
},
|
|
2449
|
-
}, options.i18n.done),
|
|
2450
|
-
]),
|
|
2451
|
-
]),
|
|
2452
|
-
]),
|
|
2453
|
-
]),
|
|
2454
|
-
// Modal overlay
|
|
2455
|
-
m('.modal-overlay', {
|
|
2456
|
-
style: {
|
|
2457
|
-
zIndex: 1002,
|
|
2458
|
-
display: 'block',
|
|
2459
|
-
opacity: 0.5,
|
|
2460
|
-
},
|
|
2461
|
-
onclick: () => {
|
|
2462
|
-
state.isOpen = false;
|
|
2463
|
-
if (options.onClose)
|
|
2464
|
-
options.onClose();
|
|
2465
|
-
},
|
|
2466
|
-
}),
|
|
2467
|
-
],
|
|
2591
|
+
// Modal is now rendered via portal in onupdate hook
|
|
2468
2592
|
]);
|
|
2469
2593
|
},
|
|
2470
2594
|
};
|
|
@@ -3304,8 +3428,8 @@
|
|
|
3304
3428
|
const shouldValidate = !isNonInteractive && (validate || type === 'email' || type === 'url' || isNumeric);
|
|
3305
3429
|
return m('.input-field', { className: cn, style }, [
|
|
3306
3430
|
iconName ? m('i.material-icons.prefix', iconName) : undefined,
|
|
3307
|
-
m('input', Object.assign(Object.assign({ class: shouldValidate ? 'validate' : undefined }, params), { type, tabindex: 0, id,
|
|
3308
|
-
placeholder, value: controlled ? value : undefined,
|
|
3431
|
+
m('input', Object.assign(Object.assign({ class: type === 'range' && attrs.vertical ? 'range-slider vertical' : shouldValidate ? 'validate' : undefined }, params), { type, tabindex: 0, id,
|
|
3432
|
+
placeholder, value: controlled ? value : undefined, style: type === 'range' && attrs.vertical
|
|
3309
3433
|
? {
|
|
3310
3434
|
height: attrs.height || '200px',
|
|
3311
3435
|
width: '6px',
|
|
@@ -4898,7 +5022,7 @@
|
|
|
4898
5022
|
closeModal(attrs);
|
|
4899
5023
|
}
|
|
4900
5024
|
}
|
|
4901
|
-
const { id, title, description, fixedFooter, bottomSheet, buttons, richContent, className, showCloseButton = true, closeOnBackdropClick = true, } = attrs;
|
|
5025
|
+
const { id, title, description, fixedFooter, bottomSheet, buttons, richContent, className, showCloseButton = true, closeOnBackdropClick = true, closeOnButtonClick = true, } = attrs;
|
|
4902
5026
|
const modalClasses = [
|
|
4903
5027
|
'modal',
|
|
4904
5028
|
className || '',
|
|
@@ -4995,7 +5119,7 @@
|
|
|
4995
5119
|
}, buttons.map((buttonProps) => m(FlatButton, Object.assign(Object.assign({}, buttonProps), { className: `modal-close ${buttonProps.className || ''}`, onclick: (e) => {
|
|
4996
5120
|
if (buttonProps.onclick)
|
|
4997
5121
|
buttonProps.onclick(e);
|
|
4998
|
-
closeModal(attrs);
|
|
5122
|
+
closeOnButtonClick && closeModal(attrs);
|
|
4999
5123
|
} })))),
|
|
5000
5124
|
]),
|
|
5001
5125
|
]);
|
|
@@ -5173,6 +5297,7 @@
|
|
|
5173
5297
|
autoClose: false,
|
|
5174
5298
|
twelveHour: true,
|
|
5175
5299
|
vibrate: true,
|
|
5300
|
+
roundBy5: false,
|
|
5176
5301
|
onOpen: () => { },
|
|
5177
5302
|
onOpenStart: () => { },
|
|
5178
5303
|
onOpenEnd: () => { },
|
|
@@ -5224,7 +5349,7 @@
|
|
|
5224
5349
|
const clickPos = getPos(e);
|
|
5225
5350
|
state.dx = clickPos.x - state.x0;
|
|
5226
5351
|
state.dy = clickPos.y - state.y0;
|
|
5227
|
-
setHand(state.dx, state.dy,
|
|
5352
|
+
setHand(state.dx, state.dy, options.roundBy5);
|
|
5228
5353
|
document.addEventListener('mousemove', handleDocumentClickMove);
|
|
5229
5354
|
document.addEventListener('touchmove', handleDocumentClickMove);
|
|
5230
5355
|
document.addEventListener('mouseup', handleDocumentClickEnd);
|
|
@@ -5236,7 +5361,7 @@
|
|
|
5236
5361
|
const x = clickPos.x - state.x0;
|
|
5237
5362
|
const y = clickPos.y - state.y0;
|
|
5238
5363
|
state.moved = true;
|
|
5239
|
-
setHand(x, y,
|
|
5364
|
+
setHand(x, y, options.roundBy5);
|
|
5240
5365
|
m.redraw();
|
|
5241
5366
|
};
|
|
5242
5367
|
const handleDocumentClickEnd = (e) => {
|
|
@@ -5672,6 +5797,66 @@
|
|
|
5672
5797
|
},
|
|
5673
5798
|
};
|
|
5674
5799
|
};
|
|
5800
|
+
const handleKeyDown = (e) => {
|
|
5801
|
+
if (e.key === 'Escape' && state.isOpen) {
|
|
5802
|
+
close();
|
|
5803
|
+
clearPortal(state.portalContainerId);
|
|
5804
|
+
m.redraw();
|
|
5805
|
+
}
|
|
5806
|
+
};
|
|
5807
|
+
const renderPickerToPortal = (attrs) => {
|
|
5808
|
+
const { showClearBtn = false, clearLabel = 'Clear', closeLabel = 'Cancel' } = attrs;
|
|
5809
|
+
const pickerModal = m('.timepicker-modal-wrapper', {
|
|
5810
|
+
style: {
|
|
5811
|
+
position: 'fixed',
|
|
5812
|
+
top: '0',
|
|
5813
|
+
left: '0',
|
|
5814
|
+
width: '100%',
|
|
5815
|
+
height: '100%',
|
|
5816
|
+
pointerEvents: 'auto',
|
|
5817
|
+
display: 'flex',
|
|
5818
|
+
alignItems: 'center',
|
|
5819
|
+
justifyContent: 'center',
|
|
5820
|
+
},
|
|
5821
|
+
}, [
|
|
5822
|
+
// Modal overlay
|
|
5823
|
+
m('.modal-overlay', {
|
|
5824
|
+
style: {
|
|
5825
|
+
position: 'absolute',
|
|
5826
|
+
top: '0',
|
|
5827
|
+
left: '0',
|
|
5828
|
+
width: '100%',
|
|
5829
|
+
height: '100%',
|
|
5830
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
5831
|
+
zIndex: '1002',
|
|
5832
|
+
},
|
|
5833
|
+
onclick: () => {
|
|
5834
|
+
close();
|
|
5835
|
+
m.redraw();
|
|
5836
|
+
},
|
|
5837
|
+
}),
|
|
5838
|
+
// Modal content
|
|
5839
|
+
m('.modal.timepicker-modal.open', {
|
|
5840
|
+
style: {
|
|
5841
|
+
position: 'relative',
|
|
5842
|
+
zIndex: '1003',
|
|
5843
|
+
display: 'block',
|
|
5844
|
+
opacity: 1,
|
|
5845
|
+
top: 'auto',
|
|
5846
|
+
transform: 'scaleX(1) scaleY(1)',
|
|
5847
|
+
margin: '0 auto',
|
|
5848
|
+
},
|
|
5849
|
+
}, [
|
|
5850
|
+
m(TimepickerModal, {
|
|
5851
|
+
showClearBtn,
|
|
5852
|
+
clearLabel,
|
|
5853
|
+
closeLabel,
|
|
5854
|
+
doneLabel: 'OK',
|
|
5855
|
+
}),
|
|
5856
|
+
]),
|
|
5857
|
+
]);
|
|
5858
|
+
renderToPortal(state.portalContainerId, pickerModal, 1004);
|
|
5859
|
+
};
|
|
5675
5860
|
return {
|
|
5676
5861
|
oninit: (vnode) => {
|
|
5677
5862
|
const attrs = vnode.attrs;
|
|
@@ -5688,11 +5873,14 @@
|
|
|
5688
5873
|
y0: 0,
|
|
5689
5874
|
dx: 0,
|
|
5690
5875
|
dy: 0,
|
|
5876
|
+
portalContainerId: `timepicker-portal-${uniqueId()}`,
|
|
5691
5877
|
};
|
|
5692
5878
|
// Handle value after options are set
|
|
5693
5879
|
if (attrs.defaultValue) {
|
|
5694
5880
|
updateTimeFromInput(attrs.defaultValue);
|
|
5695
5881
|
}
|
|
5882
|
+
// Add ESC key listener
|
|
5883
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
5696
5884
|
},
|
|
5697
5885
|
onremove: () => {
|
|
5698
5886
|
// Cleanup
|
|
@@ -5706,6 +5894,21 @@
|
|
|
5706
5894
|
document.removeEventListener('touchmove', handleDocumentClickMove);
|
|
5707
5895
|
document.removeEventListener('mouseup', handleDocumentClickEnd);
|
|
5708
5896
|
document.removeEventListener('touchend', handleDocumentClickEnd);
|
|
5897
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
5898
|
+
// Clean up portal if picker was open
|
|
5899
|
+
if (state.isOpen) {
|
|
5900
|
+
clearPortal(state.portalContainerId);
|
|
5901
|
+
}
|
|
5902
|
+
},
|
|
5903
|
+
onupdate: (vnode) => {
|
|
5904
|
+
const { useModal = true } = vnode.attrs;
|
|
5905
|
+
// Only render to portal when using modal mode
|
|
5906
|
+
if (useModal && state.isOpen) {
|
|
5907
|
+
renderPickerToPortal(vnode.attrs);
|
|
5908
|
+
}
|
|
5909
|
+
else {
|
|
5910
|
+
clearPortal(state.portalContainerId);
|
|
5911
|
+
}
|
|
5709
5912
|
},
|
|
5710
5913
|
view: ({ attrs }) => {
|
|
5711
5914
|
const { id = state.id, label, placeholder, disabled, readonly, required, iconName, helperText, onchange, oninput, useModal = true, showClearBtn = false, clearLabel = 'Clear', closeLabel = 'Cancel', twelveHour, className: cn1, class: cn2, } = attrs;
|
|
@@ -5802,29 +6005,7 @@
|
|
|
5802
6005
|
}, label),
|
|
5803
6006
|
// Helper text
|
|
5804
6007
|
helperText && m('span.helper-text', helperText),
|
|
5805
|
-
// Modal
|
|
5806
|
-
useModal &&
|
|
5807
|
-
state.isOpen && [
|
|
5808
|
-
// Modal overlay
|
|
5809
|
-
m('.modal-overlay', {
|
|
5810
|
-
style: {
|
|
5811
|
-
zIndex: 1002,
|
|
5812
|
-
display: 'block',
|
|
5813
|
-
opacity: 0.5,
|
|
5814
|
-
},
|
|
5815
|
-
onclick: () => close(),
|
|
5816
|
-
}),
|
|
5817
|
-
// Modal content
|
|
5818
|
-
m('.modal.timepicker-modal.open', {
|
|
5819
|
-
style: {
|
|
5820
|
-
zIndex: 1003,
|
|
5821
|
-
display: 'block',
|
|
5822
|
-
opacity: 1,
|
|
5823
|
-
top: '10%',
|
|
5824
|
-
transform: 'scaleX(1) scaleY(1)',
|
|
5825
|
-
},
|
|
5826
|
-
}, m(TimepickerModal, { showClearBtn, clearLabel, closeLabel, doneLabel: 'OK' })),
|
|
5827
|
-
],
|
|
6008
|
+
// Modal is now rendered via portal in onupdate hook
|
|
5828
6009
|
]);
|
|
5829
6010
|
},
|
|
5830
6011
|
};
|
|
@@ -9623,8 +9804,10 @@
|
|
|
9623
9804
|
exports.TreeView = TreeView;
|
|
9624
9805
|
exports.UrlInput = UrlInput;
|
|
9625
9806
|
exports.Wizard = Wizard;
|
|
9807
|
+
exports.clearPortal = clearPortal;
|
|
9626
9808
|
exports.createBreadcrumb = createBreadcrumb;
|
|
9627
9809
|
exports.getDropdownStyles = getDropdownStyles;
|
|
9810
|
+
exports.getPortalContainer = getPortalContainer;
|
|
9628
9811
|
exports.initPushpins = initPushpins;
|
|
9629
9812
|
exports.initTooltips = initTooltips;
|
|
9630
9813
|
exports.isNumeric = isNumeric;
|
|
@@ -9632,6 +9815,8 @@
|
|
|
9632
9815
|
exports.isValidationSuccess = isValidationSuccess;
|
|
9633
9816
|
exports.padLeft = padLeft;
|
|
9634
9817
|
exports.range = range;
|
|
9818
|
+
exports.releasePortalContainer = releasePortalContainer;
|
|
9819
|
+
exports.renderToPortal = renderToPortal;
|
|
9635
9820
|
exports.toast = toast;
|
|
9636
9821
|
exports.uniqueId = uniqueId;
|
|
9637
9822
|
exports.uuid4 = uuid4;
|
package/dist/modal.d.ts
CHANGED
|
@@ -27,10 +27,12 @@ export interface ModalAttrs extends Attributes {
|
|
|
27
27
|
onToggle?: (open: boolean) => void;
|
|
28
28
|
/** Called when modal is closed */
|
|
29
29
|
onClose?: () => void;
|
|
30
|
-
/** Show close button in top right */
|
|
30
|
+
/** Show close button in top right (default true) */
|
|
31
31
|
showCloseButton?: boolean;
|
|
32
|
-
/** Close modal when clicking backdrop */
|
|
32
|
+
/** Close modal when clicking backdrop (default true) */
|
|
33
33
|
closeOnBackdropClick?: boolean;
|
|
34
|
+
/** Close modal when clicking a button (default true) */
|
|
35
|
+
closeOnButtonClick?: boolean;
|
|
34
36
|
/** Close modal when pressing escape key */
|
|
35
37
|
closeOnEsc?: boolean;
|
|
36
38
|
}
|
package/dist/timepicker.d.ts
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -41,3 +41,35 @@ export declare const getDropdownStyles: (inputRef?: HTMLElement | null, overlap?
|
|
|
41
41
|
* @example: console.log(range(5, 10)); // [5, 6, 7, 8, 9, 10]
|
|
42
42
|
*/
|
|
43
43
|
export declare const range: (a: number, b: number) => number[];
|
|
44
|
+
/**
|
|
45
|
+
* Creates or retrieves a portal container appended to document.body.
|
|
46
|
+
* Uses reference counting to manage container lifecycle.
|
|
47
|
+
*
|
|
48
|
+
* @param id - Unique identifier for the portal container
|
|
49
|
+
* @param zIndex - Z-index for the portal container (default: 1004, above modals at 1003)
|
|
50
|
+
* @returns The portal container element
|
|
51
|
+
*/
|
|
52
|
+
export declare const getPortalContainer: (id: string, zIndex?: number) => HTMLElement;
|
|
53
|
+
/**
|
|
54
|
+
* Decrements reference count and removes portal container if no longer needed.
|
|
55
|
+
*
|
|
56
|
+
* @param id - Portal container identifier
|
|
57
|
+
*/
|
|
58
|
+
export declare const releasePortalContainer: (id: string) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Renders a Mithril vnode into a portal container using m.render().
|
|
61
|
+
* This allows components to render outside their parent DOM hierarchy,
|
|
62
|
+
* useful for modals and pickers that need to escape stacking contexts.
|
|
63
|
+
*
|
|
64
|
+
* @param containerId - Portal container identifier
|
|
65
|
+
* @param vnode - Mithril vnode to render
|
|
66
|
+
* @param zIndex - Z-index for portal container (default: 1004)
|
|
67
|
+
*/
|
|
68
|
+
export declare const renderToPortal: (containerId: string, vnode: any, zIndex?: number) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Clears portal content and releases container reference.
|
|
71
|
+
* If this is the last reference, the container will be removed from the DOM.
|
|
72
|
+
*
|
|
73
|
+
* @param containerId - Portal container identifier
|
|
74
|
+
*/
|
|
75
|
+
export declare const clearPortal: (containerId: string) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mithril-materialized",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.7",
|
|
4
4
|
"description": "A materialize library for mithril.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"build": "rollup -c rollup.config.mjs && npm run build:css-min",
|
|
39
39
|
"build:css-min": "sass --no-source-map --style=compressed --no-charset ./src/index.scss ./dist/index.min.css && npm run build:css-modules",
|
|
40
40
|
"build:css-modules": "sass --no-source-map ./src/core.scss ./dist/core.css && sass --no-source-map ./src/components.scss ./dist/components.css && sass --no-source-map ./src/forms.scss ./dist/forms.css && sass --no-source-map ./src/pickers.scss ./dist/pickers.css && sass --no-source-map ./src/advanced.scss ./dist/advanced.css && sass --no-source-map ./src/utilities.scss ./dist/utilities.css",
|
|
41
|
-
"dev": "concurrently \"rollup -c rollup.config.mjs -w\" \"
|
|
41
|
+
"dev": "concurrently \"rollup -c rollup.config.mjs -w\" \"pnpm run css:watch\"",
|
|
42
42
|
"start": "pnpm run dev",
|
|
43
43
|
"clean": "rimraf dist node_modules/.cache",
|
|
44
44
|
"test": "jest",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dry-run": "npm publish --dry-run",
|
|
56
56
|
"sass:watch": "sass --watch ./sass/materialize.scss ./dist/index.css",
|
|
57
57
|
"css:watch": "sass --watch --no-source-map --style=compressed --no-charset ./src/index.scss ./dist/index.min.css",
|
|
58
|
-
"dev:full": "
|
|
58
|
+
"dev:full": "pnpm run dev & pnpm run css:watch",
|
|
59
59
|
"patch-release": "npm run clean && npm run build && npm version patch --force -m \"Patch release\" && npm publish && git push --follow-tags",
|
|
60
60
|
"minor-release": "npm run clean && npm run build && npm version minor --force -m \"Minor release\" && npm publish && git push --follow-tags",
|
|
61
61
|
"major-release": "npm run clean && npm run build && npm version major --force -m \"Major release\" && npm publish && git push --follow-tags"
|
|
@@ -72,30 +72,29 @@
|
|
|
72
72
|
"author": "Erik Vullings <erik.vullings@gmail.com> (http://www.tno.nl)",
|
|
73
73
|
"license": "MIT",
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"mithril": "^2.3.
|
|
75
|
+
"mithril": "^2.3.8"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
|
-
"@playwright/test": "^1.
|
|
78
|
+
"@playwright/test": "^1.57.0",
|
|
79
79
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
80
80
|
"@testing-library/dom": "^10.4.1",
|
|
81
81
|
"@testing-library/jest-dom": "^6.9.1",
|
|
82
82
|
"@testing-library/user-event": "^14.6.1",
|
|
83
83
|
"@types/jest": "^30.0.0",
|
|
84
84
|
"@types/mithril": "^2.2.7",
|
|
85
|
-
"autoprefixer": "^10.4.
|
|
85
|
+
"autoprefixer": "^10.4.23",
|
|
86
86
|
"concurrently": "^9.2.1",
|
|
87
|
-
"express": "^5.1.0",
|
|
88
87
|
"identity-obj-proxy": "^3.0.0",
|
|
89
88
|
"jest": "^30.2.0",
|
|
90
89
|
"jest-environment-jsdom": "^30.2.0",
|
|
91
|
-
"js-yaml": "^4.1.
|
|
92
|
-
"rimraf": "^6.1.
|
|
93
|
-
"rollup": "^4.
|
|
90
|
+
"js-yaml": "^4.1.1",
|
|
91
|
+
"rimraf": "^6.1.2",
|
|
92
|
+
"rollup": "^4.54.0",
|
|
94
93
|
"rollup-plugin-postcss": "^4.0.2",
|
|
95
|
-
"sass": "^1.
|
|
96
|
-
"ts-jest": "^29.4.
|
|
94
|
+
"sass": "^1.97.1",
|
|
95
|
+
"ts-jest": "^29.4.6",
|
|
97
96
|
"tslib": "^2.8.1",
|
|
98
|
-
"typedoc": "^0.28.
|
|
97
|
+
"typedoc": "^0.28.15",
|
|
99
98
|
"typescript": "^5.9.3"
|
|
100
99
|
}
|
|
101
100
|
}
|