@allxsmith/bestax-bulma 5.26.2 → 5.26.3
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/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +453 -303
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +453 -303
- package/dist/index.esm.js.map +1 -1
- package/dist/types/elements/Progress.d.ts +6 -1
- package/dist/types/form/Control.d.ts +14 -2
- package/dist/types/form/DateInput.d.ts +13 -3
- package/dist/types/form/DateTimeInput.d.ts +13 -3
- package/dist/types/form/Input.d.ts +16 -4
- package/dist/types/form/InputBase.d.ts +5 -2
- package/dist/types/form/Select.d.ts +8 -2
- package/dist/types/form/TextArea.d.ts +4 -2
- package/dist/types/form/TextAreaBase.d.ts +12 -3
- package/dist/types/form/TimeInput.d.ts +13 -3
- package/dist/types/form/TimeInputBase.d.ts +5 -1
- package/dist/types/form/_pickerInternals/Calendar.d.ts +10 -1
- package/dist/types/form/_pickerInternals/PickerPopover.d.ts +6 -0
- package/dist/types/helpers/Theme.d.ts +13 -4
- package/dist/types/helpers/portal.d.ts +2 -1
- package/dist/types/helpers/useAnchoredPosition.d.ts +6 -0
- package/dist/types/helpers/useFocusTrap.d.ts +2 -1
- package/dist/types/helpers/useOtherClasses.d.ts +13 -1
- package/dist/types-cjs/elements/Progress.d.ts +6 -1
- package/dist/types-cjs/form/Control.d.ts +14 -2
- package/dist/types-cjs/form/DateInput.d.ts +13 -3
- package/dist/types-cjs/form/DateTimeInput.d.ts +13 -3
- package/dist/types-cjs/form/Input.d.ts +16 -4
- package/dist/types-cjs/form/InputBase.d.ts +5 -2
- package/dist/types-cjs/form/Select.d.ts +8 -2
- package/dist/types-cjs/form/TextArea.d.ts +4 -2
- package/dist/types-cjs/form/TextAreaBase.d.ts +12 -3
- package/dist/types-cjs/form/TimeInput.d.ts +13 -3
- package/dist/types-cjs/form/TimeInputBase.d.ts +5 -1
- package/dist/types-cjs/form/_pickerInternals/Calendar.d.ts +10 -1
- package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +6 -0
- package/dist/types-cjs/helpers/Theme.d.ts +13 -4
- package/dist/types-cjs/helpers/portal.d.ts +2 -1
- package/dist/types-cjs/helpers/useAnchoredPosition.d.ts +6 -0
- package/dist/types-cjs/helpers/useFocusTrap.d.ts +2 -1
- package/dist/types-cjs/helpers/useOtherClasses.d.ts +13 -1
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/elements/_index.scss +2 -0
- package/src/scss/elements/_progress.scss +17 -0
- package/src/scss/elements/_skeleton.scss +21 -0
- package/src/scss/form/_picker-popover.scss +16 -0
- package/src/scss/form/_timeinput.scss +7 -2
package/dist/index.esm.js
CHANGED
|
@@ -2939,7 +2939,8 @@ function resolvePortalContainer(container) {
|
|
|
2939
2939
|
* Tab order and focus follow the DOM too. Content portaled from inside a
|
|
2940
2940
|
* focus trap is no longer inside the trapped element, so the trap neither
|
|
2941
2941
|
* reaches it nor holds focus there: render nested overlays inside the
|
|
2942
|
-
* trapped element, or give them a trap of their own.
|
|
2942
|
+
* trapped element, or give them a trap of their own. To trap portaled
|
|
2943
|
+
* content, put `useFocusTrap`'s ref on the element inside the `Portal`.
|
|
2943
2944
|
*
|
|
2944
2945
|
* @function
|
|
2945
2946
|
* @param {PortalProps} props - Props for the Portal component.
|
|
@@ -5195,7 +5196,8 @@ const focusedElement = (doc) => getDeepestActiveElement(doc) ?? doc.body;
|
|
|
5195
5196
|
* behaviour, and a modal also has to block the page behind it, which `Modal`
|
|
5196
5197
|
* does. Content the container renders through a portal lives elsewhere in
|
|
5197
5198
|
* the DOM, so it is outside the trap: render nested overlays inside the
|
|
5198
|
-
* container, or give them a trap of their own.
|
|
5199
|
+
* container, or give them a trap of their own. To trap content a `Portal`
|
|
5200
|
+
* renders, pass a ref to the element inside the `Portal` as `containerRef`.
|
|
5199
5201
|
*
|
|
5200
5202
|
* The container has to be in the DOM when the trap turns on. The trap waits
|
|
5201
5203
|
* for hydration, so a container that only appears once the page has
|
|
@@ -5312,6 +5314,15 @@ function useFocusTrap(containerRef, options = {}) {
|
|
|
5312
5314
|
const useClientLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
|
|
5313
5315
|
|
|
5314
5316
|
// INTERNAL: deliberately not exported from src/index.ts.
|
|
5317
|
+
/**
|
|
5318
|
+
* A `styledGap` for a panel whose stylesheet puts the gap in a margin on the
|
|
5319
|
+
* side facing the anchor, negative on a portaled panel above it, so the
|
|
5320
|
+
* larger magnitude is the gap. In pixels.
|
|
5321
|
+
*/
|
|
5322
|
+
function marginGap(panel) {
|
|
5323
|
+
const { marginTop, marginBottom } = getComputedStyle(panel);
|
|
5324
|
+
return Math.max(Math.abs(parseFloat(marginTop)) || 0, Math.abs(parseFloat(marginBottom)) || 0);
|
|
5325
|
+
}
|
|
5315
5326
|
/**
|
|
5316
5327
|
* The corner that keeps a panel of this size in the viewport, preferring
|
|
5317
5328
|
* below the anchor and lined up with its left edge.
|
|
@@ -5536,15 +5547,6 @@ function nearestDialog(event) {
|
|
|
5536
5547
|
return role === 'dialog' || role === 'alertdialog';
|
|
5537
5548
|
}) ?? null);
|
|
5538
5549
|
}
|
|
5539
|
-
/**
|
|
5540
|
-
* The gap the stylesheet puts between the panel and the trigger, in pixels.
|
|
5541
|
-
* It is a margin on whichever side faces the trigger, and a negative one on
|
|
5542
|
-
* a portaled panel above it, so the larger magnitude is the gap.
|
|
5543
|
-
*/
|
|
5544
|
-
function styledGap(panel) {
|
|
5545
|
-
const { marginTop, marginBottom } = getComputedStyle(panel);
|
|
5546
|
-
return Math.max(Math.abs(parseFloat(marginTop)) || 0, Math.abs(parseFloat(marginBottom)) || 0);
|
|
5547
|
-
}
|
|
5548
5550
|
/**
|
|
5549
5551
|
* The `Popover` component opens a panel of interactive content, such as a filter form or a set of share options, anchored to the element that toggles it.
|
|
5550
5552
|
*
|
|
@@ -5596,7 +5598,7 @@ const PopoverComponent = ({ trigger, children, open: controlledOpen, defaultOpen
|
|
|
5596
5598
|
// The gap comes from `--bulma-popover-offset` in the stylesheet, so the
|
|
5597
5599
|
// coordinates leave it out and `auto` reads it off the panel.
|
|
5598
5600
|
offset: 0,
|
|
5599
|
-
styledGap,
|
|
5601
|
+
styledGap: marginGap,
|
|
5600
5602
|
});
|
|
5601
5603
|
// The trigger renders first inside the wrapper. Read when needed rather
|
|
5602
5604
|
// than held, so a trigger that remounted is still the one found.
|
|
@@ -11060,7 +11062,7 @@ const TextAreaBase = forwardRef(({ color, size, isRounded, isStatic, isHovered,
|
|
|
11060
11062
|
TextAreaBase.displayName = 'TextAreaBase';
|
|
11061
11063
|
|
|
11062
11064
|
/**
|
|
11063
|
-
* The `Input` component provides a Bulma-styled text input, supporting colors, sizes, rounded corners, static
|
|
11065
|
+
* The `Input` component provides a Bulma-styled text input, supporting colors, sizes, rounded corners, static state, hover/focus/loading states, and all Bulma helper props.
|
|
11064
11066
|
*
|
|
11065
11067
|
* @function
|
|
11066
11068
|
* @param {InputProps} props - Props for Input.
|
|
@@ -11163,7 +11165,7 @@ fieldClassName, controlClassName,
|
|
|
11163
11165
|
Select.displayName = 'Select';
|
|
11164
11166
|
|
|
11165
11167
|
/**
|
|
11166
|
-
* The `TextArea` component provides a Bulma-styled multi-line text input, supporting color, size,
|
|
11168
|
+
* The `TextArea` component provides a Bulma-styled multi-line text input, supporting color, size, hover/focus/loading states, fixed size, and all Bulma helper props.
|
|
11167
11169
|
*
|
|
11168
11170
|
* @function
|
|
11169
11171
|
* @param {TextAreaProps} props - Props for TextArea.
|
|
@@ -11249,6 +11251,208 @@ const mergeLabels = (overrides) => ({
|
|
|
11249
11251
|
...(overrides ?? {}),
|
|
11250
11252
|
});
|
|
11251
11253
|
|
|
11254
|
+
function isSameDay(a, b) {
|
|
11255
|
+
return (a.getFullYear() === b.getFullYear() &&
|
|
11256
|
+
a.getMonth() === b.getMonth() &&
|
|
11257
|
+
a.getDate() === b.getDate());
|
|
11258
|
+
}
|
|
11259
|
+
function isSameMonth(a, b) {
|
|
11260
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
|
|
11261
|
+
}
|
|
11262
|
+
function isBefore(a, b) {
|
|
11263
|
+
return a.getTime() < b.getTime();
|
|
11264
|
+
}
|
|
11265
|
+
function isAfter(a, b) {
|
|
11266
|
+
return a.getTime() > b.getTime();
|
|
11267
|
+
}
|
|
11268
|
+
function isWithin(d, min, max) {
|
|
11269
|
+
if (min && isBefore(d, min))
|
|
11270
|
+
return false;
|
|
11271
|
+
if (max && isAfter(d, max))
|
|
11272
|
+
return false;
|
|
11273
|
+
return true;
|
|
11274
|
+
}
|
|
11275
|
+
// The boundaries build on `makeDate` (below), so a date in years 0–99 keeps
|
|
11276
|
+
// its year.
|
|
11277
|
+
function startOfDay(d) {
|
|
11278
|
+
return makeDate(d.getFullYear(), d.getMonth(), d.getDate());
|
|
11279
|
+
}
|
|
11280
|
+
function endOfDay(d) {
|
|
11281
|
+
const r = makeDate(d.getFullYear(), d.getMonth(), d.getDate());
|
|
11282
|
+
r.setHours(23, 59, 59, 999);
|
|
11283
|
+
return r;
|
|
11284
|
+
}
|
|
11285
|
+
function startOfMonth(d) {
|
|
11286
|
+
return makeDate(d.getFullYear(), d.getMonth());
|
|
11287
|
+
}
|
|
11288
|
+
/**
|
|
11289
|
+
* Local midnight on `year`-`month`-`day` (`month` from 0), like the
|
|
11290
|
+
* `Date(y, m, d)` constructor except that years 0–99 stay as given rather than
|
|
11291
|
+
* turning into 1900–1999.
|
|
11292
|
+
*/
|
|
11293
|
+
function makeDate(year, month = 0, day = 1) {
|
|
11294
|
+
const d = new Date(2000, 0, 1);
|
|
11295
|
+
d.setFullYear(year, month, day);
|
|
11296
|
+
return d;
|
|
11297
|
+
}
|
|
11298
|
+
/**
|
|
11299
|
+
* First instant of the day, month or year containing `d`. Works by setters on
|
|
11300
|
+
* a copy rather than the `Date(y, m, d)` constructor, which reads years 0–99
|
|
11301
|
+
* as 1900–1999: segmented typing passes through such years on the way to a
|
|
11302
|
+
* four-digit one.
|
|
11303
|
+
*/
|
|
11304
|
+
function startOfPeriod(d, granularity) {
|
|
11305
|
+
const r = new Date(d);
|
|
11306
|
+
if (granularity === 'year')
|
|
11307
|
+
r.setMonth(0, 1);
|
|
11308
|
+
else if (granularity === 'month')
|
|
11309
|
+
r.setDate(1);
|
|
11310
|
+
r.setHours(0, 0, 0, 0);
|
|
11311
|
+
return r;
|
|
11312
|
+
}
|
|
11313
|
+
/** Last instant of the day, month or year containing `d`. */
|
|
11314
|
+
function endOfPeriod(d, granularity) {
|
|
11315
|
+
const r = new Date(d);
|
|
11316
|
+
if (granularity === 'year')
|
|
11317
|
+
r.setMonth(11, 31);
|
|
11318
|
+
// Day 0 of the next month is this month's last day.
|
|
11319
|
+
else if (granularity === 'month')
|
|
11320
|
+
r.setMonth(r.getMonth() + 1, 0);
|
|
11321
|
+
r.setHours(23, 59, 59, 999);
|
|
11322
|
+
return r;
|
|
11323
|
+
}
|
|
11324
|
+
/**
|
|
11325
|
+
* Whether the day `d` falls outside `[min, max]` or is blocked by
|
|
11326
|
+
* `shouldDisableDate` / `unselectableDates`. This is the day grid's rule for a
|
|
11327
|
+
* disabled cell.
|
|
11328
|
+
*/
|
|
11329
|
+
function isDayUnselectable(d, c) {
|
|
11330
|
+
if (!isWithin(d, c.min, c.max))
|
|
11331
|
+
return true;
|
|
11332
|
+
if (c.shouldDisableDate?.(d))
|
|
11333
|
+
return true;
|
|
11334
|
+
if (c.unselectableDates?.some(u => isSameDay(u, d)))
|
|
11335
|
+
return true;
|
|
11336
|
+
return false;
|
|
11337
|
+
}
|
|
11338
|
+
/**
|
|
11339
|
+
* Whether every day of the month or year containing `d` is unselectable, so
|
|
11340
|
+
* the period as a whole can't be picked. Walks the period's days and stops at
|
|
11341
|
+
* the first selectable one. `min` and `max` count whole days here, so a `min`
|
|
11342
|
+
* of 3pm on a month's last day leaves that day, and so its month, selectable.
|
|
11343
|
+
*/
|
|
11344
|
+
function isPeriodUnselectable(d, granularity, c) {
|
|
11345
|
+
const days = {
|
|
11346
|
+
...c,
|
|
11347
|
+
min: c.min && startOfDay(c.min),
|
|
11348
|
+
max: c.max && endOfDay(c.max),
|
|
11349
|
+
};
|
|
11350
|
+
const start = startOfPeriod(d, granularity);
|
|
11351
|
+
const end = endOfPeriod(d, granularity).getTime();
|
|
11352
|
+
// A period wholly outside the bounds needs no walk.
|
|
11353
|
+
if (days.min && end < days.min.getTime())
|
|
11354
|
+
return true;
|
|
11355
|
+
if (days.max && start.getTime() > days.max.getTime())
|
|
11356
|
+
return true;
|
|
11357
|
+
for (let day = start; day.getTime() <= end; day = addDays(day, 1)) {
|
|
11358
|
+
if (!isDayUnselectable(day, days))
|
|
11359
|
+
return false;
|
|
11360
|
+
}
|
|
11361
|
+
return true;
|
|
11362
|
+
}
|
|
11363
|
+
function addDays(d, n) {
|
|
11364
|
+
const r = new Date(d);
|
|
11365
|
+
r.setDate(r.getDate() + n);
|
|
11366
|
+
return r;
|
|
11367
|
+
}
|
|
11368
|
+
function addMonths(d, n) {
|
|
11369
|
+
const r = new Date(d);
|
|
11370
|
+
const day = r.getDate();
|
|
11371
|
+
r.setDate(1);
|
|
11372
|
+
r.setMonth(r.getMonth() + n);
|
|
11373
|
+
// Clamp day to last day of new month if original day is out of range.
|
|
11374
|
+
const lastDay = makeDate(r.getFullYear(), r.getMonth() + 1, 0).getDate();
|
|
11375
|
+
r.setDate(Math.min(day, lastDay));
|
|
11376
|
+
return r;
|
|
11377
|
+
}
|
|
11378
|
+
function addYears(d, n) {
|
|
11379
|
+
return addMonths(d, n * 12);
|
|
11380
|
+
}
|
|
11381
|
+
function setTimeOfDay(d, parts) {
|
|
11382
|
+
const r = new Date(d);
|
|
11383
|
+
if (parts.hours !== undefined)
|
|
11384
|
+
r.setHours(parts.hours);
|
|
11385
|
+
if (parts.minutes !== undefined)
|
|
11386
|
+
r.setMinutes(parts.minutes);
|
|
11387
|
+
if (parts.seconds !== undefined)
|
|
11388
|
+
r.setSeconds(parts.seconds);
|
|
11389
|
+
return r;
|
|
11390
|
+
}
|
|
11391
|
+
function clampDate(d, min, max) {
|
|
11392
|
+
if (min && isBefore(d, min))
|
|
11393
|
+
return new Date(min);
|
|
11394
|
+
if (max && isAfter(d, max))
|
|
11395
|
+
return new Date(max);
|
|
11396
|
+
return new Date(d);
|
|
11397
|
+
}
|
|
11398
|
+
/**
|
|
11399
|
+
* Snap a Date's time-of-day to the nearest grid defined by the given
|
|
11400
|
+
* increment steps. Used by the "Now" button in pickers configured with
|
|
11401
|
+
* non-1 hour / minute / second steps so the committed time always lands on
|
|
11402
|
+
* a slot that exists on the wheel. Overflows roll up: e.g.
|
|
11403
|
+
* 13:58 with step=15 → 14:00. Seconds are zeroed when `enableSeconds` is
|
|
11404
|
+
* false, regardless of step.
|
|
11405
|
+
*/
|
|
11406
|
+
function snapTimeToIncrement(d, opts = {}) {
|
|
11407
|
+
const { incrementHours = 1, incrementMinutes = 1, incrementSeconds = 1, enableSeconds = false, } = opts;
|
|
11408
|
+
const r = new Date(d);
|
|
11409
|
+
let h = r.getHours();
|
|
11410
|
+
let m = r.getMinutes();
|
|
11411
|
+
let s = enableSeconds ? r.getSeconds() : 0;
|
|
11412
|
+
if (enableSeconds && incrementSeconds > 1) {
|
|
11413
|
+
s = Math.round(s / incrementSeconds) * incrementSeconds;
|
|
11414
|
+
if (s >= 60) {
|
|
11415
|
+
s -= 60;
|
|
11416
|
+
m += 1;
|
|
11417
|
+
}
|
|
11418
|
+
}
|
|
11419
|
+
if (incrementMinutes > 1) {
|
|
11420
|
+
m = Math.round(m / incrementMinutes) * incrementMinutes;
|
|
11421
|
+
if (m >= 60) {
|
|
11422
|
+
m -= 60;
|
|
11423
|
+
h += 1;
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
if (incrementHours > 1) {
|
|
11427
|
+
h = Math.round(h / incrementHours) * incrementHours;
|
|
11428
|
+
// setHours handles 24+ overflow naturally (rolls forward into next day).
|
|
11429
|
+
}
|
|
11430
|
+
r.setHours(h, m, s, 0);
|
|
11431
|
+
return r;
|
|
11432
|
+
}
|
|
11433
|
+
/**
|
|
11434
|
+
* Build a 6-week × 7-day grid (42 cells) anchored on the month containing
|
|
11435
|
+
* `monthAnchor`. The first cell is the start of the week containing the first
|
|
11436
|
+
* of the month, where the week starts on `firstDayOfWeek`.
|
|
11437
|
+
*/
|
|
11438
|
+
function buildMonthGrid(monthAnchor, firstDayOfWeek = 0) {
|
|
11439
|
+
const monthStart = startOfMonth(monthAnchor);
|
|
11440
|
+
const firstWeekday = monthStart.getDay();
|
|
11441
|
+
const offset = (firstWeekday - firstDayOfWeek + 7) % 7;
|
|
11442
|
+
const gridStart = addDays(monthStart, -offset);
|
|
11443
|
+
const today = startOfDay(new Date());
|
|
11444
|
+
const cells = [];
|
|
11445
|
+
for (let i = 0; i < 42; i++) {
|
|
11446
|
+
const date = addDays(gridStart, i);
|
|
11447
|
+
cells.push({
|
|
11448
|
+
date,
|
|
11449
|
+
inCurrentMonth: date.getMonth() === monthAnchor.getMonth(),
|
|
11450
|
+
isToday: isSameDay(date, today),
|
|
11451
|
+
});
|
|
11452
|
+
}
|
|
11453
|
+
return cells;
|
|
11454
|
+
}
|
|
11455
|
+
|
|
11252
11456
|
const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD';
|
|
11253
11457
|
const DEFAULT_MONTH_FORMAT = 'YYYY-MM';
|
|
11254
11458
|
const DEFAULT_YEAR_FORMAT = 'YYYY';
|
|
@@ -11367,7 +11571,8 @@ _locale) {
|
|
|
11367
11571
|
const hours = parts.hours ?? 0;
|
|
11368
11572
|
const minutes = parts.minutes ?? 0;
|
|
11369
11573
|
const seconds = parts.seconds ?? 0;
|
|
11370
|
-
const d =
|
|
11574
|
+
const d = makeDate(year, month, day);
|
|
11575
|
+
d.setHours(hours, minutes, seconds, 0);
|
|
11371
11576
|
if (d.getFullYear() !== year ||
|
|
11372
11577
|
d.getMonth() !== month ||
|
|
11373
11578
|
d.getDate() !== day) {
|
|
@@ -11533,204 +11738,11 @@ function getMonthNames(locale, length = 'long') {
|
|
|
11533
11738
|
return out;
|
|
11534
11739
|
}
|
|
11535
11740
|
|
|
11536
|
-
function isSameDay(a, b) {
|
|
11537
|
-
return (a.getFullYear() === b.getFullYear() &&
|
|
11538
|
-
a.getMonth() === b.getMonth() &&
|
|
11539
|
-
a.getDate() === b.getDate());
|
|
11540
|
-
}
|
|
11541
|
-
function isSameMonth(a, b) {
|
|
11542
|
-
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
|
|
11543
|
-
}
|
|
11544
|
-
function isBefore(a, b) {
|
|
11545
|
-
return a.getTime() < b.getTime();
|
|
11546
|
-
}
|
|
11547
|
-
function isAfter(a, b) {
|
|
11548
|
-
return a.getTime() > b.getTime();
|
|
11549
|
-
}
|
|
11550
|
-
function isWithin(d, min, max) {
|
|
11551
|
-
if (min && isBefore(d, min))
|
|
11552
|
-
return false;
|
|
11553
|
-
if (max && isAfter(d, max))
|
|
11554
|
-
return false;
|
|
11555
|
-
return true;
|
|
11556
|
-
}
|
|
11557
|
-
function startOfDay(d) {
|
|
11558
|
-
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
|
|
11559
|
-
}
|
|
11560
|
-
function endOfDay(d) {
|
|
11561
|
-
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
|
|
11562
|
-
}
|
|
11563
|
-
function startOfMonth(d) {
|
|
11564
|
-
return new Date(d.getFullYear(), d.getMonth(), 1, 0, 0, 0, 0);
|
|
11565
|
-
}
|
|
11566
|
-
/**
|
|
11567
|
-
* Local midnight on `year`-`month`-`day` (`month` from 0), like the
|
|
11568
|
-
* `Date(y, m, d)` constructor except that years 0–99 stay as given rather than
|
|
11569
|
-
* turning into 1900–1999.
|
|
11570
|
-
*/
|
|
11571
|
-
function makeDate(year, month = 0, day = 1) {
|
|
11572
|
-
const d = new Date(2000, 0, 1);
|
|
11573
|
-
d.setFullYear(year, month, day);
|
|
11574
|
-
return d;
|
|
11575
|
-
}
|
|
11576
|
-
/**
|
|
11577
|
-
* First instant of the day, month or year containing `d`. Works by setters on
|
|
11578
|
-
* a copy rather than the `Date(y, m, d)` constructor, which reads years 0–99
|
|
11579
|
-
* as 1900–1999: segmented typing passes through such years on the way to a
|
|
11580
|
-
* four-digit one.
|
|
11581
|
-
*/
|
|
11582
|
-
function startOfPeriod(d, granularity) {
|
|
11583
|
-
const r = new Date(d);
|
|
11584
|
-
if (granularity === 'year')
|
|
11585
|
-
r.setMonth(0, 1);
|
|
11586
|
-
else if (granularity === 'month')
|
|
11587
|
-
r.setDate(1);
|
|
11588
|
-
r.setHours(0, 0, 0, 0);
|
|
11589
|
-
return r;
|
|
11590
|
-
}
|
|
11591
|
-
/** Last instant of the day, month or year containing `d`. */
|
|
11592
|
-
function endOfPeriod(d, granularity) {
|
|
11593
|
-
const r = new Date(d);
|
|
11594
|
-
if (granularity === 'year')
|
|
11595
|
-
r.setMonth(11, 31);
|
|
11596
|
-
// Day 0 of the next month is this month's last day.
|
|
11597
|
-
else if (granularity === 'month')
|
|
11598
|
-
r.setMonth(r.getMonth() + 1, 0);
|
|
11599
|
-
r.setHours(23, 59, 59, 999);
|
|
11600
|
-
return r;
|
|
11601
|
-
}
|
|
11602
|
-
/**
|
|
11603
|
-
* Whether the day `d` falls outside `[min, max]` or is blocked by
|
|
11604
|
-
* `shouldDisableDate` / `unselectableDates`. This is the day grid's rule for a
|
|
11605
|
-
* disabled cell.
|
|
11606
|
-
*/
|
|
11607
|
-
function isDayUnselectable(d, c) {
|
|
11608
|
-
if (!isWithin(d, c.min, c.max))
|
|
11609
|
-
return true;
|
|
11610
|
-
if (c.shouldDisableDate?.(d))
|
|
11611
|
-
return true;
|
|
11612
|
-
if (c.unselectableDates?.some(u => isSameDay(u, d)))
|
|
11613
|
-
return true;
|
|
11614
|
-
return false;
|
|
11615
|
-
}
|
|
11616
|
-
/**
|
|
11617
|
-
* Whether every day of the month or year containing `d` is unselectable, so
|
|
11618
|
-
* the period as a whole can't be picked. Walks the period's days and stops at
|
|
11619
|
-
* the first selectable one. `min` and `max` count whole days here, so a `min`
|
|
11620
|
-
* of 3pm on a month's last day leaves that day, and so its month, selectable.
|
|
11621
|
-
*/
|
|
11622
|
-
function isPeriodUnselectable(d, granularity, c) {
|
|
11623
|
-
const days = {
|
|
11624
|
-
...c,
|
|
11625
|
-
min: c.min && startOfDay(c.min),
|
|
11626
|
-
max: c.max && endOfDay(c.max),
|
|
11627
|
-
};
|
|
11628
|
-
const start = startOfPeriod(d, granularity);
|
|
11629
|
-
const end = endOfPeriod(d, granularity).getTime();
|
|
11630
|
-
// A period wholly outside the bounds needs no walk.
|
|
11631
|
-
if (days.min && end < days.min.getTime())
|
|
11632
|
-
return true;
|
|
11633
|
-
if (days.max && start.getTime() > days.max.getTime())
|
|
11634
|
-
return true;
|
|
11635
|
-
for (let day = start; day.getTime() <= end; day = addDays(day, 1)) {
|
|
11636
|
-
if (!isDayUnselectable(day, days))
|
|
11637
|
-
return false;
|
|
11638
|
-
}
|
|
11639
|
-
return true;
|
|
11640
|
-
}
|
|
11641
|
-
function addDays(d, n) {
|
|
11642
|
-
const r = new Date(d);
|
|
11643
|
-
r.setDate(r.getDate() + n);
|
|
11644
|
-
return r;
|
|
11645
|
-
}
|
|
11646
|
-
function addMonths(d, n) {
|
|
11647
|
-
const r = new Date(d);
|
|
11648
|
-
const day = r.getDate();
|
|
11649
|
-
r.setDate(1);
|
|
11650
|
-
r.setMonth(r.getMonth() + n);
|
|
11651
|
-
// Clamp day to last day of new month if original day is out of range.
|
|
11652
|
-
const lastDay = new Date(r.getFullYear(), r.getMonth() + 1, 0).getDate();
|
|
11653
|
-
r.setDate(Math.min(day, lastDay));
|
|
11654
|
-
return r;
|
|
11655
|
-
}
|
|
11656
|
-
function addYears(d, n) {
|
|
11657
|
-
return addMonths(d, n * 12);
|
|
11658
|
-
}
|
|
11659
|
-
function setTimeOfDay(d, parts) {
|
|
11660
|
-
const r = new Date(d);
|
|
11661
|
-
if (parts.hours !== undefined)
|
|
11662
|
-
r.setHours(parts.hours);
|
|
11663
|
-
if (parts.minutes !== undefined)
|
|
11664
|
-
r.setMinutes(parts.minutes);
|
|
11665
|
-
if (parts.seconds !== undefined)
|
|
11666
|
-
r.setSeconds(parts.seconds);
|
|
11667
|
-
return r;
|
|
11668
|
-
}
|
|
11669
|
-
function clampDate(d, min, max) {
|
|
11670
|
-
if (min && isBefore(d, min))
|
|
11671
|
-
return new Date(min);
|
|
11672
|
-
if (max && isAfter(d, max))
|
|
11673
|
-
return new Date(max);
|
|
11674
|
-
return new Date(d);
|
|
11675
|
-
}
|
|
11676
|
-
/**
|
|
11677
|
-
* Snap a Date's time-of-day to the nearest grid defined by the given
|
|
11678
|
-
* increment steps. Used by the "Now" button in pickers configured with
|
|
11679
|
-
* non-1 hour / minute / second steps so the committed time always lands on
|
|
11680
|
-
* a slot that exists on the wheel. Overflows roll up: e.g.
|
|
11681
|
-
* 13:58 with step=15 → 14:00. Seconds are zeroed when `enableSeconds` is
|
|
11682
|
-
* false, regardless of step.
|
|
11683
|
-
*/
|
|
11684
|
-
function snapTimeToIncrement(d, opts = {}) {
|
|
11685
|
-
const { incrementHours = 1, incrementMinutes = 1, incrementSeconds = 1, enableSeconds = false, } = opts;
|
|
11686
|
-
const r = new Date(d);
|
|
11687
|
-
let h = r.getHours();
|
|
11688
|
-
let m = r.getMinutes();
|
|
11689
|
-
let s = enableSeconds ? r.getSeconds() : 0;
|
|
11690
|
-
if (enableSeconds && incrementSeconds > 1) {
|
|
11691
|
-
s = Math.round(s / incrementSeconds) * incrementSeconds;
|
|
11692
|
-
if (s >= 60) {
|
|
11693
|
-
s -= 60;
|
|
11694
|
-
m += 1;
|
|
11695
|
-
}
|
|
11696
|
-
}
|
|
11697
|
-
if (incrementMinutes > 1) {
|
|
11698
|
-
m = Math.round(m / incrementMinutes) * incrementMinutes;
|
|
11699
|
-
if (m >= 60) {
|
|
11700
|
-
m -= 60;
|
|
11701
|
-
h += 1;
|
|
11702
|
-
}
|
|
11703
|
-
}
|
|
11704
|
-
if (incrementHours > 1) {
|
|
11705
|
-
h = Math.round(h / incrementHours) * incrementHours;
|
|
11706
|
-
// setHours handles 24+ overflow naturally (rolls forward into next day).
|
|
11707
|
-
}
|
|
11708
|
-
r.setHours(h, m, s, 0);
|
|
11709
|
-
return r;
|
|
11710
|
-
}
|
|
11711
11741
|
/**
|
|
11712
|
-
*
|
|
11713
|
-
*
|
|
11714
|
-
* of the month, where the week starts on `firstDayOfWeek`.
|
|
11742
|
+
* Selects the calendar's focused cell, the one tab stop of whichever grid or
|
|
11743
|
+
* year list is on show, for a popover to put focus on as it opens.
|
|
11715
11744
|
*/
|
|
11716
|
-
|
|
11717
|
-
const monthStart = startOfMonth(monthAnchor);
|
|
11718
|
-
const firstWeekday = monthStart.getDay();
|
|
11719
|
-
const offset = (firstWeekday - firstDayOfWeek + 7) % 7;
|
|
11720
|
-
const gridStart = addDays(monthStart, -offset);
|
|
11721
|
-
const today = startOfDay(new Date());
|
|
11722
|
-
const cells = [];
|
|
11723
|
-
for (let i = 0; i < 42; i++) {
|
|
11724
|
-
const date = addDays(gridStart, i);
|
|
11725
|
-
cells.push({
|
|
11726
|
-
date,
|
|
11727
|
-
inCurrentMonth: date.getMonth() === monthAnchor.getMonth(),
|
|
11728
|
-
isToday: isSameDay(date, today),
|
|
11729
|
-
});
|
|
11730
|
-
}
|
|
11731
|
-
return cells;
|
|
11732
|
-
}
|
|
11733
|
-
|
|
11745
|
+
const CALENDAR_FOCUSED_CELL = '[data-focused="true"], [data-focused-year="true"]';
|
|
11734
11746
|
const BASE_VIEW = {
|
|
11735
11747
|
day: 'days',
|
|
11736
11748
|
month: 'months',
|
|
@@ -11776,20 +11788,32 @@ function monthStep(key, col) {
|
|
|
11776
11788
|
}
|
|
11777
11789
|
}
|
|
11778
11790
|
/**
|
|
11779
|
-
*
|
|
11780
|
-
*
|
|
11781
|
-
*
|
|
11791
|
+
* Whether DOM focus should follow a grid's tab stop as it moves: focus is in
|
|
11792
|
+
* the grid, or fell to <body> while the grid held it last, as it does when
|
|
11793
|
+
* the focused cell leaves the page or becomes disabled. Focus is read in the
|
|
11794
|
+
* grid's own tree, so this holds inside a shadow root too.
|
|
11795
|
+
*/
|
|
11796
|
+
function focusFollows(grid, heldLast) {
|
|
11797
|
+
const active = getActiveElementInTree(grid);
|
|
11798
|
+
if (grid.contains(active))
|
|
11799
|
+
return true;
|
|
11800
|
+
return heldLast && active === grid.ownerDocument.body;
|
|
11801
|
+
}
|
|
11802
|
+
/**
|
|
11803
|
+
* The cell nearest index `i` that `disabled` leaves enabled, `i` itself when
|
|
11804
|
+
* it is. At equal distance the later cell wins. With every cell disabled it
|
|
11805
|
+
* stays `i`, as there is nothing to reach.
|
|
11782
11806
|
*/
|
|
11783
|
-
function
|
|
11784
|
-
if (!disabled[
|
|
11785
|
-
return
|
|
11786
|
-
for (let d = 1; d <
|
|
11787
|
-
if (
|
|
11788
|
-
return
|
|
11789
|
-
if (
|
|
11790
|
-
return
|
|
11807
|
+
function nearestEnabled(i, disabled) {
|
|
11808
|
+
if (!disabled[i])
|
|
11809
|
+
return i;
|
|
11810
|
+
for (let d = 1; d < disabled.length; d++) {
|
|
11811
|
+
if (i + d < disabled.length && !disabled[i + d])
|
|
11812
|
+
return i + d;
|
|
11813
|
+
if (i - d >= 0 && !disabled[i - d])
|
|
11814
|
+
return i - d;
|
|
11791
11815
|
}
|
|
11792
|
-
return
|
|
11816
|
+
return i;
|
|
11793
11817
|
}
|
|
11794
11818
|
/**
|
|
11795
11819
|
* The year list's keyboard when it is the selection surface: arrows move a
|
|
@@ -11861,6 +11885,33 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
11861
11885
|
shouldDisableDate,
|
|
11862
11886
|
unselectableDates,
|
|
11863
11887
|
}), [min, max, shouldDisableDate, unselectableDates]);
|
|
11888
|
+
// The day grid's one tab stop. A disabled button can't take focus, so when
|
|
11889
|
+
// the focused day is disabled the stop moves to the nearest enabled day of
|
|
11890
|
+
// the month on show, and focusing it makes it the focused day. A nearby
|
|
11891
|
+
// month's day is left out, as focusing it would turn the grid to its month.
|
|
11892
|
+
const tabStopDay = useMemo(() => {
|
|
11893
|
+
const days = cells.filter(c => c.inCurrentMonth).map(c => c.date);
|
|
11894
|
+
const i = nearestEnabled(focusedDate.getDate() - 1, days.map(isDateUnselectable));
|
|
11895
|
+
return days[i];
|
|
11896
|
+
}, [cells, focusedDate, isDateUnselectable]);
|
|
11897
|
+
// Whether the grid on show holds focus, or held it last before focus went
|
|
11898
|
+
// nowhere: the cell that had it left the page or became disabled. A blur
|
|
11899
|
+
// that takes focus out of the grid, to nowhere included, is the user
|
|
11900
|
+
// leaving, so it clears this. A render that removes or disables the focused
|
|
11901
|
+
// cell sends no blur here: browsers fire none, or fire it inside React's
|
|
11902
|
+
// commit, where React delivers no events.
|
|
11903
|
+
const gridHeldFocusRef = useRef(false);
|
|
11904
|
+
const gridFocusHandlers = {
|
|
11905
|
+
onFocus: () => {
|
|
11906
|
+
gridHeldFocusRef.current = true;
|
|
11907
|
+
},
|
|
11908
|
+
onBlur: (e) => {
|
|
11909
|
+
const next = e.relatedTarget;
|
|
11910
|
+
if (!next || !e.currentTarget.contains(next)) {
|
|
11911
|
+
gridHeldFocusRef.current = false;
|
|
11912
|
+
}
|
|
11913
|
+
},
|
|
11914
|
+
};
|
|
11864
11915
|
// Step from `next` by `direction` days until we hit a selectable date,
|
|
11865
11916
|
// skipping past dates blocked by min/max, shouldDisableDate, or
|
|
11866
11917
|
// unselectableDates. If the whole searched range is disabled, focus stays
|
|
@@ -12024,6 +12075,16 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12024
12075
|
// Only the selection surface disables years; as navigation every listed
|
|
12025
12076
|
// year can be visited.
|
|
12026
12077
|
const disabledYears = useMemo(() => new Set(isYearGranularity ? yearList.filter(y => isYearUnselectable(y)) : []), [isYearGranularity, yearList, isYearUnselectable]);
|
|
12078
|
+
// The year list's one tab stop, after the month grid's: when the focused
|
|
12079
|
+
// year is disabled the stop moves to the nearest enabled year in the list,
|
|
12080
|
+
// and focusing it makes it the focused year. A year outside the list keeps
|
|
12081
|
+
// the stop, which then marks no option.
|
|
12082
|
+
const tabStopYear = useMemo(() => {
|
|
12083
|
+
const i = yearList.indexOf(focusedYear);
|
|
12084
|
+
if (i < 0)
|
|
12085
|
+
return focusedYear;
|
|
12086
|
+
return yearList[nearestEnabled(i, yearList.map(y => disabledYears.has(y)))];
|
|
12087
|
+
}, [yearList, focusedYear, disabledYears]);
|
|
12027
12088
|
// Which of the focused year's months have no selectable day.
|
|
12028
12089
|
const disabledMonths = useMemo(() => granularity === 'month'
|
|
12029
12090
|
? Array.from({ length: 12 }, (_, m) => isMonthUnselectable(makeDate(focusedYear, m)))
|
|
@@ -12031,7 +12092,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12031
12092
|
// The month grid's one tab stop. A disabled button can't take focus, so
|
|
12032
12093
|
// when the focused month is disabled the stop moves to the nearest enabled
|
|
12033
12094
|
// month, and focusing it makes it the focused month.
|
|
12034
|
-
const tabStopMonth =
|
|
12095
|
+
const tabStopMonth = nearestEnabled(focusedDate.getMonth(), disabledMonths);
|
|
12035
12096
|
const handleYearSelect = useCallback((year) => {
|
|
12036
12097
|
const next = new Date(focusedDate);
|
|
12037
12098
|
next.setFullYear(year);
|
|
@@ -12073,24 +12134,37 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12073
12134
|
e.preventDefault();
|
|
12074
12135
|
moveYearFocus(step[0], step[1]);
|
|
12075
12136
|
}, [isYearGranularity, baseView, focusedYear, yearList, moveYearFocus]);
|
|
12137
|
+
// The day or month grid takes focus as the popover opens on it, or comes
|
|
12138
|
+
// back to it from the year list.
|
|
12076
12139
|
useEffect(() => {
|
|
12077
|
-
if (
|
|
12140
|
+
if (!autoFocusCell)
|
|
12078
12141
|
return;
|
|
12079
|
-
|
|
12142
|
+
const grid = view === 'days'
|
|
12143
|
+
? gridRef.current
|
|
12144
|
+
: view === 'months'
|
|
12145
|
+
? monthGridRef.current
|
|
12146
|
+
: null;
|
|
12147
|
+
grid?.querySelector('[data-focused="true"]')?.focus();
|
|
12148
|
+
}, [autoFocusCell, view]);
|
|
12149
|
+
// From then on focus follows the tab stop only while the grid has it, or
|
|
12150
|
+
// has just lost it, so focus on a header button or a time wheel stays put.
|
|
12151
|
+
const tabStopDayTime = tabStopDay.getTime();
|
|
12152
|
+
useEffect(() => {
|
|
12153
|
+
const grid = gridRef.current;
|
|
12154
|
+
if (view !== 'days' || !grid)
|
|
12080
12155
|
return;
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
// the focused month while the keyboard moves through the grid.
|
|
12156
|
+
if (!focusFollows(grid, gridHeldFocusRef.current))
|
|
12157
|
+
return;
|
|
12158
|
+
grid.querySelector('[data-focused="true"]')?.focus();
|
|
12159
|
+
}, [tabStopDayTime, view]);
|
|
12086
12160
|
useEffect(() => {
|
|
12087
12161
|
const grid = monthGridRef.current;
|
|
12088
12162
|
if (view !== 'months' || !grid)
|
|
12089
12163
|
return;
|
|
12090
|
-
if (!
|
|
12164
|
+
if (!focusFollows(grid, gridHeldFocusRef.current))
|
|
12091
12165
|
return;
|
|
12092
12166
|
grid.querySelector('[data-focused="true"]')?.focus();
|
|
12093
|
-
}, [
|
|
12167
|
+
}, [focusedYear, tabStopMonth, view]);
|
|
12094
12168
|
// When the year view opens as navigation, scroll the focused year into
|
|
12095
12169
|
// view.
|
|
12096
12170
|
useEffect(() => {
|
|
@@ -12124,15 +12198,15 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12124
12198
|
const list = yearGridRef.current;
|
|
12125
12199
|
if (!isYearGranularity || !list)
|
|
12126
12200
|
return;
|
|
12127
|
-
if (!list.contains(
|
|
12201
|
+
if (!list.contains(getActiveElementInTree(list)))
|
|
12128
12202
|
return;
|
|
12129
12203
|
list.querySelector('[data-focused-year="true"]')?.focus();
|
|
12130
12204
|
}, [isYearGranularity, focusedYear]);
|
|
12131
12205
|
const navArrow = (d) => (jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: jsx("path", { d: d, fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
12132
|
-
return (jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => setView(v => (v === 'years' ? baseView : 'years')), children: [jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxs("div", { className: navGroupClass, children: [jsx("button", { type: "button", className: navPrevClass, "aria-label": isDayGranularity ? t.prevMonth : t.prevYear, disabled: prevDisabled || view !== baseView, onClick: () => onFocusedDateChange(prevAnchor), children: navArrow('M10 3l-5 5 5 5') }), jsx("button", { type: "button", className: navNextClass, "aria-label": isDayGranularity ? t.nextMonth : t.nextYear, disabled: nextDisabled || view !== baseView, onClick: () => onFocusedDateChange(nextAnchor), children: navArrow('M6 3l5 5-5 5') })] }))] }), view === 'days' && (jsxs(Fragment, { children: [jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsx("div", { className: dayNameClass, children: name }, i))) }), jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, className: gridClass, onKeyDown: handleKeyDown, children: cells.map(cell => {
|
|
12206
|
+
return (jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => setView(v => (v === 'years' ? baseView : 'years')), children: [jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxs("div", { className: navGroupClass, children: [jsx("button", { type: "button", className: navPrevClass, "aria-label": isDayGranularity ? t.prevMonth : t.prevYear, disabled: prevDisabled || view !== baseView, onClick: () => onFocusedDateChange(prevAnchor), children: navArrow('M10 3l-5 5 5 5') }), jsx("button", { type: "button", className: navNextClass, "aria-label": isDayGranularity ? t.nextMonth : t.nextYear, disabled: nextDisabled || view !== baseView, onClick: () => onFocusedDateChange(nextAnchor), children: navArrow('M6 3l5 5-5 5') })] }))] }), view === 'days' && (jsxs(Fragment, { children: [jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsx("div", { className: dayNameClass, children: name }, i))) }), jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, className: gridClass, onKeyDown: handleKeyDown, ...gridFocusHandlers, children: cells.map(cell => {
|
|
12133
12207
|
const disabled = isDateUnselectable(cell.date);
|
|
12134
12208
|
const isSelected = !!value && isSameDay(value, cell.date);
|
|
12135
|
-
const isFocused = isSameDay(cell.date,
|
|
12209
|
+
const isFocused = isSameDay(cell.date, tabStopDay);
|
|
12136
12210
|
const otherMonth = !cell.inCurrentMonth;
|
|
12137
12211
|
const cellClass = prefixedClassNames(classPrefix, 'dateinput-cell', {
|
|
12138
12212
|
'is-selected': isSelected,
|
|
@@ -12141,13 +12215,20 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12141
12215
|
'is-other-month': otherMonth,
|
|
12142
12216
|
});
|
|
12143
12217
|
const display = !otherMonth || nearbyMonthDays;
|
|
12144
|
-
return (jsx("button", { type: "button", role: "gridcell", tabIndex: isFocused ? 0 : -1, "aria-selected": isSelected, "aria-disabled": disabled, "aria-current": cell.isToday ? 'date' : undefined, "data-focused": isFocused ? 'true' : undefined, disabled: disabled || !display, className: cellClass,
|
|
12218
|
+
return (jsx("button", { type: "button", role: "gridcell", tabIndex: isFocused ? 0 : -1, "aria-selected": isSelected, "aria-disabled": disabled, "aria-current": cell.isToday ? 'date' : undefined, "data-focused": isFocused ? 'true' : undefined, disabled: disabled || !display, className: cellClass, onFocus: () => {
|
|
12219
|
+
// Reached by Tab while the focused day is disabled, or
|
|
12220
|
+
// by pointer: keys move on from here. A nearby month's
|
|
12221
|
+
// day waits for its click, which turns the grid.
|
|
12222
|
+
if (!otherMonth && !isSameDay(cell.date, focusedDate)) {
|
|
12223
|
+
onFocusedDateChange(cell.date);
|
|
12224
|
+
}
|
|
12225
|
+
}, onClick: () => {
|
|
12145
12226
|
if (disabled)
|
|
12146
12227
|
return;
|
|
12147
12228
|
onFocusedDateChange(cell.date);
|
|
12148
12229
|
onSelect(cell.date);
|
|
12149
12230
|
}, style: !display ? { visibility: 'hidden' } : undefined, children: cell.date.getDate() }, cell.date.toISOString()));
|
|
12150
|
-
}) })] })), view === 'months' && (jsx("div", { ref: monthGridRef, role: "grid", "aria-labelledby": labelId, className: monthsGridClass, onKeyDown: handleMonthKeyDown, children: MONTH_ROWS.map(row => (jsx("div", { role: "row", className: monthsRowClass, children: row.map(month => {
|
|
12231
|
+
}) })] })), view === 'months' && (jsx("div", { ref: monthGridRef, role: "grid", "aria-labelledby": labelId, className: monthsGridClass, onKeyDown: handleMonthKeyDown, ...gridFocusHandlers, children: MONTH_ROWS.map(row => (jsx("div", { role: "row", className: monthsRowClass, children: row.map(month => {
|
|
12151
12232
|
const date = makeDate(focusedYear, month);
|
|
12152
12233
|
const disabled = disabledMonths[month];
|
|
12153
12234
|
const isSelected = !!value && isSameMonth(value, date);
|
|
@@ -12169,7 +12250,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12169
12250
|
onSelect(date);
|
|
12170
12251
|
}, children: monthCellNames[month] }, month));
|
|
12171
12252
|
}) }, row[0]))) })), view === 'years' && (jsx("div", { ref: yearGridRef, className: yearsGridClass, role: "listbox", "aria-label": isYearGranularity ? t.chooseYear : monthLabel, onKeyDown: handleYearListKeyDown, children: yearList.map(year => {
|
|
12172
|
-
const isFocused = year ===
|
|
12253
|
+
const isFocused = year === tabStopYear;
|
|
12173
12254
|
const isToday = year === todayYear;
|
|
12174
12255
|
// As navigation the focused year is the current one. As the
|
|
12175
12256
|
// selection surface the value's year is selected and focus roams.
|
|
@@ -12182,7 +12263,13 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12182
12263
|
'is-today': isToday,
|
|
12183
12264
|
'is-disabled': disabled,
|
|
12184
12265
|
});
|
|
12185
|
-
return (jsx("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": isYearGranularity ? disabled : undefined, "aria-current": isYearGranularity && isToday ? 'date' : undefined, "data-focused-year": isFocused ? 'true' : undefined, tabIndex: isFocused ? 0 : -1, disabled: disabled, className: cellCls,
|
|
12266
|
+
return (jsx("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": isYearGranularity ? disabled : undefined, "aria-current": isYearGranularity && isToday ? 'date' : undefined, "data-focused-year": isFocused ? 'true' : undefined, tabIndex: isFocused ? 0 : -1, disabled: disabled, className: cellCls, onFocus: () => {
|
|
12267
|
+
// Reached by Tab while the focused year is disabled, or by
|
|
12268
|
+
// pointer: keys move on from here.
|
|
12269
|
+
if (isYearGranularity && year !== focusedYear) {
|
|
12270
|
+
onFocusedDateChange(clampDate(makeDate(year), min, max));
|
|
12271
|
+
}
|
|
12272
|
+
}, onClick: () => isYearGranularity
|
|
12186
12273
|
? handleYearPick(year)
|
|
12187
12274
|
: handleYearSelect(year), children: year }, year));
|
|
12188
12275
|
}) }))] }));
|
|
@@ -12190,12 +12277,26 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min, max,
|
|
|
12190
12277
|
Calendar.displayName = 'Calendar';
|
|
12191
12278
|
|
|
12192
12279
|
const isBrowser = typeof window !== 'undefined';
|
|
12193
|
-
const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'bottom-left', appendToBody = false, className, trapFocus = true, closeOnClickOutside = true, closeOnEscape = true, ariaLabel, ariaLabelledBy, role = 'dialog', id, restoreFocusRef, }) => {
|
|
12280
|
+
const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'bottom-left', appendToBody = false, className, trapFocus = true, closeOnClickOutside = true, closeOnEscape = true, ariaLabel, ariaLabelledBy, role = 'dialog', id, restoreFocusRef, initialFocusSelector, }) => {
|
|
12194
12281
|
const panelRef = useRef(null);
|
|
12282
|
+
// Read as the trap turns on, once the panel's content has rendered.
|
|
12283
|
+
const initialFocusRef = useMemo(() => initialFocusSelector === undefined
|
|
12284
|
+
? undefined
|
|
12285
|
+
: {
|
|
12286
|
+
get current() {
|
|
12287
|
+
return (panelRef.current?.querySelector(initialFocusSelector) ?? null);
|
|
12288
|
+
},
|
|
12289
|
+
}, [initialFocusSelector]);
|
|
12195
12290
|
const resolved = useAnchoredPosition(anchorRef, panelRef, {
|
|
12196
12291
|
active: isOpen,
|
|
12197
12292
|
position,
|
|
12198
12293
|
fixed: appendToBody,
|
|
12294
|
+
// A portaled panel takes its gap from `--bulma-picker-popover-offset` as
|
|
12295
|
+
// a margin, so its coordinates leave the gap out and `auto` reads it off
|
|
12296
|
+
// the panel. An in-place panel's gap is in its `top` or `bottom`, which
|
|
12297
|
+
// `auto` cannot read, so it keeps the hook's default estimate.
|
|
12298
|
+
offset: appendToBody ? 0 : undefined,
|
|
12299
|
+
styledGap: marginGap,
|
|
12199
12300
|
});
|
|
12200
12301
|
useEffect(() => {
|
|
12201
12302
|
if (!isOpen || !closeOnClickOutside || !isBrowser)
|
|
@@ -12229,6 +12330,7 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
12229
12330
|
}, [isOpen, closeOnEscape, onClose]);
|
|
12230
12331
|
useFocusTrap(panelRef, {
|
|
12231
12332
|
active: isOpen && trapFocus,
|
|
12333
|
+
initialFocusRef,
|
|
12232
12334
|
restoreFocus: restoreFocusRef ?? true,
|
|
12233
12335
|
});
|
|
12234
12336
|
const panelClass = usePrefixedClassNames('picker-popover', {
|
|
@@ -12408,8 +12510,11 @@ function buildSegmentMap(format) {
|
|
|
12408
12510
|
// ---------------------------------------------------------------------------
|
|
12409
12511
|
const wrap$1 = (n, max) => ((n % max) + max) % max;
|
|
12410
12512
|
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
12411
|
-
/**
|
|
12412
|
-
|
|
12513
|
+
/**
|
|
12514
|
+
* Number of days in the month of `d` (handles leap Februarys). Built on
|
|
12515
|
+
* `makeDate`, so a year below 100 keeps its own leap rule.
|
|
12516
|
+
*/
|
|
12517
|
+
const daysInMonth = (d) => makeDate(d.getFullYear(), d.getMonth() + 1, 0).getDate();
|
|
12413
12518
|
/**
|
|
12414
12519
|
* Returns a new Date with the given segment incremented (`delta` = +1 or -1).
|
|
12415
12520
|
* Wraps at segment boundaries: year is unbounded, month 0↔11 (in place, no
|
|
@@ -13017,16 +13122,19 @@ const toIsoValue = (d, granularity) => {
|
|
|
13017
13122
|
const month = `${year}-${pad2$1(d.getMonth() + 1)}`;
|
|
13018
13123
|
return granularity === 'month' ? month : `${month}-${pad2$1(d.getDate())}`;
|
|
13019
13124
|
};
|
|
13020
|
-
/**
|
|
13125
|
+
/**
|
|
13126
|
+
* Read a native `type="date"` or `type="month"` value back into a Date. HTML
|
|
13127
|
+
* allows a year of four or more digits, as `toIsoValue` writes one past 9999.
|
|
13128
|
+
*/
|
|
13021
13129
|
const fromIsoValue = (s, granularity) => {
|
|
13022
13130
|
if (granularity === 'month') {
|
|
13023
|
-
const m = /^(\d{4})-(\d{2})$/.exec(s);
|
|
13131
|
+
const m = /^(\d{4,})-(\d{2})$/.exec(s);
|
|
13024
13132
|
return m ? makeDate(Number(m[1]), Number(m[2]) - 1) : null;
|
|
13025
13133
|
}
|
|
13026
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
13134
|
+
const m = /^(\d{4,})-(\d{2})-(\d{2})$/.exec(s);
|
|
13027
13135
|
if (!m)
|
|
13028
13136
|
return null;
|
|
13029
|
-
return
|
|
13137
|
+
return makeDate(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
13030
13138
|
};
|
|
13031
13139
|
const DEFAULT_FORMATS = {
|
|
13032
13140
|
day: DEFAULT_DATE_FORMAT,
|
|
@@ -13090,16 +13198,20 @@ const DateInputBase = forwardRef((props, ref) => {
|
|
|
13090
13198
|
});
|
|
13091
13199
|
const containerClass = usePrefixedClassNames('dateinput-container');
|
|
13092
13200
|
const triggerClass = usePrefixedClassNames('dateinput-trigger');
|
|
13201
|
+
// What the latest request asked for, so a second request in the same
|
|
13202
|
+
// event sees it before React renders. The callbacks fire here rather than
|
|
13203
|
+
// in a state updater, which StrictMode calls twice, and after the state
|
|
13204
|
+
// is set, so a request a callback makes is the one that lands last.
|
|
13205
|
+
const requestedOpenRef = useRef(false);
|
|
13093
13206
|
const setOpen = useCallback((next) => {
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
});
|
|
13207
|
+
if (requestedOpenRef.current === next)
|
|
13208
|
+
return;
|
|
13209
|
+
requestedOpenRef.current = next;
|
|
13210
|
+
setOpenState(next);
|
|
13211
|
+
if (next)
|
|
13212
|
+
onOpen?.();
|
|
13213
|
+
else
|
|
13214
|
+
onClose?.();
|
|
13103
13215
|
}, [onOpen, onClose]);
|
|
13104
13216
|
// Sync displayed text with value when value changes externally.
|
|
13105
13217
|
useEffect(() => {
|
|
@@ -13244,14 +13356,14 @@ const DateInputBase = forwardRef((props, ref) => {
|
|
|
13244
13356
|
: t.chooseDate;
|
|
13245
13357
|
const calendar = (jsx(Calendar
|
|
13246
13358
|
// A new granularity starts the calendar over on its own view.
|
|
13247
|
-
, { granularity: granularity, value: value, focusedDate: focusedDate, onSelect: handleSelect, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size, id: popoverId
|
|
13359
|
+
, { granularity: granularity, value: value, focusedDate: focusedDate, onSelect: handleSelect, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size, id: `${popoverId}-cal`, autoFocusCell: open, labels: labels }, granularity));
|
|
13248
13360
|
if (inline) {
|
|
13249
13361
|
return (jsxs("div", { ...cleanRest, ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), children: [calendar, name && (jsx("input", { type: "hidden", name: name, form: form, value: value ? toIsoValue(value, granularity) : '', required: required }))] }));
|
|
13250
13362
|
}
|
|
13251
13363
|
return (jsxs("div", { ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), ...cleanRest, children: [jsx("input", { ref: combinedRef, type: "text", role: "combobox", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": popoverId, autoComplete: "off", className: inputClass, value: text, placeholder: placeholder, disabled: disabled, readOnly: inputReadOnlyAttr, name: name, form: form, required: required, id: id, ...inputHandlers }), popover && triggerIcon && (jsx("button", { type: "button", className: triggerClass, onClick: () => {
|
|
13252
13364
|
if (canOpen)
|
|
13253
13365
|
setOpen(!open);
|
|
13254
|
-
}, disabled: !canOpen, "aria-label": chooseLabel, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsx(PickerPopover, { isOpen: open, onClose: handlePopoverClose, anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: chooseLabel, id: popoverId, restoreFocusRef: inputRef, children: calendar }))] }));
|
|
13366
|
+
}, disabled: !canOpen, "aria-label": chooseLabel, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsx(PickerPopover, { isOpen: open, onClose: handlePopoverClose, anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: chooseLabel, id: popoverId, restoreFocusRef: inputRef, initialFocusSelector: CALENDAR_FOCUSED_CELL, children: calendar }))] }));
|
|
13255
13367
|
});
|
|
13256
13368
|
DateInputBase.displayName = 'DateInputBase';
|
|
13257
13369
|
|
|
@@ -14224,19 +14336,23 @@ const TimeInputBase = forwardRef((props, ref) => {
|
|
|
14224
14336
|
const mobileFooterClass = usePrefixedClassNames('timeinput-footer-mobile');
|
|
14225
14337
|
const mobileFooterResetClass = usePrefixedClassNames('timeinput-footer-reset');
|
|
14226
14338
|
const mobileFooterDoneClass = usePrefixedClassNames('timeinput-footer-done');
|
|
14339
|
+
// What the latest request asked for, so a second request in the same
|
|
14340
|
+
// event sees it before React renders. The callbacks fire here rather than
|
|
14341
|
+
// in a state updater, which StrictMode calls twice, and after the state
|
|
14342
|
+
// is set, so a request a callback makes is the one that lands last.
|
|
14343
|
+
const requestedOpenRef = useRef(false);
|
|
14227
14344
|
const setOpen = useCallback((next) => {
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
});
|
|
14345
|
+
if (requestedOpenRef.current === next)
|
|
14346
|
+
return;
|
|
14347
|
+
requestedOpenRef.current = next;
|
|
14348
|
+
setOpenState(next);
|
|
14349
|
+
if (next) {
|
|
14350
|
+
valueAtOpenRef.current = value;
|
|
14351
|
+
onOpen?.();
|
|
14352
|
+
}
|
|
14353
|
+
else {
|
|
14354
|
+
onClose?.();
|
|
14355
|
+
}
|
|
14240
14356
|
}, [onOpen, onClose, value]);
|
|
14241
14357
|
useEffect(() => {
|
|
14242
14358
|
setText(value ? formatTime(value, defaultFormat, locale) : '');
|
|
@@ -14392,8 +14508,9 @@ const TimeInput = forwardRef(({ label, labelSize, labelProps, horizontal, iconLe
|
|
|
14392
14508
|
});
|
|
14393
14509
|
TimeInput.displayName = 'TimeInput';
|
|
14394
14510
|
|
|
14511
|
+
// The year is padded to four digits, as `datetime-local` requires.
|
|
14395
14512
|
const toIsoDateTime = (d, withSeconds) => {
|
|
14396
|
-
const yyyy = d.getFullYear();
|
|
14513
|
+
const yyyy = String(d.getFullYear()).padStart(4, '0');
|
|
14397
14514
|
const mo = String(d.getMonth() + 1).padStart(2, '0');
|
|
14398
14515
|
const dd = String(d.getDate()).padStart(2, '0');
|
|
14399
14516
|
const hh = String(d.getHours()).padStart(2, '0');
|
|
@@ -14406,10 +14523,13 @@ const toIsoDateTime = (d, withSeconds) => {
|
|
|
14406
14523
|
const fromIsoDateTime = (s) => {
|
|
14407
14524
|
// The HTML datetime-local value may carry fractional seconds (the spec
|
|
14408
14525
|
// allows them and some engines normalize to `:ss.sss`); accept and drop.
|
|
14409
|
-
|
|
14526
|
+
// Its year is four or more digits, as `toIsoDateTime` writes one past 9999.
|
|
14527
|
+
const m = /^(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?$/.exec(s);
|
|
14410
14528
|
if (!m)
|
|
14411
14529
|
return null;
|
|
14412
|
-
|
|
14530
|
+
const d = makeDate(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
14531
|
+
d.setHours(Number(m[4]), Number(m[5]), m[6] ? Number(m[6]) : 0, 0);
|
|
14532
|
+
return d;
|
|
14413
14533
|
};
|
|
14414
14534
|
/**
|
|
14415
14535
|
* Raw DateTimeInput — combined input + calendar + time spinner without
|
|
@@ -14496,19 +14616,23 @@ const DateTimeInputBase = forwardRef((props, ref) => {
|
|
|
14496
14616
|
const footerResetClass = usePrefixedClassNames('datetimeinput-footer-reset');
|
|
14497
14617
|
const footerDoneClass = usePrefixedClassNames('datetimeinput-footer-done');
|
|
14498
14618
|
const footerTimePillClass = usePrefixedClassNames('datetimeinput-footer-time-pill');
|
|
14619
|
+
// What the latest request asked for, so a second request in the same event
|
|
14620
|
+
// sees it before React renders. The callbacks fire here rather than in a
|
|
14621
|
+
// state updater, which StrictMode calls twice, and after the state is set,
|
|
14622
|
+
// so a request a callback makes is the one that lands last.
|
|
14623
|
+
const requestedOpenRef = useRef(false);
|
|
14499
14624
|
const setOpen = useCallback((next) => {
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
});
|
|
14625
|
+
if (requestedOpenRef.current === next)
|
|
14626
|
+
return;
|
|
14627
|
+
requestedOpenRef.current = next;
|
|
14628
|
+
setOpenState(next);
|
|
14629
|
+
if (next) {
|
|
14630
|
+
valueAtOpenRef.current = value;
|
|
14631
|
+
onOpen?.();
|
|
14632
|
+
}
|
|
14633
|
+
else {
|
|
14634
|
+
onClose?.();
|
|
14635
|
+
}
|
|
14512
14636
|
}, [onOpen, onClose, value]);
|
|
14513
14637
|
useEffect(() => {
|
|
14514
14638
|
setText(value ? formatDateTime(value, defaultFormat, locale) : '');
|
|
@@ -14638,7 +14762,7 @@ const DateTimeInputBase = forwardRef((props, ref) => {
|
|
|
14638
14762
|
return (jsxs("div", { ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), ...cleanRest, children: [jsx("input", { ref: combinedRef, type: "text", role: "combobox", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": popoverId, autoComplete: "off", className: inputClass, value: text, placeholder: placeholder, disabled: disabled, readOnly: inputReadOnlyAttr, name: name, form: form, required: required, id: id, ...inputHandlers }), popover && triggerIcon && (jsx("button", { type: "button", className: triggerClass, onClick: () => {
|
|
14639
14763
|
if (canOpen)
|
|
14640
14764
|
setOpen(!open);
|
|
14641
|
-
}, disabled: !canOpen, "aria-label": t.chooseDateTime, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsx(PickerPopover, { isOpen: open, onClose: () => setOpen(false), anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseDateTime, id: popoverId, restoreFocusRef: inputRef, children: panel }))] }));
|
|
14765
|
+
}, disabled: !canOpen, "aria-label": t.chooseDateTime, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsx(PickerPopover, { isOpen: open, onClose: () => setOpen(false), anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseDateTime, id: popoverId, restoreFocusRef: inputRef, initialFocusSelector: CALENDAR_FOCUSED_CELL, children: panel }))] }));
|
|
14642
14766
|
});
|
|
14643
14767
|
DateTimeInputBase.displayName = 'DateTimeInputBase';
|
|
14644
14768
|
|
|
@@ -15620,31 +15744,57 @@ const Theme = ({ bulmaVars = {}, children, className, isRoot = false, colorMode,
|
|
|
15620
15744
|
// rewrites the shared element in place rather than removing and recreating
|
|
15621
15745
|
// it between the cleanup and the next run.
|
|
15622
15746
|
useEffect(() => () => setRootThemeRules(rootOrder, ''), [rootOrder]);
|
|
15623
|
-
//
|
|
15624
|
-
//
|
|
15625
|
-
// `
|
|
15747
|
+
// Bulma names its scheme attribute after the class prefix a stylesheet was
|
|
15748
|
+
// built with (`data-<prefix>theme`), so the prefixed builds read
|
|
15749
|
+
// `data-bestax-theme` and never `data-theme`.
|
|
15750
|
+
const classPrefix = useClassPrefix();
|
|
15751
|
+
// Toggle Bulma's light/dark scheme by writing the theme attribute on the
|
|
15752
|
+
// document root (<html>). This is always global, even on a scoped Theme.
|
|
15753
|
+
// Under a class prefix the prefixed attribute is written as well as
|
|
15754
|
+
// `data-theme`, so whichever build is loaded sees it; with no prefix only
|
|
15755
|
+
// `data-theme` is. `'system'` removes them so Bulma follows the OS
|
|
15756
|
+
// preference.
|
|
15626
15757
|
useEffect(() => {
|
|
15627
15758
|
if (colorMode === undefined) {
|
|
15628
15759
|
return;
|
|
15629
15760
|
}
|
|
15630
15761
|
const root = document.documentElement;
|
|
15631
|
-
const
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
|
|
15638
|
-
|
|
15639
|
-
return () => {
|
|
15640
|
-
if (previous === null) {
|
|
15641
|
-
root.removeAttribute('data-theme');
|
|
15762
|
+
const names = classPrefix
|
|
15763
|
+
? ['data-theme', `data-${classPrefix}theme`]
|
|
15764
|
+
: ['data-theme'];
|
|
15765
|
+
const previous = names.map(name => root.getAttribute(name));
|
|
15766
|
+
for (const name of names) {
|
|
15767
|
+
if (colorMode === 'system') {
|
|
15768
|
+
root.removeAttribute(name);
|
|
15769
|
+
continue;
|
|
15642
15770
|
}
|
|
15643
|
-
|
|
15644
|
-
root.setAttribute(
|
|
15771
|
+
try {
|
|
15772
|
+
root.setAttribute(name, colorMode);
|
|
15773
|
+
}
|
|
15774
|
+
catch {
|
|
15775
|
+
// A class prefix can hold characters an attribute name cannot.
|
|
15776
|
+
// `data-theme` is still written, as it was before the prefixed
|
|
15777
|
+
// attribute existed, but a sheet built with that prefix keeps its
|
|
15778
|
+
// scheme, so say so.
|
|
15779
|
+
warnOnce('Theme:color-mode-prefix', `[bestax-bulma] <Theme colorMode="${colorMode}">: the class ` +
|
|
15780
|
+
`prefix "${classPrefix}" makes ${name} an invalid attribute ` +
|
|
15781
|
+
'name, so only data-theme is written, and a stylesheet built ' +
|
|
15782
|
+
'with that prefix keeps its scheme.');
|
|
15645
15783
|
}
|
|
15784
|
+
}
|
|
15785
|
+
// Restore the previous values when colorMode changes or the component unmounts.
|
|
15786
|
+
return () => {
|
|
15787
|
+
names.forEach((name, i) => {
|
|
15788
|
+
const value = previous[i];
|
|
15789
|
+
if (value === null) {
|
|
15790
|
+
root.removeAttribute(name);
|
|
15791
|
+
}
|
|
15792
|
+
else {
|
|
15793
|
+
root.setAttribute(name, value);
|
|
15794
|
+
}
|
|
15795
|
+
});
|
|
15646
15796
|
};
|
|
15647
|
-
}, [colorMode]);
|
|
15797
|
+
}, [colorMode, classPrefix]);
|
|
15648
15798
|
// For local injection (when isRoot is false), prepare style object for CSS vars
|
|
15649
15799
|
const style = useMemo(() => {
|
|
15650
15800
|
if (isRoot) {
|