material-inspired-component-library 5.0.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/components/button/README.md +9 -1
- package/components/button/index.ts +21 -37
- package/components/datepicker/README.md +146 -0
- package/components/datepicker/index.scss +2 -1
- package/components/datepicker/index.ts +210 -109
- package/components/iconbutton/README.md +10 -1
- package/components/iconbutton/index.ts +21 -37
- package/components/textfield/index.ts +56 -0
- package/components/timepicker/README.md +8 -9
- package/components/timepicker/index.ts +5 -5
- package/dist/components/button/index.d.ts +2 -1
- package/dist/components/iconbutton/index.d.ts +2 -1
- package/dist/datepicker.css +1 -1
- package/dist/micl.css +1 -1
- package/dist/micl.js +1 -1
- package/docs/bottomsheet.html +3 -3
- package/docs/button.html +16 -16
- package/docs/datepicker.html +133 -9
- package/docs/dialog.html +5 -5
- package/docs/docs.js +22 -1
- package/docs/iconbutton.html +8 -8
- package/docs/index.html +3 -2
- package/docs/micl.css +1 -1
- package/docs/micl.js +1 -1
- package/docs/navigationrail.html +2 -2
- package/docs/sidesheet.html +3 -3
- package/docs/themes/gray/dark-hc.css +51 -0
- package/docs/themes/gray/dark-mc.css +51 -0
- package/docs/themes/gray/dark.css +51 -0
- package/docs/themes/gray/light-hc.css +51 -0
- package/docs/themes/gray/light-mc.css +51 -0
- package/docs/themes/gray/light.css +51 -0
- package/docs/themes/gray/theme.css +306 -0
- package/docs/themes/greenery/dark-hc.css +51 -0
- package/docs/themes/greenery/dark-mc.css +51 -0
- package/docs/themes/greenery/dark.css +51 -0
- package/docs/themes/greenery/light-hc.css +51 -0
- package/docs/themes/greenery/light-mc.css +51 -0
- package/docs/themes/greenery/light.css +51 -0
- package/docs/themes/greenery/theme.css +306 -0
- package/docs/themes/hermana/dark-hc.css +51 -0
- package/docs/themes/hermana/dark-mc.css +51 -0
- package/docs/themes/hermana/dark.css +51 -0
- package/docs/themes/hermana/light-hc.css +51 -0
- package/docs/themes/hermana/light-mc.css +51 -0
- package/docs/themes/hermana/light.css +51 -0
- package/docs/themes/hermana/theme.css +306 -0
- package/docs/themes/illuminating/dark-hc.css +51 -0
- package/docs/themes/illuminating/dark-mc.css +51 -0
- package/docs/themes/illuminating/dark.css +51 -0
- package/docs/themes/illuminating/light-hc.css +51 -0
- package/docs/themes/illuminating/light-mc.css +51 -0
- package/docs/themes/illuminating/light.css +51 -0
- package/docs/themes/illuminating/theme.css +306 -0
- package/docs/themes/magenta/dark-hc.css +51 -0
- package/docs/themes/magenta/dark-mc.css +51 -0
- package/docs/themes/magenta/dark.css +51 -0
- package/docs/themes/magenta/light-hc.css +51 -0
- package/docs/themes/magenta/light-mc.css +51 -0
- package/docs/themes/magenta/light.css +51 -0
- package/docs/themes/magenta/theme.css +306 -0
- package/docs/themes/mocha/dark-hc.css +51 -0
- package/docs/themes/mocha/dark-mc.css +51 -0
- package/docs/themes/mocha/dark.css +51 -0
- package/docs/themes/mocha/light-hc.css +51 -0
- package/docs/themes/mocha/light-mc.css +51 -0
- package/docs/themes/mocha/light.css +51 -0
- package/docs/themes/mocha/theme.css +306 -0
- package/docs/themes/peri/dark-hc.css +51 -0
- package/docs/themes/peri/dark-mc.css +51 -0
- package/docs/themes/peri/dark.css +51 -0
- package/docs/themes/peri/light-hc.css +51 -0
- package/docs/themes/peri/light-mc.css +51 -0
- package/docs/themes/peri/light.css +51 -0
- package/docs/themes/peri/theme.css +306 -0
- package/docs/timepicker.html +2 -2
- package/package.json +1 -1
- package/themes/gray/dark-hc.css +51 -0
- package/themes/gray/dark-mc.css +51 -0
- package/themes/gray/dark.css +51 -0
- package/themes/gray/light-hc.css +51 -0
- package/themes/gray/light-mc.css +51 -0
- package/themes/gray/light.css +51 -0
- package/themes/gray/theme.css +306 -0
- package/themes/greenery/dark-hc.css +51 -0
- package/themes/greenery/dark-mc.css +51 -0
- package/themes/greenery/dark.css +51 -0
- package/themes/greenery/light-hc.css +51 -0
- package/themes/greenery/light-mc.css +51 -0
- package/themes/greenery/light.css +51 -0
- package/themes/greenery/theme.css +306 -0
- package/themes/hermana/dark-hc.css +51 -0
- package/themes/hermana/dark-mc.css +51 -0
- package/themes/hermana/dark.css +51 -0
- package/themes/hermana/light-hc.css +51 -0
- package/themes/hermana/light-mc.css +51 -0
- package/themes/hermana/light.css +51 -0
- package/themes/hermana/theme.css +306 -0
- package/themes/illuminating/dark-hc.css +51 -0
- package/themes/illuminating/dark-mc.css +51 -0
- package/themes/illuminating/dark.css +51 -0
- package/themes/illuminating/light-hc.css +51 -0
- package/themes/illuminating/light-mc.css +51 -0
- package/themes/illuminating/light.css +51 -0
- package/themes/illuminating/theme.css +306 -0
- package/themes/magenta/dark-hc.css +51 -0
- package/themes/magenta/dark-mc.css +51 -0
- package/themes/magenta/dark.css +51 -0
- package/themes/magenta/light-hc.css +51 -0
- package/themes/magenta/light-mc.css +51 -0
- package/themes/magenta/light.css +51 -0
- package/themes/magenta/theme.css +306 -0
- package/themes/mocha/dark-hc.css +51 -0
- package/themes/mocha/dark-mc.css +51 -0
- package/themes/mocha/dark.css +51 -0
- package/themes/mocha/light-hc.css +51 -0
- package/themes/mocha/light-mc.css +51 -0
- package/themes/mocha/light.css +51 -0
- package/themes/mocha/theme.css +306 -0
- package/themes/peri/dark-hc.css +51 -0
- package/themes/peri/dark-mc.css +51 -0
- package/themes/peri/dark.css +51 -0
- package/themes/peri/light-hc.css +51 -0
- package/themes/peri/light-mc.css +51 -0
- package/themes/peri/light.css +51 -0
- package/themes/peri/theme.css +306 -0
|
@@ -27,17 +27,52 @@ interface DatePickerState {
|
|
|
27
27
|
invoker : ValueElement | null;
|
|
28
28
|
selected: Date;
|
|
29
29
|
viewDate: Date; // the month/year currently being viewed
|
|
30
|
-
min : Date
|
|
31
|
-
max : Date
|
|
30
|
+
min : Date;
|
|
31
|
+
max : Date;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const stateMap = new WeakMap<HTMLDialogElement, DatePickerState>();
|
|
35
|
-
|
|
36
35
|
const locale = new Intl.DateTimeFormat().resolvedOptions().locale;
|
|
37
36
|
|
|
38
|
-
const
|
|
37
|
+
const formatters = {
|
|
38
|
+
input: new Intl.DateTimeFormat(locale, {
|
|
39
|
+
year : 'numeric',
|
|
40
|
+
month: '2-digit',
|
|
41
|
+
day : '2-digit'
|
|
42
|
+
}),
|
|
43
|
+
header: new Intl.DateTimeFormat(locale, { weekday: 'short', day: 'numeric', month: 'short' }),
|
|
44
|
+
monthLong: new Intl.DateTimeFormat(undefined, { month: 'long' }),
|
|
45
|
+
monthShort: new Intl.DateTimeFormat(locale, { month: 'short' }),
|
|
46
|
+
weekdayNarrow: new Intl.DateTimeFormat(locale, { weekday: 'narrow' }),
|
|
47
|
+
weekdayLong: new Intl.DateTimeFormat(locale, { weekday: 'long' })
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const toLocalMidnight = (date: Date): Date =>
|
|
51
|
+
{
|
|
52
|
+
const d = new Date(date);
|
|
53
|
+
d.setHours(0, 0, 0, 0);
|
|
54
|
+
return d;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const formatToInputDateValue = (d: Date): string =>
|
|
58
|
+
{
|
|
59
|
+
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
|
60
|
+
const day = d.getDate().toString().padStart(2, '0');
|
|
61
|
+
return `${d.getFullYear()}-${month}-${day}`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const getDateFormat = (): string =>
|
|
39
65
|
{
|
|
40
|
-
return
|
|
66
|
+
return formatters.input.formatToParts(new Date(2025, 0, 15)).map(part =>
|
|
67
|
+
{
|
|
68
|
+
switch (part.type) {
|
|
69
|
+
case 'day' : return 'DD';
|
|
70
|
+
case 'month' : return 'MM';
|
|
71
|
+
case 'year' : return 'YYYY';
|
|
72
|
+
case 'literal': return part.value;
|
|
73
|
+
default: return '';
|
|
74
|
+
}
|
|
75
|
+
}).join('').trim();
|
|
41
76
|
};
|
|
42
77
|
|
|
43
78
|
const getFirstDayOfWeek = (): number =>
|
|
@@ -54,15 +89,7 @@ const getFirstDayOfWeek = (): number =>
|
|
|
54
89
|
};
|
|
55
90
|
|
|
56
91
|
const firstDayOfWeek = getFirstDayOfWeek();
|
|
57
|
-
|
|
58
|
-
const toLocalMidnight = (date: Date): Date =>
|
|
59
|
-
{
|
|
60
|
-
const d = new Date(date);
|
|
61
|
-
d.setHours(0, 0, 0, 0);
|
|
62
|
-
return d;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const isValidDate = (d: Date): boolean => !isNaN(d.getTime());
|
|
92
|
+
const isValidDate = (date: Date): boolean => !isNaN(date.getTime());
|
|
66
93
|
|
|
67
94
|
const setText = (parent: Element | null, text: string): void =>
|
|
68
95
|
{
|
|
@@ -90,23 +117,40 @@ const getCalendarDays = (
|
|
|
90
117
|
month: number
|
|
91
118
|
): Array<{ date: Date, val: string, isCurrentMonth: boolean }> => {
|
|
92
119
|
|
|
93
|
-
const results = [];
|
|
94
120
|
const firstOfMonth = new Date(year, month, 1);
|
|
95
121
|
const dayOfWeek = firstOfMonth.getDay();
|
|
96
122
|
const offset = (dayOfWeek - firstDayOfWeek + 7) % 7;
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
|
|
124
|
+
const startDate = new Date(year, month, 1 - offset);
|
|
125
|
+
|
|
126
|
+
return Array.from({ length: 42 }, (_, i) => {
|
|
127
|
+
const current = new Date(startDate);
|
|
128
|
+
current.setDate(startDate.getDate() + i);
|
|
129
|
+
return {
|
|
130
|
+
date : current,
|
|
131
|
+
val : formatToInputDateValue(current),
|
|
105
132
|
isCurrentMonth: current.getMonth() === month
|
|
106
|
-
}
|
|
107
|
-
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const renderCalendarHeader = (): DocumentFragment =>
|
|
138
|
+
{
|
|
139
|
+
const tempDate = new Date();
|
|
140
|
+
const startOffset = tempDate.getDay() - firstDayOfWeek;
|
|
141
|
+
tempDate.setDate(tempDate.getDate() - startOffset);
|
|
142
|
+
|
|
143
|
+
const fragment = document.createDocumentFragment();
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < 7; i++) {
|
|
146
|
+
const span = document.createElement('span');
|
|
147
|
+
span.style.gridArea = `1 / ${i + 1}`;
|
|
148
|
+
span.textContent = formatters.weekdayNarrow.format(tempDate);
|
|
149
|
+
span.title = formatters.weekdayLong.format(tempDate);
|
|
150
|
+
fragment.appendChild(span);
|
|
151
|
+
tempDate.setDate(tempDate.getDate() + 1);
|
|
108
152
|
}
|
|
109
|
-
return
|
|
153
|
+
return fragment;
|
|
110
154
|
};
|
|
111
155
|
|
|
112
156
|
const populateContainerWithDays = (
|
|
@@ -116,19 +160,7 @@ const populateContainerWithDays = (
|
|
|
116
160
|
isEmpty : boolean = false
|
|
117
161
|
): void => {
|
|
118
162
|
if (isEmpty) {
|
|
119
|
-
const fragment
|
|
120
|
-
const tempDate = new Date();
|
|
121
|
-
const startOffset = tempDate.getDay() - firstDayOfWeek;
|
|
122
|
-
tempDate.setDate(tempDate.getDate() - startOffset);
|
|
123
|
-
|
|
124
|
-
for (let i = 0; i < 7; i++) {
|
|
125
|
-
const span = document.createElement('span');
|
|
126
|
-
span.style.gridArea = `1 / ${i + 1}`;
|
|
127
|
-
span.textContent = tempDate.toLocaleDateString(locale, { weekday: 'narrow' });
|
|
128
|
-
span.title = tempDate.toLocaleDateString(locale, { weekday: 'long' });
|
|
129
|
-
fragment.appendChild(span);
|
|
130
|
-
tempDate.setDate(tempDate.getDate() + 1);
|
|
131
|
-
}
|
|
163
|
+
const fragment = renderCalendarHeader();
|
|
132
164
|
|
|
133
165
|
days.forEach((_, index) => {
|
|
134
166
|
const time = document.createElement('time');
|
|
@@ -249,62 +281,78 @@ const renderCalendar = (
|
|
|
249
281
|
|
|
250
282
|
const input = content?.querySelector<HTMLInputElement>('.micl-datepicker__input input');
|
|
251
283
|
if (input) {
|
|
252
|
-
|
|
253
|
-
input.value
|
|
284
|
+
input.value = formatters.input.format(state.selected);
|
|
285
|
+
if (input.value) {
|
|
286
|
+
input.dataset.miclvalue = '1';
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
delete input.dataset.miclvalue;
|
|
290
|
+
}
|
|
291
|
+
if (!input.dataset.micldateformat) {
|
|
292
|
+
input.dataset.micldateformat = getDateFormat();
|
|
293
|
+
}
|
|
254
294
|
}
|
|
255
|
-
|
|
256
|
-
setText(
|
|
257
|
-
|
|
258
|
-
state.selected.toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short' })
|
|
259
|
-
);
|
|
260
|
-
setText(
|
|
261
|
-
dialog.querySelector('.micl-datepicker__month'),
|
|
262
|
-
state.viewDate.toLocaleDateString(locale, { month: 'short' })
|
|
263
|
-
);
|
|
295
|
+
|
|
296
|
+
setText(dialog.querySelector('h1, h2, h3, h4, h5, h6, .micl-heading'), formatters.header.format(state.selected));
|
|
297
|
+
setText(dialog.querySelector('.micl-datepicker__month'), formatters.monthShort.format(state.viewDate));
|
|
264
298
|
setText(
|
|
265
299
|
dialog.querySelector('.micl-datepicker__year'),
|
|
266
300
|
state.viewDate.toLocaleDateString(locale, dialog.classList.contains('micl-dialog--docked') ?
|
|
267
301
|
{ year: 'numeric' } : { month: 'long', year: 'numeric' })
|
|
268
302
|
);
|
|
269
303
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
});
|
|
304
|
+
const monthInput = dialog.querySelector<HTMLInputElement>(`.micl-datepicker__months input[value="${state.viewDate.getMonth()}"]`);
|
|
305
|
+
if (monthInput) monthInput.checked = true;
|
|
306
|
+
|
|
307
|
+
const yearInput = dialog.querySelector<HTMLInputElement>(`.micl-datepicker__years input[value="${state.viewDate.getFullYear()}"]`);
|
|
308
|
+
if (yearInput) yearInput.checked = true;
|
|
277
309
|
};
|
|
278
310
|
|
|
279
|
-
const initPeriodPickers = (dialog: HTMLDialogElement,
|
|
311
|
+
const initPeriodPickers = (dialog: HTMLDialogElement, min: Date, max: Date): void =>
|
|
280
312
|
{
|
|
281
|
-
|
|
313
|
+
const minYear = min.getFullYear();
|
|
314
|
+
const maxYear = max.getFullYear();
|
|
315
|
+
|
|
316
|
+
['months', 'years'].forEach(period =>
|
|
317
|
+
{
|
|
282
318
|
const container = dialog.querySelector(`.micl-datepicker__${period}`);
|
|
283
|
-
if (container) {
|
|
284
|
-
|
|
285
|
-
|
|
319
|
+
if (!container) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
container.innerHTML = '';
|
|
323
|
+
const frag = document.createDocumentFragment();
|
|
286
324
|
|
|
287
|
-
|
|
288
|
-
const fmt = new Intl.DateTimeFormat(undefined, { month: 'long' });
|
|
325
|
+
const maxMonth = max.getMonth();
|
|
289
326
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
327
|
+
if (period === 'months') {
|
|
328
|
+
const months: number[] = [];
|
|
329
|
+
|
|
330
|
+
let current = new Date(min.getFullYear(), min.getMonth(), 1);
|
|
331
|
+
while (
|
|
332
|
+
current <= max
|
|
333
|
+
|| (current.getMonth() === maxMonth && current.getFullYear() === maxYear)
|
|
334
|
+
) {
|
|
335
|
+
months.push(current.getMonth());
|
|
336
|
+
current.setMonth(current.getMonth() + 1);
|
|
295
337
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
338
|
+
|
|
339
|
+
[...new Set(months.sort((a, b) => a - b))].forEach(m => {
|
|
340
|
+
const label = document.createElement('label');
|
|
341
|
+
label.innerHTML = `<span class="material-symbols-outlined">check</span><input type="radio" name="miclmonth" value="${m}"> ${formatters.monthLong.format(new Date(2000, m, 1))}`;
|
|
342
|
+
frag.appendChild(label);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
for (let y = minYear; y <= maxYear; y++) {
|
|
347
|
+
const label = document.createElement('label');
|
|
348
|
+
label.innerHTML = `<input type="radio" name="miclyear" value="${y}"> ${y}`;
|
|
349
|
+
frag.appendChild(label);
|
|
302
350
|
}
|
|
303
|
-
|
|
304
|
-
const inner = document.createElement('div');
|
|
305
|
-
inner.classList.add(`micl-datepicker__${period}-inner`);
|
|
306
|
-
container.appendChild(inner).appendChild(frag);
|
|
307
351
|
}
|
|
352
|
+
|
|
353
|
+
const inner = document.createElement('div');
|
|
354
|
+
inner.classList.add(`micl-datepicker__${period}-inner`);
|
|
355
|
+
container.appendChild(inner).appendChild(frag);
|
|
308
356
|
});
|
|
309
357
|
};
|
|
310
358
|
|
|
@@ -384,6 +432,14 @@ const toggleView = (dialog: HTMLDialogElement, view: 'calendars' | 'months' | 'y
|
|
|
384
432
|
period.classList.toggle('micl-datepicker__view-hidden', doHide);
|
|
385
433
|
}
|
|
386
434
|
});
|
|
435
|
+
|
|
436
|
+
const mode = dialog.querySelector<HTMLElement>('.micl-datepicker__inputmode[data-miclalt]');
|
|
437
|
+
if (mode) {
|
|
438
|
+
if (!mode.dataset.miclalticon) {
|
|
439
|
+
mode.dataset.miclalticon = mode.textContent;
|
|
440
|
+
}
|
|
441
|
+
mode.textContent = (view === 'input' ? mode.dataset.miclalt : mode.dataset.miclalticon) || '';
|
|
442
|
+
}
|
|
387
443
|
};
|
|
388
444
|
|
|
389
445
|
const changePeriod = (dialog: HTMLDialogElement, amount: number, unit: 'month' | 'year'): void =>
|
|
@@ -401,25 +457,58 @@ const changePeriod = (dialog: HTMLDialogElement, amount: number, unit: 'month' |
|
|
|
401
457
|
newDate.setFullYear(newDate.getFullYear() + amount);
|
|
402
458
|
}
|
|
403
459
|
|
|
404
|
-
|
|
405
|
-
|
|
460
|
+
const belowMin = state.min && newDate < state.min;
|
|
461
|
+
const aboveMax = state.max && newDate > state.max;
|
|
462
|
+
|
|
463
|
+
if (belowMin || aboveMax) {
|
|
464
|
+
dialog.querySelector('.micl-datepicker__calendars')?.animate([
|
|
465
|
+
{ transform: 'translateX(0)' },
|
|
466
|
+
{ transform: `translateX(${belowMin ? 8 : -8}px)` },
|
|
467
|
+
{ transform: 'translateX(0)' }
|
|
468
|
+
], { duration: 500, easing: 'ease-in-out' });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
406
471
|
|
|
407
472
|
state.viewDate = newDate;
|
|
408
473
|
renderCalendar(dialog, state, unit === 'month' ? amount : 0);
|
|
409
474
|
};
|
|
410
475
|
|
|
411
|
-
const selectDate = (dialog: HTMLDialogElement, dateStr: string): void =>
|
|
476
|
+
const selectDate = (dialog: HTMLDialogElement, dateStr: string, isLocaleFormatted = false): void =>
|
|
412
477
|
{
|
|
413
478
|
const state = stateMap.get(dialog);
|
|
414
479
|
if (!state) {
|
|
415
480
|
return;
|
|
416
481
|
}
|
|
417
482
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
483
|
+
let parts: number[] = [];
|
|
484
|
+
if (isLocaleFormatted) {
|
|
485
|
+
const dateformat = getDateFormat();
|
|
486
|
+
if (dateStr.length === dateformat.length) {
|
|
487
|
+
let d = '';
|
|
488
|
+
let m = '';
|
|
489
|
+
let y = '';
|
|
490
|
+
for (let i = 0; i < dateformat.length; i++) {
|
|
491
|
+
switch (dateformat[i]) {
|
|
492
|
+
case 'D': d += dateStr[i]; break;
|
|
493
|
+
case 'M': m += dateStr[i]; break;
|
|
494
|
+
case 'Y': y += dateStr[i]; break;
|
|
495
|
+
default:
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
parts = [parseInt(y, 10), parseInt(m, 10) - 1, parseInt(d, 10)];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
parts = dateStr.split('-').map(Number);
|
|
503
|
+
parts[1]--;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (parts.length === 3) {
|
|
507
|
+
state.selected = new Date(parts[0], parts[1], parts[2]);
|
|
508
|
+
state.viewDate = new Date(state.selected);
|
|
509
|
+
|
|
510
|
+
renderCalendar(dialog, state);
|
|
511
|
+
}
|
|
423
512
|
};
|
|
424
513
|
|
|
425
514
|
export default (() =>
|
|
@@ -446,8 +535,10 @@ export default (() =>
|
|
|
446
535
|
}
|
|
447
536
|
break;
|
|
448
537
|
case 'M':
|
|
538
|
+
toggleView(dialog, 'months');
|
|
539
|
+
break;
|
|
449
540
|
case 'Y':
|
|
450
|
-
toggleView(dialog,
|
|
541
|
+
toggleView(dialog, 'years');
|
|
451
542
|
break;
|
|
452
543
|
case 'PageUp':
|
|
453
544
|
case 'PageDown':
|
|
@@ -491,11 +582,9 @@ export default (() =>
|
|
|
491
582
|
|
|
492
583
|
const mode = target.closest('.micl-datepicker__inputmode') as HTMLElement;
|
|
493
584
|
if (mode) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const inputHidden = !!dialog.querySelector('.micl-datepicker__input.micl-datepicker__view-hidden');
|
|
498
|
-
toggleView(dialog, inputHidden ? 'input' : 'calendars');
|
|
585
|
+
toggleView(dialog, !dialog.querySelector(
|
|
586
|
+
'.micl-datepicker__input.micl-datepicker__view-hidden'
|
|
587
|
+
) ? 'calendars' : 'input');
|
|
499
588
|
}
|
|
500
589
|
|
|
501
590
|
const time = target.closest('time');
|
|
@@ -516,6 +605,12 @@ export default (() =>
|
|
|
516
605
|
else {
|
|
517
606
|
state.viewDate.setFullYear(value);
|
|
518
607
|
}
|
|
608
|
+
if (state.viewDate < state.min) {
|
|
609
|
+
state.viewDate = state.min;
|
|
610
|
+
}
|
|
611
|
+
else if (state.viewDate > state.max) {
|
|
612
|
+
state.viewDate = state.max;
|
|
613
|
+
}
|
|
519
614
|
renderCalendar(dialog, state);
|
|
520
615
|
toggleView(dialog, 'calendars');
|
|
521
616
|
}
|
|
@@ -527,23 +622,24 @@ export default (() =>
|
|
|
527
622
|
if (event.newState !== 'open') {
|
|
528
623
|
return;
|
|
529
624
|
}
|
|
625
|
+
const isInvoker = (e: Element | null): e is ValueElement => e instanceof HTMLInputElement || e instanceof HTMLButtonElement;
|
|
530
626
|
|
|
531
627
|
let invoker = document.activeElement;
|
|
532
628
|
if (
|
|
533
|
-
!
|
|
534
|
-
|| (!invoker.dataset.datepicker && !invoker.
|
|
629
|
+
!isInvoker(invoker)
|
|
630
|
+
|| (!invoker.dataset.datepicker && !invoker.popoverTargetElement && !(invoker as any).commandForElement)
|
|
535
631
|
) {
|
|
536
632
|
invoker = document.querySelector(
|
|
537
|
-
`[data-datepicker="${dialog.id}"],[popovertarget="${dialog.id}"]`
|
|
633
|
+
`[data-datepicker="${dialog.id}"],[popovertarget="${dialog.id}"],[commandfor="${dialog.id}"]`
|
|
538
634
|
);
|
|
539
635
|
}
|
|
540
|
-
if (!
|
|
636
|
+
if (!isInvoker(invoker)) {
|
|
541
637
|
return;
|
|
542
638
|
}
|
|
543
|
-
|
|
639
|
+
|
|
544
640
|
let initialDate = new Date();
|
|
545
|
-
let min
|
|
546
|
-
let max
|
|
641
|
+
let min = new Date(1900, 0, 1);
|
|
642
|
+
let max = new Date(2099, 11, 31);
|
|
547
643
|
|
|
548
644
|
if (invoker instanceof HTMLInputElement) {
|
|
549
645
|
if (invoker.type === 'date' && invoker.valueAsDate) {
|
|
@@ -564,28 +660,33 @@ export default (() =>
|
|
|
564
660
|
if (!isValidDate(initialDate)) initialDate = new Date();
|
|
565
661
|
initialDate = toLocalMidnight(initialDate);
|
|
566
662
|
|
|
567
|
-
|
|
568
|
-
invoker
|
|
663
|
+
const state: DatePickerState = {
|
|
664
|
+
invoker,
|
|
569
665
|
selected: initialDate,
|
|
570
666
|
viewDate: new Date(initialDate),
|
|
571
667
|
min,
|
|
572
668
|
max
|
|
573
|
-
}
|
|
669
|
+
};
|
|
670
|
+
stateMap.set(dialog, state);
|
|
574
671
|
|
|
575
|
-
initPeriodPickers(dialog, min
|
|
672
|
+
initPeriodPickers(dialog, min, max);
|
|
576
673
|
toggleView(dialog, 'calendars');
|
|
577
|
-
renderCalendar(dialog,
|
|
674
|
+
renderCalendar(dialog, state);
|
|
675
|
+
|
|
676
|
+
dialog.querySelector('.micl-datepicker__input input')?.addEventListener('blur', e =>
|
|
677
|
+
{
|
|
678
|
+
const element = e.target as HTMLInputElement;
|
|
679
|
+
selectDate(dialog, element.value, true);
|
|
680
|
+
}, { once: true });
|
|
578
681
|
});
|
|
579
682
|
|
|
580
683
|
dialog.addEventListener('close', (): void =>
|
|
581
684
|
{
|
|
582
685
|
const state = stateMap.get(dialog);
|
|
583
|
-
if (!state
|
|
686
|
+
if (!state?.invoker || dialog.returnValue === '') {
|
|
584
687
|
return;
|
|
585
688
|
}
|
|
586
|
-
|
|
587
|
-
const date = `${state.selected.getFullYear()}-${pad(state.selected.getMonth() + 1)}-${pad(state.selected.getDate())}`;
|
|
588
|
-
state.invoker.value = date;
|
|
689
|
+
state.invoker.value = formatToInputDateValue(state.selected);
|
|
589
690
|
|
|
590
691
|
if (state.invoker instanceof HTMLInputElement) {
|
|
591
692
|
state.invoker.dispatchEvent(new Event('change', { bubbles: true }));
|
|
@@ -67,9 +67,18 @@ A toggle button has two states: **on** (selected) and **off** (unselected). To c
|
|
|
67
67
|
**Example: A selected toggle button**
|
|
68
68
|
|
|
69
69
|
```HTML
|
|
70
|
-
<button
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
id="id0"
|
|
73
|
+
class="micl-iconbutton-outlined-l micl-button--toggle micl-button--selected material-symbols-outlined"
|
|
74
|
+
commandfor="id0"
|
|
75
|
+
command="--micl-toggle"
|
|
76
|
+
aria-label="Control Panel"
|
|
77
|
+
>settings</button>
|
|
71
78
|
```
|
|
72
79
|
|
|
80
|
+
The self-targeting `command` property (`--micl-toggle`) toggles the button state whenever the user interacts with the button.
|
|
81
|
+
|
|
73
82
|
## Icons
|
|
74
83
|
The examples above use [Google Material Symbols](https://fonts.google.com/icons). For buttons using these icons, a fill-style of `1` is applied when the button is active or hovered over. To enable this effect, ensure your `link` tag includes `FILL@0..1`.
|
|
75
84
|
|
|
@@ -19,58 +19,42 @@
|
|
|
19
19
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
20
|
// SOFTWARE.
|
|
21
21
|
|
|
22
|
-
export const buttonSelector = 'button
|
|
22
|
+
export const buttonSelector = 'button.micl-button--toggle';
|
|
23
23
|
|
|
24
24
|
export default (() =>
|
|
25
25
|
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
if (event.target.classList.contains('micl-button--toggle')) {
|
|
40
|
-
event.target.classList.add('micl-button--toggled');
|
|
41
|
-
event.target.classList.toggle('micl-button--selected');
|
|
42
|
-
if (!!event.target.dataset.miclalt) {
|
|
43
|
-
[event.target.textContent, event.target.dataset.miclalt] =
|
|
44
|
-
[event.target.dataset.miclalt, event.target.textContent];
|
|
26
|
+
return {
|
|
27
|
+
command: (event: Event): void =>
|
|
28
|
+
{
|
|
29
|
+
const target = event.target as HTMLButtonElement;
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
target.matches(buttonSelector)
|
|
33
|
+
&& !target.disabled
|
|
34
|
+
&& (event as any).command === '--micl-toggle'
|
|
35
|
+
) {
|
|
36
|
+
target.classList.add('micl-button--toggled');
|
|
37
|
+
target.classList.toggle('micl-button--selected');
|
|
45
38
|
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
39
|
+
},
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
initialize: (element: HTMLButtonElement) =>
|
|
41
|
+
initialize: function(element: HTMLButtonElement): void
|
|
51
42
|
{
|
|
52
43
|
if (
|
|
53
|
-
!element.matches(
|
|
44
|
+
!element.matches(buttonSelector)
|
|
54
45
|
|| element.dataset.miclinitialized
|
|
55
46
|
) {
|
|
56
47
|
return;
|
|
57
48
|
}
|
|
58
49
|
element.dataset.miclinitialized = '1';
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
(element.popoverTargetElement instanceof HTMLDialogElement)
|
|
62
|
-
&& !element.popoverTargetElement.hasAttribute('popover')
|
|
63
|
-
) {
|
|
64
|
-
element.addEventListener('click', onClick);
|
|
65
|
-
}
|
|
66
|
-
else if (element.classList.contains('micl-button--toggle')) {
|
|
67
|
-
element.addEventListener('click', onClick);
|
|
68
|
-
}
|
|
51
|
+
element.addEventListener('command', this.command);
|
|
69
52
|
},
|
|
70
|
-
|
|
53
|
+
|
|
54
|
+
cleanup: function(element: HTMLButtonElement): void
|
|
71
55
|
{
|
|
72
|
-
if (element.matches(
|
|
73
|
-
document.removeEventListener('
|
|
56
|
+
if (element.matches(buttonSelector)) {
|
|
57
|
+
document.removeEventListener('command', this.command);
|
|
74
58
|
delete element.dataset.miclinitialized;
|
|
75
59
|
}
|
|
76
60
|
}
|
|
@@ -44,6 +44,59 @@ export default (() =>
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
const formatAsDate = (input: HTMLInputElement, inputType: string): void =>
|
|
48
|
+
{
|
|
49
|
+
const partsRegex = /([DMY]{2,4})([^DMY])?([DMY]{2,4})([^DMY])?([DMY]{2,4})/;
|
|
50
|
+
const match = (input.dataset.micldateformat || '').match(partsRegex);
|
|
51
|
+
if (!match) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const components = [
|
|
56
|
+
{ type: match[1], length: match[1].length, separator: match[2] || '' },
|
|
57
|
+
{ type: match[3], length: match[3].length, separator: match[4] || '' },
|
|
58
|
+
{ type: match[5], length: match[5].length, separator: '' }
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
input.maxLength = components.reduce((sum, c) => sum + c.length + (c.separator ? 1 : 0), 0);
|
|
62
|
+
|
|
63
|
+
let value = input.value.replace(/\D/g, ''); // remove all non-digits
|
|
64
|
+
let formattedValue = '';
|
|
65
|
+
let valueIndex = 0;
|
|
66
|
+
let cursorPosition = input.selectionStart || 0;
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < components.length; i++) {
|
|
69
|
+
const comp = components[i];
|
|
70
|
+
if (value.length < valueIndex) break;
|
|
71
|
+
|
|
72
|
+
const segment = value.substring(valueIndex, valueIndex + comp.length);
|
|
73
|
+
formattedValue += segment;
|
|
74
|
+
valueIndex += segment.length;
|
|
75
|
+
|
|
76
|
+
if (segment.length === comp.length && comp.separator) {
|
|
77
|
+
formattedValue += comp.separator;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const prevLength = input.value.length;
|
|
82
|
+
input.value = formattedValue.substring(0, input.maxLength);
|
|
83
|
+
const newLength = input.value.length;
|
|
84
|
+
|
|
85
|
+
if (inputType.startsWith('deleteContent')) {
|
|
86
|
+
if (cursorPosition > 0) {
|
|
87
|
+
input.setSelectionRange(cursorPosition, cursorPosition);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
if (newLength > prevLength && newLength > cursorPosition) {
|
|
92
|
+
input.setSelectionRange(newLength, newLength);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
input.setSelectionRange(cursorPosition, cursorPosition);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
47
100
|
return {
|
|
48
101
|
initialize: (input: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void =>
|
|
49
102
|
{
|
|
@@ -110,6 +163,9 @@ export default (() =>
|
|
|
110
163
|
return;
|
|
111
164
|
}
|
|
112
165
|
|
|
166
|
+
if (event.target instanceof HTMLInputElement && event.target.dataset.micldateformat) {
|
|
167
|
+
formatAsDate(event.target, (event as InputEvent).inputType);
|
|
168
|
+
}
|
|
113
169
|
if (event.target.value) {
|
|
114
170
|
event.target.dataset.miclvalue = '1';
|
|
115
171
|
}
|