@allxsmith/bestax-bulma 5.26.5 → 5.27.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/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 +974 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +973 -97
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Tabs.d.ts +25 -3
- package/dist/types/form/DateInputBase.d.ts +5 -2
- package/dist/types/form/DateRangeInput.d.ts +91 -0
- package/dist/types/form/DateRangeInputBase.d.ts +172 -0
- package/dist/types/form/DateTimeInputBase.d.ts +7 -2
- package/dist/types/form/File.d.ts +15 -2
- package/dist/types/form/FormContext.d.ts +11 -0
- package/dist/types/form/Numberinput.d.ts +8 -1
- package/dist/types/form/TimeInputBase.d.ts +1 -1
- package/dist/types/form/_pickerInternals/Calendar.d.ts +45 -4
- package/dist/types/form/_pickerInternals/PickerPopover.d.ts +1 -0
- package/dist/types/form/_pickerInternals/dateUtils.d.ts +24 -0
- package/dist/types/form/_pickerInternals/formatters.d.ts +14 -0
- package/dist/types/form/_pickerInternals/pickerTypes.d.ts +27 -0
- package/dist/types/form/_pickerInternals/useSegmentedEntry.d.ts +12 -3
- package/dist/types/helpers/statusRegion.d.ts +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types-cjs/components/Tabs.d.ts +25 -3
- package/dist/types-cjs/form/DateInputBase.d.ts +5 -2
- package/dist/types-cjs/form/DateRangeInput.d.ts +91 -0
- package/dist/types-cjs/form/DateRangeInputBase.d.ts +172 -0
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +7 -2
- package/dist/types-cjs/form/File.d.ts +15 -2
- package/dist/types-cjs/form/FormContext.d.ts +11 -0
- package/dist/types-cjs/form/Numberinput.d.ts +8 -1
- package/dist/types-cjs/form/TimeInputBase.d.ts +1 -1
- package/dist/types-cjs/form/_pickerInternals/Calendar.d.ts +45 -4
- package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +1 -0
- package/dist/types-cjs/form/_pickerInternals/dateUtils.d.ts +24 -0
- package/dist/types-cjs/form/_pickerInternals/formatters.d.ts +14 -0
- package/dist/types-cjs/form/_pickerInternals/pickerTypes.d.ts +27 -0
- package/dist/types-cjs/form/_pickerInternals/useSegmentedEntry.d.ts +12 -3
- package/dist/types-cjs/helpers/statusRegion.d.ts +1 -0
- package/dist/types-cjs/index.d.ts +4 -0
- 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/components/_tabs.scss +44 -0
- package/src/scss/form/_dateinput.scss +58 -3
- package/src/scss/form/_daterangeinput.scss +63 -0
- package/src/scss/form/_file.scss +37 -0
- package/src/scss/form/_index.scss +2 -0
package/dist/index.cjs
CHANGED
|
@@ -4141,9 +4141,65 @@ const TabsContext = React.createContext(null);
|
|
|
4141
4141
|
function useTabsContext() {
|
|
4142
4142
|
return React.useContext(TabsContext);
|
|
4143
4143
|
}
|
|
4144
|
+
/**
|
|
4145
|
+
* The tabs and panels currently mounted under one `Tabs`. Each registers from
|
|
4146
|
+
* an effect and removes itself on cleanup, so the links between them only
|
|
4147
|
+
* ever name elements that exist, wherever in the tree the panels sit.
|
|
4148
|
+
*/
|
|
4149
|
+
function useRegistry() {
|
|
4150
|
+
const [records, setRecords] = React.useState([]);
|
|
4151
|
+
const register = React.useCallback((record) => {
|
|
4152
|
+
setRecords(prev => [...prev, record]);
|
|
4153
|
+
return () => setRecords(prev => prev.filter(r => r !== record));
|
|
4154
|
+
}, []);
|
|
4155
|
+
return [records, register];
|
|
4156
|
+
}
|
|
4157
|
+
/**
|
|
4158
|
+
* The list a tab belongs to: its `role="tablist"` ancestor, so an element
|
|
4159
|
+
* wrapped around a tab doesn't cut it off from the others, or its parent when
|
|
4160
|
+
* it sits outside a tab list.
|
|
4161
|
+
*/
|
|
4162
|
+
function tabListOf(tab) {
|
|
4163
|
+
return (tab.closest('[role="tablist"]') ?? tab.parentElement);
|
|
4164
|
+
}
|
|
4165
|
+
/**
|
|
4166
|
+
* The tab a key press moves focus to, found among the tabs of the same list
|
|
4167
|
+
* in DOM order, skipping disabled ones and wrapping at either end.
|
|
4168
|
+
* `undefined` for a key the tab list does not handle, or when no enabled tab
|
|
4169
|
+
* is left to go to.
|
|
4170
|
+
*/
|
|
4171
|
+
function findTargetTab(current, key, vertical) {
|
|
4172
|
+
const tabs = Array.from(tabListOf(current).querySelectorAll('[role="tab"]'));
|
|
4173
|
+
const isEnabled = (el) => el.getAttribute('aria-disabled') !== 'true';
|
|
4174
|
+
if (key === 'Home')
|
|
4175
|
+
return tabs.find(isEnabled);
|
|
4176
|
+
if (key === 'End')
|
|
4177
|
+
return [...tabs].reverse().find(isEnabled);
|
|
4178
|
+
let step;
|
|
4179
|
+
if (key === 'ArrowRight' || (vertical && key === 'ArrowDown'))
|
|
4180
|
+
step = 1;
|
|
4181
|
+
else if (key === 'ArrowLeft' || (vertical && key === 'ArrowUp'))
|
|
4182
|
+
step = -1;
|
|
4183
|
+
else
|
|
4184
|
+
return undefined;
|
|
4185
|
+
const start = tabs.indexOf(current);
|
|
4186
|
+
for (let i = 1; i <= tabs.length; i++) {
|
|
4187
|
+
const candidate = tabs[(((start + step * i) % tabs.length) + tabs.length) % tabs.length];
|
|
4188
|
+
if (isEnabled(candidate))
|
|
4189
|
+
return candidate;
|
|
4190
|
+
}
|
|
4191
|
+
return undefined;
|
|
4192
|
+
}
|
|
4144
4193
|
/**
|
|
4145
4194
|
* The `Tabs` component provides flexible and fully-featured Bulma tab navigation for your Bulma React UI.
|
|
4146
4195
|
*
|
|
4196
|
+
* Built from `Tabs.Tab`, the tabs follow the WAI-ARIA tabs pattern with
|
|
4197
|
+
* manual activation: the tab list is a single tab stop, the arrow keys, Home
|
|
4198
|
+
* and End move focus between tabs (skipping disabled ones), and Enter or
|
|
4199
|
+
* Space activates the focused tab. Each tab names its `Tabs.Content.Item` in
|
|
4200
|
+
* `aria-controls`, and each panel names its tab in `aria-labelledby`. Built
|
|
4201
|
+
* from `Tabs.Item` with links inside, the tabs stay plain Bulma navigation.
|
|
4202
|
+
*
|
|
4147
4203
|
* @function
|
|
4148
4204
|
* @param {TabsProps} props - Props for the Tabs component.
|
|
4149
4205
|
* @returns {JSX.Element} The rendered tabs component.
|
|
@@ -4175,7 +4231,45 @@ const TabsComponent = ({ align, size, isFullwidth, isFullWidth, fullwidth, boxed
|
|
|
4175
4231
|
}
|
|
4176
4232
|
onChange?.(index);
|
|
4177
4233
|
}, [isControlled, onChange]);
|
|
4178
|
-
const
|
|
4234
|
+
const baseId = React.useId();
|
|
4235
|
+
const [tabs, registerTab] = useRegistry();
|
|
4236
|
+
const [panels, registerPanel] = useRegistry();
|
|
4237
|
+
const [focusedTab, setFocusedTab] = React.useState(null);
|
|
4238
|
+
// One tab stop for the whole list (roving tabindex). While focus is inside
|
|
4239
|
+
// the list the stop follows it, so Tab and Shift+Tab leave the list from
|
|
4240
|
+
// whichever tab has focus; once focus leaves, the stop is the selected tab
|
|
4241
|
+
// again. A disabled tab never holds it, and when the selected tab is
|
|
4242
|
+
// disabled or missing the enabled tab with the lowest index does, so the
|
|
4243
|
+
// list can always be reached. Before the tabs register (the server render
|
|
4244
|
+
// and the first client render), the selected tab holds it. The registry is
|
|
4245
|
+
// in the order the tabs last registered, which a tab changing its
|
|
4246
|
+
// `disabled` or `id` reshuffles, hence the sort.
|
|
4247
|
+
const enabledTabs = tabs
|
|
4248
|
+
.filter(t => !t.disabled)
|
|
4249
|
+
.sort((a, b) => a.index - b.index);
|
|
4250
|
+
const canHoldStop = (index) => enabledTabs.some(t => t.index === index);
|
|
4251
|
+
let tabStop;
|
|
4252
|
+
if (canHoldStop(focusedTab))
|
|
4253
|
+
tabStop = focusedTab;
|
|
4254
|
+
else if (tabs.length === 0 || canHoldStop(activeTab))
|
|
4255
|
+
tabStop = activeTab;
|
|
4256
|
+
else
|
|
4257
|
+
tabStop = enabledTabs.length > 0 ? enabledTabs[0].index : null;
|
|
4258
|
+
// Check if children include TabsContent
|
|
4259
|
+
const childArray = React.Children.toArray(children);
|
|
4260
|
+
const hasContent = childArray.some(child => React.isValidElement(child) && child.type === TabsContent);
|
|
4261
|
+
const contextValue = {
|
|
4262
|
+
activeTab,
|
|
4263
|
+
setActiveTab,
|
|
4264
|
+
baseId,
|
|
4265
|
+
tabStop,
|
|
4266
|
+
setFocusedTab,
|
|
4267
|
+
tabId: index => tabs.find(t => t.index === index)?.id,
|
|
4268
|
+
panelId: index => panels.find(p => p.index === index)?.id,
|
|
4269
|
+
registerTab,
|
|
4270
|
+
registerPanel,
|
|
4271
|
+
orientation: hasContent && vertical ? 'vertical' : 'horizontal',
|
|
4272
|
+
};
|
|
4179
4273
|
// Build classes for the .tabs div
|
|
4180
4274
|
const tabsClasses = usePrefixedClassNames('tabs', {
|
|
4181
4275
|
[`is-${align}`]: align,
|
|
@@ -4186,9 +4280,6 @@ const TabsComponent = ({ align, size, isFullwidth, isFullWidth, fullwidth, boxed
|
|
|
4186
4280
|
'is-toggle': toggle,
|
|
4187
4281
|
'is-toggle-rounded': rounded,
|
|
4188
4282
|
});
|
|
4189
|
-
// Check if children include TabsContent
|
|
4190
|
-
const childArray = React.Children.toArray(children);
|
|
4191
|
-
const hasContent = childArray.some(child => React.isValidElement(child) && child.type === TabsContent);
|
|
4192
4283
|
// Hoisted unconditionally to respect rules-of-hooks. Modifiers gate themselves
|
|
4193
4284
|
// via their truthy values — `tabs-root` is always prefixed, modifiers only
|
|
4194
4285
|
// apply in the vertical-with-content branch.
|
|
@@ -4237,26 +4328,82 @@ const TabsComponent = ({ align, size, isFullwidth, isFullWidth, fullwidth, boxed
|
|
|
4237
4328
|
* @param {TabListProps} props - Props for the TabList component.
|
|
4238
4329
|
* @returns {JSX.Element} The rendered tab list.
|
|
4239
4330
|
*/
|
|
4240
|
-
const TabList = ({ className, children, ...props }) =>
|
|
4331
|
+
const TabList = ({ className, children, ...props }) => {
|
|
4332
|
+
const ctx = useTabsContext();
|
|
4333
|
+
return (jsxRuntime.jsx("ul", { role: "tablist", "aria-orientation": ctx?.orientation === 'vertical' ? 'vertical' : undefined, className: classNames(className) || undefined, ...props, children: children }));
|
|
4334
|
+
};
|
|
4241
4335
|
/**
|
|
4242
4336
|
* Individual tab button. Consumes Tabs context for active state management.
|
|
4243
4337
|
* Renders `<a>` internally — consumers provide only the label text/children.
|
|
4338
|
+
* Inside a `Tabs` it takes part in the tab list's keyboard support and links
|
|
4339
|
+
* to the `Tabs.Content.Item` with the same `index`.
|
|
4244
4340
|
*
|
|
4245
4341
|
* @function
|
|
4246
4342
|
* @param {TabProps} props - Props for the Tab component.
|
|
4247
4343
|
* @returns {JSX.Element} The rendered tab.
|
|
4248
4344
|
*/
|
|
4249
|
-
const Tab = ({ index, disabled, icon, iconLibrary, iconVariant, iconSize = 'small', iconFeatures, className, children, ...props }) => {
|
|
4345
|
+
const Tab = ({ index, disabled, icon, iconLibrary, iconVariant, iconSize = 'small', iconFeatures, className, children, id, onKeyDown, onFocus, onBlur, ...props }) => {
|
|
4250
4346
|
const ctx = useTabsContext();
|
|
4251
4347
|
const isActive = ctx ? ctx.activeTab === index : false;
|
|
4348
|
+
const anchorRef = React.useRef(null);
|
|
4252
4349
|
const activeClass = usePrefixedClassNames({ 'is-active': isActive });
|
|
4350
|
+
const tabId = id ?? (ctx ? `${ctx.baseId}-tab-${index}` : undefined);
|
|
4351
|
+
const registerTab = ctx?.registerTab;
|
|
4352
|
+
React.useEffect(() => {
|
|
4353
|
+
if (!registerTab || tabId === undefined)
|
|
4354
|
+
return undefined;
|
|
4355
|
+
return registerTab({ index, id: tabId, disabled: !!disabled });
|
|
4356
|
+
}, [registerTab, index, tabId, disabled]);
|
|
4357
|
+
// Outside a Tabs there is no list to rove, so every enabled tab stays in
|
|
4358
|
+
// the page tab order, as it always did.
|
|
4359
|
+
let tabIndex;
|
|
4360
|
+
if (disabled)
|
|
4361
|
+
tabIndex = -1;
|
|
4362
|
+
else if (ctx)
|
|
4363
|
+
tabIndex = ctx.tabStop === index ? 0 : -1;
|
|
4364
|
+
else
|
|
4365
|
+
tabIndex = 0;
|
|
4253
4366
|
const handleClick = React.useCallback((e) => {
|
|
4254
4367
|
e.preventDefault();
|
|
4255
4368
|
if (!disabled && ctx) {
|
|
4256
4369
|
ctx.setActiveTab(index);
|
|
4257
4370
|
}
|
|
4258
4371
|
}, [disabled, ctx, index]);
|
|
4259
|
-
|
|
4372
|
+
const handleKeyDown = (e) => {
|
|
4373
|
+
onKeyDown?.(e);
|
|
4374
|
+
// A key on something inside the tab is that element's to answer.
|
|
4375
|
+
if (e.defaultPrevented ||
|
|
4376
|
+
e.target !== e.currentTarget ||
|
|
4377
|
+
e.altKey ||
|
|
4378
|
+
e.ctrlKey ||
|
|
4379
|
+
e.metaKey) {
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
4383
|
+
e.preventDefault();
|
|
4384
|
+
// Activate through a click on the inner <a>, the element a pointer
|
|
4385
|
+
// clicks, so click listeners see keyboard activation the way they see
|
|
4386
|
+
// a native button's.
|
|
4387
|
+
anchorRef.current.click();
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
const target = findTargetTab(e.currentTarget, e.key, ctx?.orientation === 'vertical');
|
|
4391
|
+
if (target) {
|
|
4392
|
+
e.preventDefault();
|
|
4393
|
+
target.focus();
|
|
4394
|
+
}
|
|
4395
|
+
};
|
|
4396
|
+
const handleFocus = (e) => {
|
|
4397
|
+
onFocus?.(e);
|
|
4398
|
+
ctx?.setFocusedTab(index);
|
|
4399
|
+
};
|
|
4400
|
+
const handleBlur = (e) => {
|
|
4401
|
+
onBlur?.(e);
|
|
4402
|
+
if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {
|
|
4403
|
+
ctx?.setFocusedTab(null);
|
|
4404
|
+
}
|
|
4405
|
+
};
|
|
4406
|
+
return (jsxRuntime.jsx("li", { className: classNames(activeClass, className) || undefined, role: "tab", id: tabId, "aria-selected": isActive, "aria-controls": ctx?.panelId(index), "aria-disabled": disabled || undefined, tabIndex: tabIndex, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, ...props, children: jsxRuntime.jsxs("a", { ref: anchorRef, onClick: handleClick, "aria-disabled": disabled || undefined, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })), children && jsxRuntime.jsx("span", { children: children })] }) }));
|
|
4260
4407
|
};
|
|
4261
4408
|
/**
|
|
4262
4409
|
* Each tab; accepts `active`, `onClick`, etc.
|
|
@@ -4286,13 +4433,20 @@ const TabsContentComponent = ({ className, children, ...props }) => {
|
|
|
4286
4433
|
* @param {TabContentItemProps} props - Props for the TabContentItem component.
|
|
4287
4434
|
* @returns {JSX.Element} The rendered tab content panel.
|
|
4288
4435
|
*/
|
|
4289
|
-
const TabContentItem = ({ index, className, children, ...props }) => {
|
|
4436
|
+
const TabContentItem = ({ index, className, children, id, ...props }) => {
|
|
4290
4437
|
const ctx = useTabsContext();
|
|
4291
4438
|
const isActive = ctx ? ctx.activeTab === index : false;
|
|
4292
4439
|
const itemClass = usePrefixedClassNames('tabs-content-item', {
|
|
4293
4440
|
'is-active': isActive,
|
|
4294
4441
|
});
|
|
4295
|
-
|
|
4442
|
+
const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);
|
|
4443
|
+
const registerPanel = ctx?.registerPanel;
|
|
4444
|
+
React.useEffect(() => {
|
|
4445
|
+
if (!registerPanel || panelId === undefined)
|
|
4446
|
+
return undefined;
|
|
4447
|
+
return registerPanel({ index, id: panelId });
|
|
4448
|
+
}, [registerPanel, index, panelId]);
|
|
4449
|
+
return (jsxRuntime.jsx("div", { className: classNames(itemClass, className), role: "tabpanel", id: panelId, "aria-labelledby": ctx?.tabId(index), "aria-hidden": !isActive, ...props, children: children }));
|
|
4296
4450
|
};
|
|
4297
4451
|
// ---------------------------------------------------------------------------
|
|
4298
4452
|
// Static property attachment
|
|
@@ -7691,7 +7845,8 @@ const noAnnouncementsYet = {
|
|
|
7691
7845
|
// the accessibility tree. The declarations match the `extras-sr-only` mixin,
|
|
7692
7846
|
// plus `clip-path`, which replaces the deprecated `clip`. The negative margin
|
|
7693
7847
|
// pulls the 1px box back inside the page, so it doesn't add scrollable
|
|
7694
|
-
// overflow.
|
|
7848
|
+
// overflow. Exported for other live regions that must work without a
|
|
7849
|
+
// stylesheet, such as a range calendar's.
|
|
7695
7850
|
const visuallyHidden = {
|
|
7696
7851
|
position: 'absolute',
|
|
7697
7852
|
width: '1px',
|
|
@@ -8849,6 +9004,10 @@ const ON_CONTROL = {
|
|
|
8849
9004
|
* only the props it hands its own `Control`, as the caller gave them, so a
|
|
8850
9005
|
* default the wrapper fills in itself never warns. A prop counts when it is
|
|
8851
9006
|
* truthy, since a falsy one would change nothing on a `Control` either.
|
|
9007
|
+
* `iconLeftSize` and `hasIconsLeft` show nothing on a `Control` without a
|
|
9008
|
+
* glyph, so when they move and the caller chose no left icon, the advice
|
|
9009
|
+
* names the wrapper's default glyph too, and following it draws what the
|
|
9010
|
+
* wrapper's own `Control` did.
|
|
8852
9011
|
* Internal; not part of the public API.
|
|
8853
9012
|
*/
|
|
8854
9013
|
const rendersOwnControl = (component, options) => {
|
|
@@ -8859,18 +9018,32 @@ const rendersOwnControl = (component, options) => {
|
|
|
8859
9018
|
return false;
|
|
8860
9019
|
const names = held.join(' ');
|
|
8861
9020
|
const doNothing = held.length > 1 ? 'those props do' : 'that prop does';
|
|
9021
|
+
let message;
|
|
8862
9022
|
if (options.inline) {
|
|
8863
|
-
|
|
8864
|
-
`
|
|
8865
|
-
|
|
8866
|
-
|
|
9023
|
+
message =
|
|
9024
|
+
`[bestax-bulma] <${component} inline ${names}> renders no <Control> ` +
|
|
9025
|
+
`in inline mode, inside a <Control> or not, so ${doNothing} ` +
|
|
9026
|
+
`nothing. Leave ${held.length > 1 ? 'them' : 'it'} out of an ` +
|
|
9027
|
+
`inline picker.`;
|
|
8867
9028
|
}
|
|
8868
9029
|
else {
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
9030
|
+
const toSet = held.map(prop => ON_CONTROL[prop]);
|
|
9031
|
+
const needsGlyph = (options.iconLeftSize || options.hasIconsLeft) &&
|
|
9032
|
+
!options.iconLeft &&
|
|
9033
|
+
options.iconLeftName === undefined &&
|
|
9034
|
+
options.defaultIconLeftName;
|
|
9035
|
+
if (needsGlyph) {
|
|
9036
|
+
toSet.push(`iconLeftName="${options.defaultIconLeftName}" (its default icon)`);
|
|
9037
|
+
}
|
|
9038
|
+
message =
|
|
9039
|
+
`[bestax-bulma] <${component} ${names}> inside a <Control> renders ` +
|
|
9040
|
+
`no <Control> of its own, so ${doNothing} nothing there. Set ` +
|
|
9041
|
+
`${listOf(toSet)} on that <Control> instead.`;
|
|
8873
9042
|
}
|
|
9043
|
+
// Keyed by the message itself: the props named do not settle the advice
|
|
9044
|
+
// (an empty `iconLeftName` and an unset one name the same props), so two
|
|
9045
|
+
// calls that would advise differently must not share a key.
|
|
9046
|
+
warnOnce(message, message);
|
|
8874
9047
|
return false;
|
|
8875
9048
|
};
|
|
8876
9049
|
const FieldLabelIdContext = React.createContext(undefined);
|
|
@@ -9357,7 +9530,40 @@ const Checkboxes = withSubComponents(CheckboxesComponent, { Checkbox }, 'Checkbo
|
|
|
9357
9530
|
*/
|
|
9358
9531
|
const File = React.forwardRef(({
|
|
9359
9532
|
// Field props
|
|
9360
|
-
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, color, size, isBoxed, isFullwidth, isFullWidth, isRight, isCentered, hasName, buttonLabel, iconLeft, iconRight, className, inputClassName, fileName, ...props }, ref) => {
|
|
9533
|
+
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, color, size, isBoxed, isFullwidth, isFullWidth, isRight, isCentered, hasName, buttonLabel, iconLeft, iconRight, className, inputClassName, fileName, pickedFilesLabel, onChange, ...props }, ref) => {
|
|
9534
|
+
// What the user last picked, and the form a reset of which clears it.
|
|
9535
|
+
const [picked, setPicked] = React.useState();
|
|
9536
|
+
const pickedForm = picked?.form;
|
|
9537
|
+
React.useEffect(() => {
|
|
9538
|
+
if (!pickedForm)
|
|
9539
|
+
return;
|
|
9540
|
+
let timer;
|
|
9541
|
+
// The reset event fires before the form resets, and a listener after this one can still
|
|
9542
|
+
// cancel it, so the name is cleared a task later, and only if the reset went ahead.
|
|
9543
|
+
const onReset = (event) => {
|
|
9544
|
+
timer = setTimeout(() => {
|
|
9545
|
+
if (!event.defaultPrevented)
|
|
9546
|
+
setPicked(undefined);
|
|
9547
|
+
});
|
|
9548
|
+
};
|
|
9549
|
+
pickedForm.addEventListener('reset', onReset);
|
|
9550
|
+
return () => {
|
|
9551
|
+
pickedForm.removeEventListener('reset', onReset);
|
|
9552
|
+
clearTimeout(timer);
|
|
9553
|
+
};
|
|
9554
|
+
}, [pickedForm]);
|
|
9555
|
+
const handleChange = (event) => {
|
|
9556
|
+
const { files, form } = event.target;
|
|
9557
|
+
setPicked(files?.length
|
|
9558
|
+
? { count: files.length, firstName: files[0].name, form }
|
|
9559
|
+
: undefined);
|
|
9560
|
+
onChange?.(event);
|
|
9561
|
+
};
|
|
9562
|
+
const pickedText = picked &&
|
|
9563
|
+
(picked.count === 1
|
|
9564
|
+
? picked.firstName
|
|
9565
|
+
: (pickedFilesLabel?.(picked.count) ?? `${picked.count} files`));
|
|
9566
|
+
const shownName = fileName ?? pickedText;
|
|
9361
9567
|
const insideField = useInsideField();
|
|
9362
9568
|
const insideControl = useInsideControl();
|
|
9363
9569
|
const ownField = rendersOwnField('File', {
|
|
@@ -9403,7 +9609,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
9403
9609
|
[`is-${messageColor}`]: !!messageColor,
|
|
9404
9610
|
});
|
|
9405
9611
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
9406
|
-
const fileElement = (jsxRuntime.jsx("div", { className: fileClass, children: jsxRuntime.jsxs("label", { className: usePrefixedClassNames('file-label'), children: [jsxRuntime.jsx("input", { ref: ref, className: classNames(usePrefixedClassNames('file-input'), inputClassName), type: "file", id: controlId, ...rest }), jsxRuntime.jsxs("span", { className: usePrefixedClassNames('file-cta'), children: [iconLeft && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconLeft })), jsxRuntime.jsx("span", { className: usePrefixedClassNames('file-label'), children: buttonLabel || 'Choose a file\u2026' }), iconRight && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconRight }))] }), hasName &&
|
|
9612
|
+
const fileElement = (jsxRuntime.jsx("div", { className: fileClass, children: jsxRuntime.jsxs("label", { className: usePrefixedClassNames('file-label'), children: [jsxRuntime.jsx("input", { ref: ref, className: classNames(usePrefixedClassNames('file-input'), inputClassName), type: "file", id: controlId, onChange: handleChange, ...rest }), jsxRuntime.jsxs("span", { className: usePrefixedClassNames('file-cta'), children: [iconLeft && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconLeft })), jsxRuntime.jsx("span", { className: usePrefixedClassNames('file-label'), children: buttonLabel || 'Choose a file\u2026' }), iconRight && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconRight }))] }), hasName && shownName && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-name'), children: shownName }))] }) }));
|
|
9407
9613
|
if (ownField) {
|
|
9408
9614
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [fileElement, messageEl] }));
|
|
9409
9615
|
}
|
|
@@ -10142,7 +10348,11 @@ const Numberinput = React.forwardRef(({
|
|
|
10142
10348
|
// Field props
|
|
10143
10349
|
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, value: controlledValue, defaultValue, min, max, step = 1, size, color, inputColor, controlsPosition = 'both', controlsRounded = false, compact = false, bare, variant = 'plusminus', disabled = false, editable = true, isLoading = false, exponential = false, onChange, className, ...props }, ref) => {
|
|
10144
10350
|
const insideField = useInsideField();
|
|
10145
|
-
|
|
10351
|
+
// Bare controls join their parent's row, which a `.control` does not lay
|
|
10352
|
+
// out. `useInsideControl()` is true anywhere below a `Control`, even past
|
|
10353
|
+
// a `Field` nested in it, so there they keep their own row as well.
|
|
10354
|
+
const insideControl = useInsideControl();
|
|
10355
|
+
const effectiveBare = bare ?? (insideField && !insideControl);
|
|
10146
10356
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
10147
10357
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
10148
10358
|
const inputRef = React.useRef(null);
|
|
@@ -11476,6 +11686,12 @@ fieldClassName, controlClassName,
|
|
|
11476
11686
|
});
|
|
11477
11687
|
TextArea.displayName = 'TextArea';
|
|
11478
11688
|
|
|
11689
|
+
/**
|
|
11690
|
+
* Whether a key is Alt+ArrowDown or Alt+ArrowUp, which open and close a
|
|
11691
|
+
* picker's popover. The calendar and the time wheels leave both to it rather
|
|
11692
|
+
* than reading them as plain arrows.
|
|
11693
|
+
*/
|
|
11694
|
+
const isPopoverToggleKey = (e) => e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp');
|
|
11479
11695
|
const DEFAULT_PICKER_LABELS = {
|
|
11480
11696
|
prevMonth: 'Previous month',
|
|
11481
11697
|
nextMonth: 'Next month',
|
|
@@ -11484,6 +11700,11 @@ const DEFAULT_PICKER_LABELS = {
|
|
|
11484
11700
|
chooseDate: 'Choose date',
|
|
11485
11701
|
chooseMonth: 'Choose month',
|
|
11486
11702
|
chooseYear: 'Choose year',
|
|
11703
|
+
rangeStart: 'Start date',
|
|
11704
|
+
rangeEnd: 'End date',
|
|
11705
|
+
rangePreviewEnd: 'Choose as end date',
|
|
11706
|
+
rangeSeparator: '–',
|
|
11707
|
+
chooseDateRange: 'Choose date range',
|
|
11487
11708
|
hours: 'hours',
|
|
11488
11709
|
minutes: 'minutes',
|
|
11489
11710
|
seconds: 'seconds',
|
|
@@ -11636,6 +11857,43 @@ function isPeriodUnselectable(d, granularity, c) {
|
|
|
11636
11857
|
}
|
|
11637
11858
|
return true;
|
|
11638
11859
|
}
|
|
11860
|
+
/**
|
|
11861
|
+
* The most days {@link canCloseRange} walks between a range's ends, about
|
|
11862
|
+
* eleven years. A span it would have to walk further is refused, so the check
|
|
11863
|
+
* stays cheap enough to run on every pointer move over the calendar.
|
|
11864
|
+
*/
|
|
11865
|
+
const RANGE_WALK_LIMIT = 4000;
|
|
11866
|
+
/**
|
|
11867
|
+
* Whether `end` can close a range that opens on `start`: it falls on `start`'s
|
|
11868
|
+
* day or later, and, unless `allowDisabled`, no day between the two is one
|
|
11869
|
+
* that `shouldDisableDate` or `unselectableDates` disables. Checking that
|
|
11870
|
+
* walks the days between, so with either rule set and `allowDisabled` off,
|
|
11871
|
+
* an `end` more than {@link RANGE_WALK_LIMIT} days after `start` is refused.
|
|
11872
|
+
* The ends' own constraints, `min` and `max` among them, are the caller's to
|
|
11873
|
+
* check. Bounds can't fall between two days inside them, so they are not
|
|
11874
|
+
* walked.
|
|
11875
|
+
*/
|
|
11876
|
+
function canCloseRange(start, end, c, allowDisabled = false) {
|
|
11877
|
+
const first = startOfDay(start);
|
|
11878
|
+
const last = startOfDay(end).getTime();
|
|
11879
|
+
if (last < first.getTime())
|
|
11880
|
+
return false;
|
|
11881
|
+
if (allowDisabled)
|
|
11882
|
+
return true;
|
|
11883
|
+
const blocks = {
|
|
11884
|
+
shouldDisableDate: c.shouldDisableDate,
|
|
11885
|
+
unselectableDates: c.unselectableDates,
|
|
11886
|
+
};
|
|
11887
|
+
if (!blocks.shouldDisableDate && !blocks.unselectableDates?.length) {
|
|
11888
|
+
return true;
|
|
11889
|
+
}
|
|
11890
|
+
for (let d = addDays(first, 1), walked = 1; d.getTime() < last; d = addDays(d, 1), walked++) {
|
|
11891
|
+
if (walked >= RANGE_WALK_LIMIT || isDayUnselectable(d, blocks)) {
|
|
11892
|
+
return false;
|
|
11893
|
+
}
|
|
11894
|
+
}
|
|
11895
|
+
return true;
|
|
11896
|
+
}
|
|
11639
11897
|
function addDays(d, n) {
|
|
11640
11898
|
const r = new Date(d);
|
|
11641
11899
|
r.setDate(r.getDate() + n);
|
|
@@ -11654,6 +11912,12 @@ function addMonths(d, n) {
|
|
|
11654
11912
|
function addYears(d, n) {
|
|
11655
11913
|
return addMonths(d, n * 12);
|
|
11656
11914
|
}
|
|
11915
|
+
/**
|
|
11916
|
+
* A copy of `d` with the given parts of its time of day set and the rest
|
|
11917
|
+
* left as they are. A time started from the clock rather than from a value
|
|
11918
|
+
* should pass the seconds its picker does not show, and the milliseconds,
|
|
11919
|
+
* as `0`, or it keeps the ones the clock had.
|
|
11920
|
+
*/
|
|
11657
11921
|
function setTimeOfDay(d, parts) {
|
|
11658
11922
|
const r = new Date(d);
|
|
11659
11923
|
if (parts.hours !== undefined)
|
|
@@ -11662,6 +11926,8 @@ function setTimeOfDay(d, parts) {
|
|
|
11662
11926
|
r.setMinutes(parts.minutes);
|
|
11663
11927
|
if (parts.seconds !== undefined)
|
|
11664
11928
|
r.setSeconds(parts.seconds);
|
|
11929
|
+
if (parts.milliseconds !== undefined)
|
|
11930
|
+
r.setMilliseconds(parts.milliseconds);
|
|
11665
11931
|
return r;
|
|
11666
11932
|
}
|
|
11667
11933
|
/**
|
|
@@ -11810,6 +12076,36 @@ function formatDateTime(d, fmt, locale) {
|
|
|
11810
12076
|
return formatTokenString(d, f);
|
|
11811
12077
|
return formatWithIntl(d, f, locale);
|
|
11812
12078
|
}
|
|
12079
|
+
/**
|
|
12080
|
+
* The value as a native input and a hidden form input carry it:
|
|
12081
|
+
* `YYYY-MM-DD`, `YYYY-MM` or `YYYY`, the shapes `<input type="date">` and
|
|
12082
|
+
* `<input type="month">` use. The year is padded to four digits, as those
|
|
12083
|
+
* inputs require and as the `YYYY` token displays it. HTML has no such shape
|
|
12084
|
+
* for a year before 1, so a date then is empty, as those inputs would make it.
|
|
12085
|
+
*/
|
|
12086
|
+
function toIsoValue(d, granularity) {
|
|
12087
|
+
if (d.getFullYear() < FIRST_YEAR)
|
|
12088
|
+
return '';
|
|
12089
|
+
const year = pad(d.getFullYear(), 4);
|
|
12090
|
+
if (granularity === 'year')
|
|
12091
|
+
return year;
|
|
12092
|
+
const month = `${year}-${pad(d.getMonth() + 1)}`;
|
|
12093
|
+
return granularity === 'month' ? month : `${month}-${pad(d.getDate())}`;
|
|
12094
|
+
}
|
|
12095
|
+
/**
|
|
12096
|
+
* Read a native `type="date"` or `type="month"` value back into a Date. HTML
|
|
12097
|
+
* allows a year of four or more digits, as `toIsoValue` writes one past 9999.
|
|
12098
|
+
*/
|
|
12099
|
+
function fromIsoValue(s, granularity) {
|
|
12100
|
+
if (granularity === 'month') {
|
|
12101
|
+
const m = /^(\d{4,})-(\d{2})$/.exec(s);
|
|
12102
|
+
return m ? makeDate(Number(m[1]), Number(m[2]) - 1) : null;
|
|
12103
|
+
}
|
|
12104
|
+
const m = /^(\d{4,})-(\d{2})-(\d{2})$/.exec(s);
|
|
12105
|
+
if (!m)
|
|
12106
|
+
return null;
|
|
12107
|
+
return makeDate(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
12108
|
+
}
|
|
11813
12109
|
/**
|
|
11814
12110
|
* Derive the hour cycle (`'12'` or `'24'`) a display format will render, from
|
|
11815
12111
|
* its first hour token: `h`/`hh` → 12-hour with meridiem, `H`/`HH` → 24-hour.
|
|
@@ -12096,6 +12392,28 @@ function nearestEnabled(i, disabled) {
|
|
|
12096
12392
|
}
|
|
12097
12393
|
return i;
|
|
12098
12394
|
}
|
|
12395
|
+
/** The long, spoken form of a date in a range's announcement. */
|
|
12396
|
+
const LONG_DATE = {
|
|
12397
|
+
year: 'numeric',
|
|
12398
|
+
month: 'long',
|
|
12399
|
+
day: 'numeric',
|
|
12400
|
+
};
|
|
12401
|
+
/** A range's pending start: its start, when it has no end. */
|
|
12402
|
+
const pendingStartOf = (range) => range?.[0] && !range[1] ? startOfDay(range[0]) : null;
|
|
12403
|
+
/**
|
|
12404
|
+
* The day grid's cells in rows of a week. A grid owns rows and rows own
|
|
12405
|
+
* cells, so assistive technology can walk the grid, and the selection it
|
|
12406
|
+
* reports, row by row.
|
|
12407
|
+
*/
|
|
12408
|
+
const inWeeks = (days, className) => {
|
|
12409
|
+
const rows = [];
|
|
12410
|
+
for (let i = 0; i < days.length; i += 7) {
|
|
12411
|
+
rows.push(jsxRuntime.jsx("div", { role: "row", className: className, children: days.slice(i, i + 7) }, i));
|
|
12412
|
+
}
|
|
12413
|
+
return rows;
|
|
12414
|
+
};
|
|
12415
|
+
/** A date's time, or `null` for none, for comparing ranges by value. */
|
|
12416
|
+
const timeOf = (d) => d ? d.getTime() : null;
|
|
12099
12417
|
/**
|
|
12100
12418
|
* The year list's keyboard, as the selection surface and as navigation:
|
|
12101
12419
|
* arrows move a year or a row, Home/End go to the list's ends. Returns the
|
|
@@ -12121,7 +12439,7 @@ function yearStep(key, focusedYear, years) {
|
|
|
12121
12439
|
return null;
|
|
12122
12440
|
}
|
|
12123
12441
|
}
|
|
12124
|
-
const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minProp, max, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, locale, dayNames, monthNames, nearbyMonthDays = true, color, size, className, id, autoFocusCell = false, labels, yearsRange, granularity = 'day', }) => {
|
|
12442
|
+
const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minProp, max, shouldDisableDate, unselectableDates, firstDayOfWeek = 0, locale, dayNames, monthNames, nearbyMonthDays = true, color, size, className, id, autoFocusCell = false, labels, yearsRange, granularity = 'day', range, onRangeSelect, allowDisabledInRange = false, }) => {
|
|
12125
12443
|
const gridRef = React.useRef(null);
|
|
12126
12444
|
const monthGridRef = React.useRef(null);
|
|
12127
12445
|
const yearGridRef = React.useRef(null);
|
|
@@ -12178,6 +12496,80 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12178
12496
|
const i = nearestEnabled(focusedDate.getDate() - 1, days.map(isDateUnselectable));
|
|
12179
12497
|
return days[i];
|
|
12180
12498
|
}, [cells, focusedDate, isDateUnselectable]);
|
|
12499
|
+
// ----- Range mode -----
|
|
12500
|
+
// Range props come only with the day grid.
|
|
12501
|
+
const rangeMode = !!range;
|
|
12502
|
+
const valuePending = pendingStartOf(range);
|
|
12503
|
+
// The start of a range being picked, which nothing outside the calendar
|
|
12504
|
+
// sees until its end is picked too. It starts as the range's own pending
|
|
12505
|
+
// start, and starts there again whenever the range changes from outside.
|
|
12506
|
+
const [anchor, setAnchor] = React.useState(valuePending);
|
|
12507
|
+
const rangeKey = `${timeOf(range?.[0])}|${timeOf(range?.[1])}`;
|
|
12508
|
+
const [anchorRangeKey, setAnchorRangeKey] = React.useState(rangeKey);
|
|
12509
|
+
if (anchorRangeKey !== rangeKey) {
|
|
12510
|
+
setAnchorRangeKey(rangeKey);
|
|
12511
|
+
setAnchor(valuePending);
|
|
12512
|
+
}
|
|
12513
|
+
// The day under the pointer while a start is pending, which the preview
|
|
12514
|
+
// runs to ahead of the focused day.
|
|
12515
|
+
const [hoverDate, setHoverDate] = React.useState(null);
|
|
12516
|
+
// Whether `day` can end a range that starts on `start`. The start is
|
|
12517
|
+
// checked as well as the end: one the range brought, or one picked before
|
|
12518
|
+
// the bounds or the disabled days changed, can rule itself out.
|
|
12519
|
+
const canEndAt = React.useCallback((start, day) => !isDateUnselectable(start) &&
|
|
12520
|
+
!isDateUnselectable(day) &&
|
|
12521
|
+
canCloseRange(start, day, { shouldDisableDate, unselectableDates }, allowDisabledInRange), [
|
|
12522
|
+
isDateUnselectable,
|
|
12523
|
+
shouldDisableDate,
|
|
12524
|
+
unselectableDates,
|
|
12525
|
+
allowDisabledInRange,
|
|
12526
|
+
]);
|
|
12527
|
+
// A pick in range mode: the end of the pending range when it can be one,
|
|
12528
|
+
// and otherwise the start of a new one.
|
|
12529
|
+
const pickRangeDay = React.useCallback((picked) => {
|
|
12530
|
+
const day = startOfDay(picked);
|
|
12531
|
+
// The pointer is on the picked day, which the focused day now is too.
|
|
12532
|
+
setHoverDate(null);
|
|
12533
|
+
if (anchor && canEndAt(anchor, day)) {
|
|
12534
|
+
setAnchor(null);
|
|
12535
|
+
onRangeSelect?.(anchor, day);
|
|
12536
|
+
}
|
|
12537
|
+
else {
|
|
12538
|
+
setAnchor(day);
|
|
12539
|
+
}
|
|
12540
|
+
}, [anchor, canEndAt, onRangeSelect]);
|
|
12541
|
+
// What the grid shows: while a start is pending, the range from it to the
|
|
12542
|
+
// hovered or focused day, when that day can end it; otherwise the
|
|
12543
|
+
// committed range.
|
|
12544
|
+
let shownStart;
|
|
12545
|
+
let shownEnd;
|
|
12546
|
+
if (anchor) {
|
|
12547
|
+
shownStart = anchor;
|
|
12548
|
+
const previewEnd = startOfDay(hoverDate ?? focusedDate);
|
|
12549
|
+
shownEnd = canEndAt(anchor, previewEnd) ? previewEnd : null;
|
|
12550
|
+
}
|
|
12551
|
+
else {
|
|
12552
|
+
shownStart = range?.[0] ? startOfDay(range[0]) : null;
|
|
12553
|
+
shownEnd = range?.[1] ? startOfDay(range[1]) : null;
|
|
12554
|
+
}
|
|
12555
|
+
const previewing = !!anchor;
|
|
12556
|
+
// Ids for the words that name a range's ends and state the whole range.
|
|
12557
|
+
const reactId = React.useId();
|
|
12558
|
+
const rangeIdBase = id ?? `calendar-${reactId}`;
|
|
12559
|
+
const rangeStartId = `${rangeIdBase}-range-start`;
|
|
12560
|
+
const rangeEndId = `${rangeIdBase}-range-end`;
|
|
12561
|
+
const rangePreviewEndId = `${rangeIdBase}-range-preview-end`;
|
|
12562
|
+
const rangeSummaryId = `${rangeIdBase}-range-summary`;
|
|
12563
|
+
// The selection in words: the pending start alone, or the committed ends.
|
|
12564
|
+
const selectedEnd = previewing ? null : shownEnd;
|
|
12565
|
+
const rangeSummary = [
|
|
12566
|
+
shownStart &&
|
|
12567
|
+
`${t.rangeStart}: ${formatDate(shownStart, LONG_DATE, locale)}`,
|
|
12568
|
+
selectedEnd &&
|
|
12569
|
+
`${t.rangeEnd}: ${formatDate(selectedEnd, LONG_DATE, locale)}`,
|
|
12570
|
+
]
|
|
12571
|
+
.filter(Boolean)
|
|
12572
|
+
.join(', ');
|
|
12181
12573
|
// Whether the grid on show holds focus, or held it last before focus went
|
|
12182
12574
|
// nowhere: the cell that had it left the page or became disabled. A blur
|
|
12183
12575
|
// that takes focus out of the grid, to nowhere included, is the user
|
|
@@ -12215,6 +12607,10 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12215
12607
|
}
|
|
12216
12608
|
}, [min, max, isDateUnselectable, onFocusedDateChange]);
|
|
12217
12609
|
const handleKeyDown = React.useCallback((e) => {
|
|
12610
|
+
if (isPopoverToggleKey(e))
|
|
12611
|
+
return;
|
|
12612
|
+
// A key takes the range preview back from the pointer.
|
|
12613
|
+
setHoverDate(null);
|
|
12218
12614
|
switch (e.key) {
|
|
12219
12615
|
case 'ArrowLeft':
|
|
12220
12616
|
e.preventDefault();
|
|
@@ -12257,11 +12653,35 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12257
12653
|
case 'Enter':
|
|
12258
12654
|
case ' ':
|
|
12259
12655
|
e.preventDefault();
|
|
12260
|
-
if (
|
|
12261
|
-
|
|
12656
|
+
if (isDateUnselectable(focusedDate))
|
|
12657
|
+
break;
|
|
12658
|
+
if (rangeMode)
|
|
12659
|
+
pickRangeDay(focusedDate);
|
|
12660
|
+
else
|
|
12661
|
+
onSelect?.(focusedDate);
|
|
12662
|
+
break;
|
|
12663
|
+
case 'Escape':
|
|
12664
|
+
// A pending pick is taken back, and the key stops there so a
|
|
12665
|
+
// popover around the calendar stays open. The range's own pending
|
|
12666
|
+
// start is no pick, so Escape goes on to close the popover.
|
|
12667
|
+
if (anchor && !(valuePending && isSameDay(anchor, valuePending))) {
|
|
12668
|
+
e.preventDefault();
|
|
12669
|
+
e.stopPropagation();
|
|
12670
|
+
setAnchor(valuePending);
|
|
12671
|
+
}
|
|
12262
12672
|
break;
|
|
12263
12673
|
}
|
|
12264
|
-
}, [
|
|
12674
|
+
}, [
|
|
12675
|
+
focusedDate,
|
|
12676
|
+
firstDayOfWeek,
|
|
12677
|
+
isDateUnselectable,
|
|
12678
|
+
moveFocus,
|
|
12679
|
+
onSelect,
|
|
12680
|
+
rangeMode,
|
|
12681
|
+
pickRangeDay,
|
|
12682
|
+
anchor,
|
|
12683
|
+
valuePending,
|
|
12684
|
+
]);
|
|
12265
12685
|
// The month grid's counterpart to `moveFocus`: step by `direction` months
|
|
12266
12686
|
// until a month with a selectable day turns up, stopping at the min/max
|
|
12267
12687
|
// months. The cap guards against a range with nothing selectable.
|
|
@@ -12281,10 +12701,12 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12281
12701
|
}
|
|
12282
12702
|
}, [min, max, isMonthUnselectable, onFocusedDateChange]);
|
|
12283
12703
|
const handleMonthKeyDown = React.useCallback((e) => {
|
|
12704
|
+
if (isPopoverToggleKey(e))
|
|
12705
|
+
return;
|
|
12284
12706
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
12285
12707
|
e.preventDefault();
|
|
12286
12708
|
if (!isMonthUnselectable(focusedDate)) {
|
|
12287
|
-
onSelect(startOfMonth(focusedDate));
|
|
12709
|
+
onSelect?.(startOfMonth(focusedDate));
|
|
12288
12710
|
}
|
|
12289
12711
|
return;
|
|
12290
12712
|
}
|
|
@@ -12310,6 +12732,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12310
12732
|
const dayNamesRowClass = usePrefixedClassNames('dateinput-day-names');
|
|
12311
12733
|
const dayNameClass = usePrefixedClassNames('dateinput-day-name');
|
|
12312
12734
|
const gridClass = usePrefixedClassNames('dateinput-grid');
|
|
12735
|
+
const weekClass = usePrefixedClassNames('dateinput-week');
|
|
12313
12736
|
const monthsGridClass = usePrefixedClassNames('dateinput-months-grid');
|
|
12314
12737
|
const monthsRowClass = usePrefixedClassNames('dateinput-months-row');
|
|
12315
12738
|
const yearsGridClass = usePrefixedClassNames('dateinput-years-grid');
|
|
@@ -12399,7 +12822,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12399
12822
|
// pick and `onSelect` gets the year's first day.
|
|
12400
12823
|
const handleYearPick = React.useCallback((year) => {
|
|
12401
12824
|
onFocusedDateChange(clampDate(makeDate(year), min, max));
|
|
12402
|
-
onSelect(makeDate(year));
|
|
12825
|
+
onSelect?.(makeDate(year));
|
|
12403
12826
|
}, [min, max, onFocusedDateChange, onSelect]);
|
|
12404
12827
|
// Step through the listed years from `year` by `direction`, skipping
|
|
12405
12828
|
// disabled ones, and focus the first selectable year found.
|
|
@@ -12414,6 +12837,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12414
12837
|
}
|
|
12415
12838
|
}, [yearList, disabledYears, onFocusedDateChange, min, max]);
|
|
12416
12839
|
const handleYearListKeyDown = React.useCallback((e) => {
|
|
12840
|
+
if (isPopoverToggleKey(e))
|
|
12841
|
+
return;
|
|
12417
12842
|
if (isYearGranularity) {
|
|
12418
12843
|
const step = yearStep(e.key, focusedYear, yearList);
|
|
12419
12844
|
if (!step)
|
|
@@ -12520,32 +12945,69 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12520
12945
|
return (jsxRuntime.jsxs("div", { className: classNames(calendarClass, className), id: id, children: [jsxRuntime.jsxs("div", { className: headerClass, children: [isYearGranularity ? (jsxRuntime.jsx("span", { className: monthLabelClass, id: labelId, children: monthLabel })) : (jsxRuntime.jsxs("button", { type: "button", className: monthTriggerClass, "aria-haspopup": "listbox", "aria-expanded": view === 'years', onClick: () => {
|
|
12521
12946
|
setNavYear(null);
|
|
12522
12947
|
setView(v => (v === 'years' ? baseView : 'years'));
|
|
12523
|
-
}, children: [jsxRuntime.jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsxRuntime.jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxRuntime.jsxs("div", { className: navGroupClass, children: [jsxRuntime.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') }), jsxRuntime.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' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsxRuntime.jsx("div", { className: dayNameClass, children: name }, i))) }), jsxRuntime.jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, className: gridClass, onKeyDown: handleKeyDown, ...gridFocusHandlers, children: cells.map(cell => {
|
|
12948
|
+
}, children: [jsxRuntime.jsx("span", { className: monthLabelClass, id: labelId, "aria-live": "polite", children: monthLabel }), jsxRuntime.jsx("svg", { className: monthCaretClass, viewBox: "0 0 12 12", width: "10", height: "10", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M2 4l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })] })), !isYearGranularity && (jsxRuntime.jsxs("div", { className: navGroupClass, children: [jsxRuntime.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') }), jsxRuntime.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' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: dayNamesRowClass, "aria-hidden": "true", children: computedDayNames.map((name, i) => (jsxRuntime.jsx("div", { className: dayNameClass, children: name }, i))) }), jsxRuntime.jsx("div", { ref: gridRef, role: "grid", "aria-labelledby": labelId, "aria-multiselectable": rangeMode || undefined, "aria-describedby": rangeMode ? rangeSummaryId : undefined, className: gridClass, onKeyDown: handleKeyDown, onMouseLeave: anchor ? () => setHoverDate(null) : undefined, ...gridFocusHandlers, children: inWeeks(cells.map(cell => {
|
|
12524
12949
|
const disabled = isDateUnselectable(cell.date);
|
|
12525
|
-
const
|
|
12950
|
+
const time = cell.date.getTime();
|
|
12951
|
+
// Range mode. The range on show has its ends filled. Every day
|
|
12952
|
+
// of a committed range is selected, and of a preview only the
|
|
12953
|
+
// start.
|
|
12954
|
+
const isStart = time === timeOf(shownStart);
|
|
12955
|
+
const isEnd = time === timeOf(shownEnd);
|
|
12956
|
+
const inRange = !!shownStart &&
|
|
12957
|
+
!!shownEnd &&
|
|
12958
|
+
time > shownStart.getTime() &&
|
|
12959
|
+
time < shownEnd.getTime();
|
|
12960
|
+
// The day a pending range would end on, which isn't picked
|
|
12961
|
+
// yet, so its description says what picking it does.
|
|
12962
|
+
const isPreviewEnd = previewing && isEnd && !isStart;
|
|
12963
|
+
const isPreview = (previewing && inRange) || isPreviewEnd;
|
|
12964
|
+
const isFilled = rangeMode
|
|
12965
|
+
? isStart || (isEnd && !previewing)
|
|
12966
|
+
: !!value && isSameDay(value, cell.date);
|
|
12967
|
+
const isSelected = isFilled || (rangeMode && !previewing && inRange);
|
|
12968
|
+
const describedBy = rangeMode
|
|
12969
|
+
? [
|
|
12970
|
+
isStart && rangeStartId,
|
|
12971
|
+
isEnd && !previewing && rangeEndId,
|
|
12972
|
+
isPreviewEnd && rangePreviewEndId,
|
|
12973
|
+
]
|
|
12974
|
+
.filter(Boolean)
|
|
12975
|
+
.join(' ') || undefined
|
|
12976
|
+
: undefined;
|
|
12526
12977
|
const isFocused = isSameDay(cell.date, tabStopDay);
|
|
12527
12978
|
const otherMonth = !cell.inCurrentMonth;
|
|
12528
12979
|
const cellClass = prefixedClassNames(classPrefix, 'dateinput-cell', {
|
|
12529
|
-
'is-selected':
|
|
12980
|
+
'is-selected': isFilled,
|
|
12981
|
+
'is-range-start': isStart,
|
|
12982
|
+
'is-range-end': isEnd,
|
|
12983
|
+
'is-in-range': inRange,
|
|
12984
|
+
'is-preview': isPreview,
|
|
12530
12985
|
'is-today': cell.isToday,
|
|
12531
12986
|
'is-disabled': disabled,
|
|
12532
12987
|
'is-other-month': otherMonth,
|
|
12533
12988
|
});
|
|
12534
12989
|
const display = !otherMonth || nearbyMonthDays;
|
|
12535
|
-
return (jsxRuntime.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: () => {
|
|
12990
|
+
return (jsxRuntime.jsx("button", { type: "button", role: "gridcell", tabIndex: isFocused ? 0 : -1, "aria-selected": isSelected, "aria-disabled": disabled, "aria-current": cell.isToday ? 'date' : undefined, "aria-describedby": describedBy, "data-focused": isFocused ? 'true' : undefined, disabled: disabled || !display, className: cellClass, onFocus: () => {
|
|
12536
12991
|
// Reached by Tab while the focused day is disabled, or
|
|
12537
12992
|
// by pointer: keys move on from here. A nearby month's
|
|
12538
12993
|
// day waits for its click, which turns the grid.
|
|
12539
12994
|
if (!otherMonth && !isSameDay(cell.date, focusedDate)) {
|
|
12540
12995
|
onFocusedDateChange(cell.date);
|
|
12541
12996
|
}
|
|
12542
|
-
},
|
|
12997
|
+
},
|
|
12998
|
+
// Mouseover rather than mouseenter: browsers send it to a
|
|
12999
|
+
// disabled day as well, where React holds mouseenter back,
|
|
13000
|
+
// so the preview leaves the last enabled day behind.
|
|
13001
|
+
onMouseOver: anchor ? () => setHoverDate(cell.date) : undefined, onClick: () => {
|
|
12543
13002
|
if (disabled)
|
|
12544
13003
|
return;
|
|
12545
13004
|
onFocusedDateChange(cell.date);
|
|
12546
|
-
|
|
13005
|
+
if (rangeMode)
|
|
13006
|
+
pickRangeDay(cell.date);
|
|
13007
|
+
else
|
|
13008
|
+
onSelect?.(cell.date);
|
|
12547
13009
|
}, style: !display ? { visibility: 'hidden' } : undefined, children: cell.date.getDate() }, cell.date.toISOString()));
|
|
12548
|
-
}) })] })), view === 'months' && (jsxRuntime.jsx("div", { ref: monthGridRef, role: "grid", "aria-labelledby": labelId, className: monthsGridClass, onKeyDown: handleMonthKeyDown, ...gridFocusHandlers, children: MONTH_ROWS.map(row => (jsxRuntime.jsx("div", { role: "row", className: monthsRowClass, children: row.map(month => {
|
|
13010
|
+
}), weekClass) })] })), view === 'months' && (jsxRuntime.jsx("div", { ref: monthGridRef, role: "grid", "aria-labelledby": labelId, className: monthsGridClass, onKeyDown: handleMonthKeyDown, ...gridFocusHandlers, children: MONTH_ROWS.map(row => (jsxRuntime.jsx("div", { role: "row", className: monthsRowClass, children: row.map(month => {
|
|
12549
13011
|
const date = makeDate(focusedYear, month);
|
|
12550
13012
|
const disabled = disabledMonths[month];
|
|
12551
13013
|
const isSelected = !!value && isSameMonth(value, date);
|
|
@@ -12564,7 +13026,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12564
13026
|
}
|
|
12565
13027
|
}, onClick: () => {
|
|
12566
13028
|
onFocusedDateChange(clampDate(date, min, max));
|
|
12567
|
-
onSelect(date);
|
|
13029
|
+
onSelect?.(date);
|
|
12568
13030
|
}, children: monthCellNames[month] }, month));
|
|
12569
13031
|
}) }, row[0]))) })), view === 'years' && (jsxRuntime.jsx("div", { ref: yearGridRef, className: yearsGridClass, role: "listbox", "aria-label": isYearGranularity ? t.chooseYear : monthLabel, onKeyDown: handleYearListKeyDown, children: yearList.map(year => {
|
|
12570
13032
|
const isFocused = year === tabStopYear;
|
|
@@ -12595,7 +13057,7 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12595
13057
|
}, onClick: () => isYearGranularity
|
|
12596
13058
|
? handleYearPick(year)
|
|
12597
13059
|
: handleYearSelect(year), children: year }, year));
|
|
12598
|
-
}) }))] }));
|
|
13060
|
+
}) })), rangeMode && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { id: rangeStartId, hidden: true, children: t.rangeStart }), jsxRuntime.jsx("span", { id: rangeEndId, hidden: true, children: t.rangeEnd }), jsxRuntime.jsx("span", { id: rangePreviewEndId, hidden: true, children: t.rangePreviewEnd }), jsxRuntime.jsx("div", { id: rangeSummaryId, "aria-live": "polite", style: visuallyHidden, children: rangeSummary })] }))] }));
|
|
12599
13061
|
};
|
|
12600
13062
|
Calendar.displayName = 'Calendar';
|
|
12601
13063
|
|
|
@@ -12638,6 +13100,8 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
12638
13100
|
document.addEventListener('pointerdown', handler);
|
|
12639
13101
|
return () => document.removeEventListener('pointerdown', handler);
|
|
12640
13102
|
}, [isOpen, closeOnClickOutside, anchorRef, onClose]);
|
|
13103
|
+
// Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,
|
|
13104
|
+
// which the calendar and the time wheels inside leave to it.
|
|
12641
13105
|
React.useEffect(() => {
|
|
12642
13106
|
if (!isOpen || !closeOnEscape || !isBrowser)
|
|
12643
13107
|
return undefined;
|
|
@@ -12646,6 +13110,11 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
12646
13110
|
e.stopPropagation();
|
|
12647
13111
|
onClose();
|
|
12648
13112
|
}
|
|
13113
|
+
else if (e.altKey && e.key === 'ArrowUp') {
|
|
13114
|
+
e.preventDefault();
|
|
13115
|
+
e.stopPropagation();
|
|
13116
|
+
onClose();
|
|
13117
|
+
}
|
|
12649
13118
|
};
|
|
12650
13119
|
document.addEventListener('keydown', handler);
|
|
12651
13120
|
return () => document.removeEventListener('keydown', handler);
|
|
@@ -13049,7 +13518,7 @@ const SEPARATOR_RE = /[/:\-.\s]/;
|
|
|
13049
13518
|
* null and the input transparently falls back to free-form text entry.
|
|
13050
13519
|
*/
|
|
13051
13520
|
function useSegmentedEntry(params) {
|
|
13052
|
-
const { format, skipKinds, value, commitValue, formatFn, tryParse, text, setText, makeBaseDate, locale, min, max, isBlocked, disabled, readOnly, editable = true, popover = true, openOnFocus = true, closeOnSelect = false, isOpen, setOpen, inputRef, containerRef, onFocus, onClick, onKeyDown, onBlur, } = params;
|
|
13521
|
+
const { format, skipKinds, value, commitValue, formatFn, tryParse, text, setText, makeBaseDate, locale, min, max, isBlocked, isUnfinishedBlocked, disabled, readOnly, editable = true, popover = true, openOnFocus = true, closeOnSelect = false, isOpen, setOpen, inputRef, containerRef, onFocus, onClick, onKeyDown, onBlur, } = params;
|
|
13053
13522
|
const segmentMap = React.useMemo(() => {
|
|
13054
13523
|
const map = typeof format === 'string' ? buildSegmentMap(format) : null;
|
|
13055
13524
|
if (!map || !skipKinds)
|
|
@@ -13245,7 +13714,7 @@ function useSegmentedEntry(params) {
|
|
|
13245
13714
|
// Clicking the field opens the popover only when `openOnFocus` is on —
|
|
13246
13715
|
// a click is just a focus. With `openOnFocus={false}` (manual-entry
|
|
13247
13716
|
// mode) the click positions the caret for typing and the popover is
|
|
13248
|
-
// opened explicitly via the right launcher button (or ArrowDown).
|
|
13717
|
+
// opened explicitly via the right launcher button (or Alt+ArrowDown).
|
|
13249
13718
|
if (openOnFocus && popover && !disabled && !readOnly)
|
|
13250
13719
|
setOpen(true);
|
|
13251
13720
|
if (segmentEditable && segmentMap && inputRef.current) {
|
|
@@ -13281,14 +13750,23 @@ function useSegmentedEntry(params) {
|
|
|
13281
13750
|
const isPm = base.getHours() >= 12;
|
|
13282
13751
|
switch (e.key) {
|
|
13283
13752
|
case 'ArrowUp':
|
|
13284
|
-
e.preventDefault();
|
|
13285
|
-
typedDigitsRef.current = '';
|
|
13286
|
-
applyDateFromSegment(incrementSegmentValue(seg, base, 1, isPm));
|
|
13287
|
-
return;
|
|
13288
13753
|
case 'ArrowDown':
|
|
13289
13754
|
e.preventDefault();
|
|
13755
|
+
// The plain arrows step the segment, so the popover opens on
|
|
13756
|
+
// Alt+ArrowDown and closes on Alt+ArrowUp, as a combobox's does,
|
|
13757
|
+
// and neither steps it.
|
|
13758
|
+
if (e.altKey) {
|
|
13759
|
+
if (e.key === 'ArrowUp') {
|
|
13760
|
+
if (isOpen)
|
|
13761
|
+
setOpen(false);
|
|
13762
|
+
}
|
|
13763
|
+
else if (!isOpen && popover) {
|
|
13764
|
+
setOpen(true);
|
|
13765
|
+
}
|
|
13766
|
+
return;
|
|
13767
|
+
}
|
|
13290
13768
|
typedDigitsRef.current = '';
|
|
13291
|
-
applyDateFromSegment(incrementSegmentValue(seg, base, -1, isPm));
|
|
13769
|
+
applyDateFromSegment(incrementSegmentValue(seg, base, e.key === 'ArrowUp' ? 1 : -1, isPm));
|
|
13292
13770
|
return;
|
|
13293
13771
|
case 'ArrowRight':
|
|
13294
13772
|
e.preventDefault();
|
|
@@ -13354,6 +13832,8 @@ function useSegmentedEntry(params) {
|
|
|
13354
13832
|
const nextBuffer = typedDigitsRef.current + e.key;
|
|
13355
13833
|
typedDigitsRef.current = nextBuffer;
|
|
13356
13834
|
const { date, advance } = setSegmentValue(seg, base, nextBuffer, isPm);
|
|
13835
|
+
if (!advance && isUnfinishedBlocked?.(date))
|
|
13836
|
+
return;
|
|
13357
13837
|
applyDateFromSegment(date);
|
|
13358
13838
|
if (advance) {
|
|
13359
13839
|
typedDigitsRef.current = '';
|
|
@@ -13364,11 +13844,23 @@ function useSegmentedEntry(params) {
|
|
|
13364
13844
|
}
|
|
13365
13845
|
}
|
|
13366
13846
|
// ----- free-form / popover key handling -----
|
|
13367
|
-
|
|
13847
|
+
// With no segment to step, ArrowDown opens the popover with or without
|
|
13848
|
+
// Alt, unless the field is read-only or disabled, as focus and the
|
|
13849
|
+
// launcher don't open it then either. Alt+ArrowUp closes it.
|
|
13850
|
+
if (e.key === 'ArrowDown' &&
|
|
13851
|
+
!isOpen &&
|
|
13852
|
+
popover &&
|
|
13853
|
+
!disabled &&
|
|
13854
|
+
!readOnly) {
|
|
13368
13855
|
e.preventDefault();
|
|
13369
13856
|
setOpen(true);
|
|
13370
13857
|
return;
|
|
13371
13858
|
}
|
|
13859
|
+
if (e.key === 'ArrowUp' && e.altKey && isOpen) {
|
|
13860
|
+
e.preventDefault();
|
|
13861
|
+
setOpen(false);
|
|
13862
|
+
return;
|
|
13863
|
+
}
|
|
13372
13864
|
if (e.key === 'Escape' && isOpen) {
|
|
13373
13865
|
e.preventDefault();
|
|
13374
13866
|
setOpen(false);
|
|
@@ -13398,11 +13890,14 @@ function useSegmentedEntry(params) {
|
|
|
13398
13890
|
activeSegmentIdx,
|
|
13399
13891
|
segmentBaseDate,
|
|
13400
13892
|
applyDateFromSegment,
|
|
13893
|
+
isUnfinishedBlocked,
|
|
13401
13894
|
moveSegment,
|
|
13402
13895
|
isOpen,
|
|
13403
13896
|
setOpen,
|
|
13404
13897
|
closeOnSelect,
|
|
13405
13898
|
popover,
|
|
13899
|
+
disabled,
|
|
13900
|
+
readOnly,
|
|
13406
13901
|
isUntouched,
|
|
13407
13902
|
tryParse,
|
|
13408
13903
|
text,
|
|
@@ -13447,37 +13942,6 @@ function supportsInputType(type) {
|
|
|
13447
13942
|
return supported;
|
|
13448
13943
|
}
|
|
13449
13944
|
|
|
13450
|
-
const pad2$1 = (n) => String(n).padStart(2, '0');
|
|
13451
|
-
/**
|
|
13452
|
-
* The value as the native input and the hidden form input carry it:
|
|
13453
|
-
* `YYYY-MM-DD`, `YYYY-MM` or `YYYY`, the shapes `<input type="date">` and
|
|
13454
|
-
* `<input type="month">` use. The year is padded to four digits, as those
|
|
13455
|
-
* inputs require and as the `YYYY` token displays it. HTML has no such shape
|
|
13456
|
-
* for a year before 1, so a date then is empty, as those inputs would make it.
|
|
13457
|
-
*/
|
|
13458
|
-
const toIsoValue = (d, granularity) => {
|
|
13459
|
-
if (d.getFullYear() < FIRST_YEAR)
|
|
13460
|
-
return '';
|
|
13461
|
-
const year = String(d.getFullYear()).padStart(4, '0');
|
|
13462
|
-
if (granularity === 'year')
|
|
13463
|
-
return year;
|
|
13464
|
-
const month = `${year}-${pad2$1(d.getMonth() + 1)}`;
|
|
13465
|
-
return granularity === 'month' ? month : `${month}-${pad2$1(d.getDate())}`;
|
|
13466
|
-
};
|
|
13467
|
-
/**
|
|
13468
|
-
* Read a native `type="date"` or `type="month"` value back into a Date. HTML
|
|
13469
|
-
* allows a year of four or more digits, as `toIsoValue` writes one past 9999.
|
|
13470
|
-
*/
|
|
13471
|
-
const fromIsoValue = (s, granularity) => {
|
|
13472
|
-
if (granularity === 'month') {
|
|
13473
|
-
const m = /^(\d{4,})-(\d{2})$/.exec(s);
|
|
13474
|
-
return m ? makeDate(Number(m[1]), Number(m[2]) - 1) : null;
|
|
13475
|
-
}
|
|
13476
|
-
const m = /^(\d{4,})-(\d{2})-(\d{2})$/.exec(s);
|
|
13477
|
-
if (!m)
|
|
13478
|
-
return null;
|
|
13479
|
-
return makeDate(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
13480
|
-
};
|
|
13481
13945
|
const DEFAULT_FORMATS = {
|
|
13482
13946
|
day: DEFAULT_DATE_FORMAT,
|
|
13483
13947
|
month: DEFAULT_MONTH_FORMAT,
|
|
@@ -13579,7 +14043,11 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13579
14043
|
// them, so the month or year containing `min` stays reachable.
|
|
13580
14044
|
const periodMin = React.useMemo(() => isDayGranularity ? lowerBound : startOfPeriod(lowerBound, granularity), [lowerBound, isDayGranularity, granularity]);
|
|
13581
14045
|
const periodMax = React.useMemo(() => (max && !isDayGranularity ? endOfPeriod(max, granularity) : max), [max, isDayGranularity, granularity]);
|
|
13582
|
-
|
|
14046
|
+
// A pick is the day at midnight, as a click on its cell gives. Enter hands
|
|
14047
|
+
// over the focused date, which can carry a time of day from the clock, a
|
|
14048
|
+
// value or a bound, so its time is dropped here.
|
|
14049
|
+
const handleSelect = React.useCallback((picked) => {
|
|
14050
|
+
const d = startOfDay(picked);
|
|
13583
14051
|
if (!isWithin(d, periodMin, periodMax))
|
|
13584
14052
|
return;
|
|
13585
14053
|
commitValue(d);
|
|
@@ -13751,13 +14219,14 @@ const DateInput = React.forwardRef((props, ref) => {
|
|
|
13751
14219
|
// icon-left container has nothing to anchor to, and it renders no
|
|
13752
14220
|
// Control inside one or out. The left icon is checked as the caller
|
|
13753
14221
|
// passed it: the default glyph is this component's own choice, not a
|
|
13754
|
-
// prop the caller set.
|
|
14222
|
+
// prop the caller set, so it goes in only for the advice.
|
|
13755
14223
|
const ownControl = rendersOwnControl('DateInput', {
|
|
13756
14224
|
insideControl,
|
|
13757
14225
|
inline: baseProps.inline,
|
|
13758
14226
|
isLoading,
|
|
13759
14227
|
iconLeft,
|
|
13760
14228
|
iconLeftName: props.iconLeftName,
|
|
14229
|
+
defaultIconLeftName: iconLeftName,
|
|
13761
14230
|
iconLeftSize,
|
|
13762
14231
|
iconRight,
|
|
13763
14232
|
iconRightName,
|
|
@@ -14147,23 +14616,28 @@ const WheelInner = ({ values, index, onChange, formatLabel, formatAriaText, aria
|
|
|
14147
14616
|
cancelRaf();
|
|
14148
14617
|
commitPosition(Math.round(positionRef.current) + delta);
|
|
14149
14618
|
}, [disabled, cancelRaf, commitPosition]);
|
|
14619
|
+
// The keys are a spinbutton's: up raises the value and down lowers it. The
|
|
14620
|
+
// values run down the wheel, so up brings the one below the band into it,
|
|
14621
|
+
// the way a drag or a scroll that moves the items up does.
|
|
14150
14622
|
const onKeyDown = React.useCallback((e) => {
|
|
14623
|
+
if (isPopoverToggleKey(e))
|
|
14624
|
+
return;
|
|
14151
14625
|
switch (e.key) {
|
|
14152
14626
|
case 'ArrowUp':
|
|
14153
14627
|
e.preventDefault();
|
|
14154
|
-
moveBy(
|
|
14628
|
+
moveBy(1);
|
|
14155
14629
|
break;
|
|
14156
14630
|
case 'ArrowDown':
|
|
14157
14631
|
e.preventDefault();
|
|
14158
|
-
moveBy(1);
|
|
14632
|
+
moveBy(-1);
|
|
14159
14633
|
break;
|
|
14160
14634
|
case 'PageUp':
|
|
14161
14635
|
e.preventDefault();
|
|
14162
|
-
moveBy(
|
|
14636
|
+
moveBy(5);
|
|
14163
14637
|
break;
|
|
14164
14638
|
case 'PageDown':
|
|
14165
14639
|
e.preventDefault();
|
|
14166
|
-
moveBy(5);
|
|
14640
|
+
moveBy(-5);
|
|
14167
14641
|
break;
|
|
14168
14642
|
case 'Home':
|
|
14169
14643
|
e.preventDefault();
|
|
@@ -14480,6 +14954,7 @@ const TimeWheels = ({ value, onChange, hourFormat = '24', enableSeconds = false,
|
|
|
14480
14954
|
hours: h,
|
|
14481
14955
|
minutes: m,
|
|
14482
14956
|
seconds: s,
|
|
14957
|
+
milliseconds: 0,
|
|
14483
14958
|
});
|
|
14484
14959
|
return !unselectableTimes(probe);
|
|
14485
14960
|
}, [unselectableTimes]);
|
|
@@ -14746,9 +15221,16 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14746
15221
|
setInternalValue(next);
|
|
14747
15222
|
onChange?.(next);
|
|
14748
15223
|
}, [isControlled, onChange]);
|
|
15224
|
+
// An empty field starts from today, at the whole minute or second the
|
|
15225
|
+
// wheels show.
|
|
14749
15226
|
const handleSpinnerChange = React.useCallback((parts) => {
|
|
14750
|
-
const
|
|
14751
|
-
|
|
15227
|
+
const next = value
|
|
15228
|
+
? setTimeOfDay(value, parts)
|
|
15229
|
+
: setTimeOfDay(new Date(), {
|
|
15230
|
+
...parts,
|
|
15231
|
+
seconds: parts.seconds ?? 0,
|
|
15232
|
+
milliseconds: 0,
|
|
15233
|
+
});
|
|
14752
15234
|
if (!isWithin(next, lowerBound, max))
|
|
14753
15235
|
return;
|
|
14754
15236
|
commitValue(next);
|
|
@@ -14879,13 +15361,14 @@ const TimeInput = React.forwardRef((props, ref) => {
|
|
|
14879
15361
|
// icon-left container has nothing to anchor to, and it renders no
|
|
14880
15362
|
// Control inside one or out. The left icon is checked as the caller
|
|
14881
15363
|
// passed it: the default glyph is this component's own choice, not a
|
|
14882
|
-
// prop the caller set.
|
|
15364
|
+
// prop the caller set, so it goes in only for the advice.
|
|
14883
15365
|
const ownControl = rendersOwnControl('TimeInput', {
|
|
14884
15366
|
insideControl,
|
|
14885
15367
|
inline: baseProps.inline,
|
|
14886
15368
|
isLoading,
|
|
14887
15369
|
iconLeft,
|
|
14888
15370
|
iconLeftName: props.iconLeftName,
|
|
15371
|
+
defaultIconLeftName: iconLeftName,
|
|
14889
15372
|
iconLeftSize,
|
|
14890
15373
|
iconRight,
|
|
14891
15374
|
iconRightName,
|
|
@@ -15063,6 +15546,34 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15063
15546
|
if (!open)
|
|
15064
15547
|
setTimeOpen(false);
|
|
15065
15548
|
}, [open]);
|
|
15549
|
+
// The footer's Time button opens the wheels, and the hours wheel takes
|
|
15550
|
+
// focus so the keys turn it straight away. The calendar they cover is
|
|
15551
|
+
// inert meanwhile, so no tab stop, pointer or screen reader reaches a day
|
|
15552
|
+
// behind them; the attribute is set on the element, as React 18 has no
|
|
15553
|
+
// `inert` prop. Closing the wheels hands focus back to the button: focus
|
|
15554
|
+
// in them would fall to the page as they unmount, and a pointer press
|
|
15555
|
+
// outside them has already dropped it on the panel or the page. Focus the
|
|
15556
|
+
// keys moved on to the footer, such as Reset, stays there.
|
|
15557
|
+
const panelRef = React.useRef(null);
|
|
15558
|
+
const calendarRef = React.useRef(null);
|
|
15559
|
+
const timeButtonRef = React.useRef(null);
|
|
15560
|
+
const timeOverlayRef = React.useRef(null);
|
|
15561
|
+
React.useEffect(() => {
|
|
15562
|
+
calendarRef.current?.toggleAttribute('inert', timeOpen);
|
|
15563
|
+
if (!timeOpen)
|
|
15564
|
+
return;
|
|
15565
|
+
timeOverlayRef.current
|
|
15566
|
+
?.querySelector('[role="spinbutton"]')
|
|
15567
|
+
?.focus();
|
|
15568
|
+
}, [timeOpen]);
|
|
15569
|
+
const closeTime = React.useCallback(() => {
|
|
15570
|
+
const panel = panelRef.current;
|
|
15571
|
+
const active = getActiveElementInTree(panel);
|
|
15572
|
+
const movedOn = !!panel?.contains(active) && !timeOverlayRef.current?.contains(active);
|
|
15573
|
+
if (!movedOn)
|
|
15574
|
+
timeButtonRef.current?.focus();
|
|
15575
|
+
setTimeOpen(false);
|
|
15576
|
+
}, []);
|
|
15066
15577
|
const commitValue = React.useCallback((next) => {
|
|
15067
15578
|
if (!isControlled)
|
|
15068
15579
|
setInternalValue(next);
|
|
@@ -15071,20 +15582,32 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15071
15582
|
setFocusedDate(next);
|
|
15072
15583
|
onChange?.(next);
|
|
15073
15584
|
}, [isControlled, onChange]);
|
|
15585
|
+
// A picked day takes the value's whole time of day, seconds and all, or
|
|
15586
|
+
// midnight in an empty field. The day handed over by the keyboard is the
|
|
15587
|
+
// calendar's focused date, which can carry the time of the clock it
|
|
15588
|
+
// started from, so none of its time is kept.
|
|
15074
15589
|
const handleDateSelect = React.useCallback((d) => {
|
|
15075
15590
|
const merged = setTimeOfDay(d, {
|
|
15076
15591
|
hours: value?.getHours() ?? 0,
|
|
15077
15592
|
minutes: value?.getMinutes() ?? 0,
|
|
15078
|
-
seconds:
|
|
15593
|
+
seconds: value?.getSeconds() ?? 0,
|
|
15594
|
+
milliseconds: value?.getMilliseconds() ?? 0,
|
|
15079
15595
|
});
|
|
15080
15596
|
if (!isWithin(merged, lowerBound, max))
|
|
15081
15597
|
return;
|
|
15082
15598
|
commitValue(merged);
|
|
15083
15599
|
setFocusedDate(d);
|
|
15084
|
-
}, [value,
|
|
15600
|
+
}, [value, lowerBound, max, commitValue]);
|
|
15601
|
+
// The wheels set the value's time. An empty field starts from the focused
|
|
15602
|
+
// day, at the whole minute or second the wheels show.
|
|
15085
15603
|
const handleTimeChange = React.useCallback((parts) => {
|
|
15086
|
-
const
|
|
15087
|
-
|
|
15604
|
+
const next = value
|
|
15605
|
+
? setTimeOfDay(value, parts)
|
|
15606
|
+
: setTimeOfDay(focusedDate, {
|
|
15607
|
+
...parts,
|
|
15608
|
+
seconds: parts.seconds ?? 0,
|
|
15609
|
+
milliseconds: 0,
|
|
15610
|
+
});
|
|
15088
15611
|
if (!isWithin(next, lowerBound, max))
|
|
15089
15612
|
return;
|
|
15090
15613
|
commitValue(next);
|
|
@@ -15098,8 +15621,12 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15098
15621
|
const fmt = typeof defaultFormat === 'string' ? defaultFormat : undefined;
|
|
15099
15622
|
return parseDate(trimmed, fmt ?? DEFAULT_DATETIME_FORMAT);
|
|
15100
15623
|
}, [parse, defaultFormat, locale]);
|
|
15101
|
-
// The Date the user edits when starting without a current value
|
|
15102
|
-
|
|
15624
|
+
// The Date the user edits when starting without a current value: now, at
|
|
15625
|
+
// the whole minute, or the whole second with `enableSeconds`.
|
|
15626
|
+
const makeBaseDate = React.useCallback(() => setTimeOfDay(new Date(), {
|
|
15627
|
+
seconds: enableSeconds ? undefined : 0,
|
|
15628
|
+
milliseconds: 0,
|
|
15629
|
+
}), [enableSeconds]);
|
|
15103
15630
|
const inputReadOnlyAttr = !!readOnly || !editable;
|
|
15104
15631
|
const canOpen = !!popover && !disabled && !readOnly;
|
|
15105
15632
|
// Blocking predicate for manual entry, combining the calendar's
|
|
@@ -15163,20 +15690,20 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15163
15690
|
minutes: value?.getMinutes() ?? 0,
|
|
15164
15691
|
seconds: enableSeconds ? (value?.getSeconds() ?? 0) : undefined,
|
|
15165
15692
|
};
|
|
15166
|
-
const panel = (jsxRuntime.jsxs("div", { className: panelClass, onKeyDown: e => {
|
|
15693
|
+
const panel = (jsxRuntime.jsxs("div", { ref: panelRef, className: panelClass, onKeyDown: e => {
|
|
15167
15694
|
// First Escape collapses the floating time wheels; the popover's own
|
|
15168
15695
|
// Escape handler (which closes the whole panel) only sees the second.
|
|
15169
15696
|
if (timeOpen && e.key === 'Escape') {
|
|
15170
15697
|
e.stopPropagation();
|
|
15171
|
-
|
|
15698
|
+
closeTime();
|
|
15172
15699
|
}
|
|
15173
|
-
}, children: [jsxRuntime.jsxs("div", { className: calendarWrapClass, children: [jsxRuntime.jsx(Calendar, { value: value, focusedDate: focusedDate, onSelect: handleDateSelect, 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 }), timeOpen && (jsxRuntime.jsx("div", { className: timeOverlayClass, onClick: e => {
|
|
15700
|
+
}, children: [jsxRuntime.jsxs("div", { className: calendarWrapClass, children: [jsxRuntime.jsx("div", { ref: calendarRef, children: jsxRuntime.jsx(Calendar, { value: value, focusedDate: focusedDate, onSelect: handleDateSelect, 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 }) }), timeOpen && (jsxRuntime.jsx("div", { ref: timeOverlayRef, className: timeOverlayClass, onClick: e => {
|
|
15174
15701
|
// Tap outside the wheel card (on the covered calendar area)
|
|
15175
15702
|
// collapses the wheels without selecting a date, matching the
|
|
15176
15703
|
// native behavior.
|
|
15177
15704
|
if (e.target === e.currentTarget)
|
|
15178
|
-
|
|
15179
|
-
}, children: jsxRuntime.jsx("div", { className: timeCardClass, children: jsxRuntime.jsx(TimeWheels, { value: spinnerValue, onChange: handleTimeChange, hourFormat: effectiveHourFormat, enableSeconds: enableSeconds, incrementHours: incrementHours, incrementMinutes: incrementMinutes, incrementSeconds: incrementSeconds, unselectableTimes: unselectableTimes, color: color, size: size, disabled: disabled, id: `${popoverId}-time`, labels: labels, itemHeight: wheelItemHeight, audioTick: effectiveAudioTick, onCommit: () => setOpen(false) }) }) }))] }), jsxRuntime.jsxs("div", { className: footerClass, children: [jsxRuntime.jsxs("button", { type: "button", className: footerTimeClass, onClick: () =>
|
|
15705
|
+
closeTime();
|
|
15706
|
+
}, children: jsxRuntime.jsx("div", { className: timeCardClass, children: jsxRuntime.jsx(TimeWheels, { value: spinnerValue, onChange: handleTimeChange, hourFormat: effectiveHourFormat, enableSeconds: enableSeconds, incrementHours: incrementHours, incrementMinutes: incrementMinutes, incrementSeconds: incrementSeconds, unselectableTimes: unselectableTimes, color: color, size: size, disabled: disabled, id: `${popoverId}-time`, labels: labels, itemHeight: wheelItemHeight, audioTick: effectiveAudioTick, onCommit: () => setOpen(false) }) }) }))] }), jsxRuntime.jsxs("div", { className: footerClass, children: [jsxRuntime.jsxs("button", { ref: timeButtonRef, type: "button", className: footerTimeClass, onClick: () => (timeOpen ? closeTime() : setTimeOpen(true)), "aria-expanded": timeOpen, disabled: disabled, children: [jsxRuntime.jsx("span", { children: t.time }), jsxRuntime.jsx("span", { className: footerTimePillClass, children: value ? formatTime(value, timeDisplayFormat, locale) : '—' })] }), jsxRuntime.jsxs("div", { className: footerActionsClass, children: [jsxRuntime.jsx("button", { type: "button", className: footerResetClass, onClick: () => commitValue(valueAtOpenRef.current), children: t.reset }), jsxRuntime.jsx("button", { type: "button", className: footerDoneClass, "aria-label": t.done, onClick: () => setOpen(false), children: jsxRuntime.jsx("svg", { width: "22", height: "22", viewBox: "0 0 22 22", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M5 11l4 4 8-8", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] })] }));
|
|
15180
15707
|
if (inline) {
|
|
15181
15708
|
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), children: [panel, name && (jsxRuntime.jsx("input", { type: "hidden", name: name, form: form, value: value ? toIsoDateTime(value, enableSeconds) : '', required: required }))] }));
|
|
15182
15709
|
}
|
|
@@ -15220,13 +15747,14 @@ const DateTimeInput = React.forwardRef((props, ref) => {
|
|
|
15220
15747
|
// icon-left container has nothing to anchor to, and it renders no
|
|
15221
15748
|
// Control inside one or out. The left icon is checked as the caller
|
|
15222
15749
|
// passed it: the default glyph is this component's own choice, not a
|
|
15223
|
-
// prop the caller set.
|
|
15750
|
+
// prop the caller set, so it goes in only for the advice.
|
|
15224
15751
|
const ownControl = rendersOwnControl('DateTimeInput', {
|
|
15225
15752
|
insideControl,
|
|
15226
15753
|
inline: baseProps.inline,
|
|
15227
15754
|
isLoading,
|
|
15228
15755
|
iconLeft,
|
|
15229
15756
|
iconLeftName: props.iconLeftName,
|
|
15757
|
+
defaultIconLeftName: iconLeftName,
|
|
15230
15758
|
iconLeftSize,
|
|
15231
15759
|
iconRight,
|
|
15232
15760
|
iconRightName,
|
|
@@ -15261,6 +15789,354 @@ const DateTimeInput = React.forwardRef((props, ref) => {
|
|
|
15261
15789
|
});
|
|
15262
15790
|
DateTimeInput.displayName = 'DateTimeInput';
|
|
15263
15791
|
|
|
15792
|
+
const EMPTY_RANGE = [null, null];
|
|
15793
|
+
/** A range's end as the form and the native inputs carry it. */
|
|
15794
|
+
const isoDay = (d) => (d ? toIsoValue(d, 'day') : '');
|
|
15795
|
+
/** Today at local midnight, where typing into an empty end starts. */
|
|
15796
|
+
const today = () => startOfDay(new Date());
|
|
15797
|
+
/**
|
|
15798
|
+
* Raw DateRangeInput: the two inputs and the popover calendar without
|
|
15799
|
+
* Field/Control wrapping. Use `DateRangeInput` for the convenience wrapper.
|
|
15800
|
+
*
|
|
15801
|
+
* @function
|
|
15802
|
+
* @param {DateRangeInputBaseProps} props
|
|
15803
|
+
* @returns {JSX.Element}
|
|
15804
|
+
*/
|
|
15805
|
+
const DateRangeInputBase = React.forwardRef((props, ref) => {
|
|
15806
|
+
const { value: controlledValue, defaultValue, onChange, onOpen, onClose, min, max, disabled, readOnly, placeholder, format, parse, locale, inline = false, mobileNative = 'auto', editable = true, popover = true, openOnFocus = true, closeOnSelect = true, position = 'bottom-left', appendToBody = false, color, size, isRounded, shouldDisableDate, unselectableDates, allowDisabledInRange = false, firstDayOfWeek = 0, dayNames, monthNames, nearbyMonthDays = true, iconLeftName: _iconLeftName, triggerIcon: triggerIconProp, triggerIconName = 'chevron-down', labels, name, startName, endName, form, required, id, className, onFocus, onFocusCapture, ...rest } = props;
|
|
15807
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
15808
|
+
const controlLoading = useControlLoading();
|
|
15809
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
15810
|
+
const t = mergeLabels(labels);
|
|
15811
|
+
const resolvedFormat = format ?? DEFAULT_DATE_FORMAT;
|
|
15812
|
+
const isControlled = controlledValue !== undefined;
|
|
15813
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue ?? EMPTY_RANGE);
|
|
15814
|
+
const range = isControlled ? controlledValue : internalValue;
|
|
15815
|
+
const [start, end] = range;
|
|
15816
|
+
// Nothing before year 1 is in range, as in HTML's date inputs.
|
|
15817
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
15818
|
+
const [focusedDate, setFocusedDate] = React.useState(() => clampDate(start ?? end ?? new Date(), lowerBound, max));
|
|
15819
|
+
// Re-clamp the focused day if the bounds change after mount, so the
|
|
15820
|
+
// calendar never shows a month outside them.
|
|
15821
|
+
React.useEffect(() => {
|
|
15822
|
+
setFocusedDate(prev => clampDate(prev, lowerBound, max));
|
|
15823
|
+
}, [lowerBound, max]);
|
|
15824
|
+
const [open, setOpenState] = React.useState(false);
|
|
15825
|
+
const [startText, setStartText] = React.useState(() => start ? formatDate(start, resolvedFormat, locale) : '');
|
|
15826
|
+
const [endText, setEndText] = React.useState(() => end ? formatDate(end, resolvedFormat, locale) : '');
|
|
15827
|
+
// Each input shows its end of the range as it changes from outside.
|
|
15828
|
+
React.useEffect(() => {
|
|
15829
|
+
setStartText(start ? formatDate(start, resolvedFormat, locale) : '');
|
|
15830
|
+
}, [start, resolvedFormat, locale]);
|
|
15831
|
+
React.useEffect(() => {
|
|
15832
|
+
setEndText(end ? formatDate(end, resolvedFormat, locale) : '');
|
|
15833
|
+
}, [end, resolvedFormat, locale]);
|
|
15834
|
+
const containerRef = React.useRef(null);
|
|
15835
|
+
const startRef = React.useRef(null);
|
|
15836
|
+
const endRef = React.useRef(null);
|
|
15837
|
+
const reactId = React.useId();
|
|
15838
|
+
const popoverId = id ? `${id}-popover` : `picker-${reactId}`;
|
|
15839
|
+
const { bulmaHelperClasses, rest: cleanRest } = useBulmaClasses(rest);
|
|
15840
|
+
const { shouldUseNative } = useNativeMobilePicker({
|
|
15841
|
+
force: mobileNative === 'auto' ? undefined : mobileNative,
|
|
15842
|
+
});
|
|
15843
|
+
const useNative = !inline && shouldUseNative;
|
|
15844
|
+
// The root borrows DateInput's container class, which places the launcher
|
|
15845
|
+
// and sizes a Control's icons around the field.
|
|
15846
|
+
const containerClass = usePrefixedClassNames('daterangeinput', 'dateinput-container');
|
|
15847
|
+
const fieldClass = usePrefixedClassNames('input', 'daterangeinput-field', {
|
|
15848
|
+
[`is-${color}`]: !!color,
|
|
15849
|
+
[`is-${size}`]: !!size,
|
|
15850
|
+
'is-rounded': isRounded,
|
|
15851
|
+
'is-disabled': disabled,
|
|
15852
|
+
});
|
|
15853
|
+
const inputClass = usePrefixedClassNames('daterangeinput-input');
|
|
15854
|
+
const separatorClass = usePrefixedClassNames('daterangeinput-separator');
|
|
15855
|
+
const triggerClass = usePrefixedClassNames('dateinput-trigger');
|
|
15856
|
+
// What the latest request asked for, so a second request in the same event
|
|
15857
|
+
// sees it before React renders. The callbacks fire here rather than in a
|
|
15858
|
+
// state updater, which StrictMode calls twice.
|
|
15859
|
+
const requestedOpenRef = React.useRef(false);
|
|
15860
|
+
const setOpen = React.useCallback((next) => {
|
|
15861
|
+
if (requestedOpenRef.current === next)
|
|
15862
|
+
return;
|
|
15863
|
+
requestedOpenRef.current = next;
|
|
15864
|
+
setOpenState(next);
|
|
15865
|
+
if (next)
|
|
15866
|
+
onOpen?.();
|
|
15867
|
+
else
|
|
15868
|
+
onClose?.();
|
|
15869
|
+
}, [onOpen, onClose]);
|
|
15870
|
+
// Focus that moves inside the field, from one input to the other or from
|
|
15871
|
+
// the launcher, is not focus arriving, so it opens nothing. Set as the
|
|
15872
|
+
// focus is captured on the way in, and cleared once the input has handled
|
|
15873
|
+
// it, so a click that follows still opens the popover.
|
|
15874
|
+
const focusFromInsideRef = React.useRef(false);
|
|
15875
|
+
const handleFocusCapture = (e) => {
|
|
15876
|
+
const from = e.relatedTarget;
|
|
15877
|
+
focusFromInsideRef.current = !!from && e.currentTarget.contains(from);
|
|
15878
|
+
onFocusCapture?.(e);
|
|
15879
|
+
};
|
|
15880
|
+
const handleFocus = (e) => {
|
|
15881
|
+
focusFromInsideRef.current = false;
|
|
15882
|
+
onFocus?.(e);
|
|
15883
|
+
};
|
|
15884
|
+
const requestOpen = React.useCallback((next) => {
|
|
15885
|
+
if (next && focusFromInsideRef.current)
|
|
15886
|
+
return;
|
|
15887
|
+
setOpen(next);
|
|
15888
|
+
}, [setOpen]);
|
|
15889
|
+
const commitRange = React.useCallback((next) => {
|
|
15890
|
+
if (!isControlled)
|
|
15891
|
+
setInternalValue(next);
|
|
15892
|
+
onChange?.(next);
|
|
15893
|
+
}, [isControlled, onChange]);
|
|
15894
|
+
const constraints = React.useMemo(() => ({ shouldDisableDate, unselectableDates }), [shouldDisableDate, unselectableDates]);
|
|
15895
|
+
// A day the calendar disables: shouldDisableDate or unselectableDates.
|
|
15896
|
+
// Typing and the native inputs check the bounds alongside it.
|
|
15897
|
+
const isBlockedDay = React.useCallback((d) => !!shouldDisableDate?.(d) ||
|
|
15898
|
+
!!unselectableDates?.some(u => isSameDay(u, d)), [shouldDisableDate, unselectableDates]);
|
|
15899
|
+
// An end the range can take: it is not disabled, and it closes a range
|
|
15900
|
+
// from the start, which it can't when it comes first or, unless allowed,
|
|
15901
|
+
// reaches over a disabled day.
|
|
15902
|
+
const isBlockedEnd = React.useCallback((d) => isBlockedDay(d) ||
|
|
15903
|
+
(!!start && !canCloseRange(start, d, constraints, allowDisabledInRange)), [isBlockedDay, start, constraints, allowDisabledInRange]);
|
|
15904
|
+
// A new start keeps the end only while the two still make a range, so a
|
|
15905
|
+
// start moved past the end, or over a disabled day before it, clears it.
|
|
15906
|
+
const commitStart = React.useCallback((next) => {
|
|
15907
|
+
const keepsEnd = !next ||
|
|
15908
|
+
!end ||
|
|
15909
|
+
canCloseRange(next, end, constraints, allowDisabledInRange);
|
|
15910
|
+
commitRange([next, keepsEnd ? end : null]);
|
|
15911
|
+
if (next)
|
|
15912
|
+
setFocusedDate(next);
|
|
15913
|
+
}, [end, constraints, allowDisabledInRange, commitRange]);
|
|
15914
|
+
// Every digit typed into the start commits, so a start passes through
|
|
15915
|
+
// values on its way to the one being typed. The first digits of a year make
|
|
15916
|
+
// a start centuries back, which can't keep the end once a disabled day may
|
|
15917
|
+
// lie between. Such a value waits for the segment's next digit rather than
|
|
15918
|
+
// clearing an end the finished start may keep.
|
|
15919
|
+
const startCostsEnd = React.useCallback((d) => !!end && !canCloseRange(d, end, constraints, allowDisabledInRange), [end, constraints, allowDisabledInRange]);
|
|
15920
|
+
const commitEnd = React.useCallback((next) => {
|
|
15921
|
+
commitRange([start, next]);
|
|
15922
|
+
if (next)
|
|
15923
|
+
setFocusedDate(next);
|
|
15924
|
+
}, [start, commitRange]);
|
|
15925
|
+
const handleRangeSelect = React.useCallback((from, to) => {
|
|
15926
|
+
commitRange([from, to]);
|
|
15927
|
+
setFocusedDate(to);
|
|
15928
|
+
if (closeOnSelect)
|
|
15929
|
+
setOpen(false);
|
|
15930
|
+
}, [commitRange, closeOnSelect, setOpen]);
|
|
15931
|
+
const tryParse = React.useCallback((s) => {
|
|
15932
|
+
const trimmed = s.trim();
|
|
15933
|
+
if (!trimmed)
|
|
15934
|
+
return null;
|
|
15935
|
+
const fmt = typeof format === 'string' ? format : undefined;
|
|
15936
|
+
return parse
|
|
15937
|
+
? parse(trimmed)
|
|
15938
|
+
: parseDate(trimmed, fmt ?? DEFAULT_DATE_FORMAT);
|
|
15939
|
+
}, [parse, format, locale]);
|
|
15940
|
+
// Typing into an empty end starts from the start, so only what differs
|
|
15941
|
+
// needs typing; an empty start starts from today.
|
|
15942
|
+
const makeEndBase = React.useCallback(() => (start ? startOfDay(start) : today()), [start]);
|
|
15943
|
+
// Where focus goes back as the popover closes: the input focused last,
|
|
15944
|
+
// which is the one that opened it unless the launcher did, or the start
|
|
15945
|
+
// input before either has had focus.
|
|
15946
|
+
const openerRef = React.useRef(null);
|
|
15947
|
+
const restoreFocusRef = React.useMemo(() => ({
|
|
15948
|
+
get current() {
|
|
15949
|
+
return openerRef.current ?? startRef.current;
|
|
15950
|
+
},
|
|
15951
|
+
}), []);
|
|
15952
|
+
const shared = {
|
|
15953
|
+
format: resolvedFormat,
|
|
15954
|
+
formatFn: formatDate,
|
|
15955
|
+
tryParse,
|
|
15956
|
+
locale,
|
|
15957
|
+
min: lowerBound,
|
|
15958
|
+
max,
|
|
15959
|
+
disabled,
|
|
15960
|
+
readOnly,
|
|
15961
|
+
editable,
|
|
15962
|
+
popover,
|
|
15963
|
+
openOnFocus,
|
|
15964
|
+
closeOnSelect,
|
|
15965
|
+
isOpen: open,
|
|
15966
|
+
setOpen: requestOpen,
|
|
15967
|
+
};
|
|
15968
|
+
// Each input is its own picker field. Its blur counts focus leaving it,
|
|
15969
|
+
// even for the other input, so text typed into one is read as focus moves
|
|
15970
|
+
// to the other.
|
|
15971
|
+
const { inputHandlers: startHandlers } = useSegmentedEntry({
|
|
15972
|
+
...shared,
|
|
15973
|
+
value: start,
|
|
15974
|
+
commitValue: commitStart,
|
|
15975
|
+
text: startText,
|
|
15976
|
+
setText: setStartText,
|
|
15977
|
+
makeBaseDate: today,
|
|
15978
|
+
isBlocked: isBlockedDay,
|
|
15979
|
+
isUnfinishedBlocked: startCostsEnd,
|
|
15980
|
+
inputRef: startRef,
|
|
15981
|
+
containerRef: startRef,
|
|
15982
|
+
onFocus: () => {
|
|
15983
|
+
openerRef.current = startRef.current;
|
|
15984
|
+
},
|
|
15985
|
+
});
|
|
15986
|
+
const { inputHandlers: endHandlers } = useSegmentedEntry({
|
|
15987
|
+
...shared,
|
|
15988
|
+
value: end,
|
|
15989
|
+
commitValue: commitEnd,
|
|
15990
|
+
text: endText,
|
|
15991
|
+
setText: setEndText,
|
|
15992
|
+
makeBaseDate: makeEndBase,
|
|
15993
|
+
isBlocked: isBlockedEnd,
|
|
15994
|
+
inputRef: endRef,
|
|
15995
|
+
containerRef: endRef,
|
|
15996
|
+
onFocus: () => {
|
|
15997
|
+
openerRef.current = endRef.current;
|
|
15998
|
+
},
|
|
15999
|
+
});
|
|
16000
|
+
const combinedRef = React.useCallback((node) => {
|
|
16001
|
+
startRef.current =
|
|
16002
|
+
node;
|
|
16003
|
+
if (typeof ref === 'function')
|
|
16004
|
+
ref(node);
|
|
16005
|
+
else if (ref)
|
|
16006
|
+
ref.current = node;
|
|
16007
|
+
}, [ref]);
|
|
16008
|
+
const handlePopoverClose = React.useCallback(() => setOpen(false), [setOpen]);
|
|
16009
|
+
const startField = startName ?? (name ? `${name}[start]` : undefined);
|
|
16010
|
+
const endField = endName ?? (name ? `${name}[end]` : undefined);
|
|
16011
|
+
const hiddenInputs = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [startField && (jsxRuntime.jsx("input", { type: "hidden", name: startField, form: form, value: isoDay(start) })), endField && (jsxRuntime.jsx("input", { type: "hidden", name: endField, form: form, value: isoDay(end) }))] }));
|
|
16012
|
+
const separator = (jsxRuntime.jsx("span", { className: separatorClass, "aria-hidden": "true", children: t.rangeSeparator }));
|
|
16013
|
+
// Native mobile path: two <input type="date">s, held to the same rules as
|
|
16014
|
+
// typing. A change the rules refuse leaves the input showing the range.
|
|
16015
|
+
if (useNative) {
|
|
16016
|
+
const nativeChange = (commit, isBlocked) => (e) => {
|
|
16017
|
+
// An empty value is the end cleared. A date input gives nothing else
|
|
16018
|
+
// that doesn't read as a date.
|
|
16019
|
+
const parsed = fromIsoValue(e.target.value, 'day');
|
|
16020
|
+
if (!parsed)
|
|
16021
|
+
commit(null);
|
|
16022
|
+
else if (isWithin(parsed, lowerBound, max) && !isBlocked(parsed)) {
|
|
16023
|
+
commit(parsed);
|
|
16024
|
+
}
|
|
16025
|
+
};
|
|
16026
|
+
const minIso = min ? isoDay(lowerBound) : undefined;
|
|
16027
|
+
const maxIso = max ? isoDay(max) : undefined;
|
|
16028
|
+
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, role: "group", className: classNames(containerClass, bulmaHelperClasses, className), onFocus: onFocus, onFocusCapture: onFocusCapture, children: [jsxRuntime.jsxs("div", { className: fieldClass, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "date", id: id, "aria-label": t.rangeStart, className: inputClass, value: isoDay(start), onChange: nativeChange(commitStart, isBlockedDay), min: minIso, max: maxIso, disabled: disabled, readOnly: readOnly, required: required }), separator, jsxRuntime.jsx("input", { ref: endRef, type: "date", id: id ? `${id}-end` : undefined, "aria-label": t.rangeEnd, className: inputClass, value: isoDay(end), onChange: nativeChange(commitEnd, isBlockedEnd), min: start ? isoDay(start) : minIso, max: maxIso, disabled: disabled, readOnly: readOnly, required: required })] }), hiddenInputs] }));
|
|
16029
|
+
}
|
|
16030
|
+
const calendar = (jsxRuntime.jsx(Calendar, { range: range, onRangeSelect: handleRangeSelect, allowDisabledInRange: allowDisabledInRange, focusedDate: focusedDate, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size,
|
|
16031
|
+
// The popover panel takes `popoverId`, so the calendar inside it takes
|
|
16032
|
+
// one of its own. Inline there is no panel.
|
|
16033
|
+
id: inline ? popoverId : `${popoverId}-cal`, autoFocusCell: open, labels: labels }));
|
|
16034
|
+
if (inline) {
|
|
16035
|
+
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, role: "group", className: classNames(containerClass, bulmaHelperClasses, className), onFocus: onFocus, onFocusCapture: onFocusCapture, children: [calendar, hiddenInputs] }));
|
|
16036
|
+
}
|
|
16037
|
+
const canOpen = !!popover && !disabled && !readOnly;
|
|
16038
|
+
const inputReadOnly = !!readOnly || !editable;
|
|
16039
|
+
// The field reads as one text box, so a press on it that misses both
|
|
16040
|
+
// inputs, on its padding, the gap or the separator, goes to the nearer
|
|
16041
|
+
// input and does what a press on that input does.
|
|
16042
|
+
const handleFieldMouseDown = (e) => {
|
|
16043
|
+
const startInput = startRef.current;
|
|
16044
|
+
const endInput = endRef.current;
|
|
16045
|
+
if (disabled || e.button !== 0)
|
|
16046
|
+
return;
|
|
16047
|
+
if (e.target === startInput || e.target === endInput)
|
|
16048
|
+
return;
|
|
16049
|
+
e.preventDefault();
|
|
16050
|
+
const middle = (startInput.getBoundingClientRect().right +
|
|
16051
|
+
endInput.getBoundingClientRect().left) /
|
|
16052
|
+
2;
|
|
16053
|
+
(e.clientX < middle ? startInput : endInput).focus();
|
|
16054
|
+
if (openOnFocus && canOpen)
|
|
16055
|
+
requestOpen(true);
|
|
16056
|
+
};
|
|
16057
|
+
const comboboxProps = {
|
|
16058
|
+
type: 'text',
|
|
16059
|
+
role: 'combobox',
|
|
16060
|
+
'aria-haspopup': 'dialog',
|
|
16061
|
+
'aria-expanded': open,
|
|
16062
|
+
'aria-controls': popoverId,
|
|
16063
|
+
autoComplete: 'off',
|
|
16064
|
+
className: inputClass,
|
|
16065
|
+
placeholder,
|
|
16066
|
+
disabled,
|
|
16067
|
+
readOnly: inputReadOnly,
|
|
16068
|
+
required,
|
|
16069
|
+
};
|
|
16070
|
+
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, role: "group", className: classNames(containerClass, bulmaHelperClasses, className), onFocus: handleFocus, onFocusCapture: handleFocusCapture, children: [jsxRuntime.jsxs("div", { className: fieldClass, onMouseDown: handleFieldMouseDown, children: [jsxRuntime.jsx("input", { ...comboboxProps, ref: combinedRef, id: id, "aria-label": t.rangeStart, value: startText, ...startHandlers }), separator, jsxRuntime.jsx("input", { ...comboboxProps, ref: endRef, id: id ? `${id}-end` : undefined, "aria-label": t.rangeEnd, value: endText, ...endHandlers })] }), popover && triggerIcon && (jsxRuntime.jsx("button", { type: "button", className: triggerClass,
|
|
16071
|
+
// Disabled whenever the popover can't open, so a click can.
|
|
16072
|
+
onClick: () => setOpen(!open), disabled: !canOpen, "aria-label": t.chooseDateRange, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: handlePopoverClose, anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseDateRange, id: popoverId, restoreFocusRef: restoreFocusRef, initialFocusSelector: CALENDAR_FOCUSED_CELL, children: calendar })), hiddenInputs] }));
|
|
16073
|
+
});
|
|
16074
|
+
DateRangeInputBase.displayName = 'DateRangeInputBase';
|
|
16075
|
+
|
|
16076
|
+
/**
|
|
16077
|
+
* The `DateRangeInput` component is a form input for a start and end date, picked from one popover calendar or typed into two segmented inputs.
|
|
16078
|
+
*
|
|
16079
|
+
* @function
|
|
16080
|
+
* @param {DateRangeInputProps} props - Props for the DateRangeInput.
|
|
16081
|
+
* @returns {JSX.Element}
|
|
16082
|
+
*
|
|
16083
|
+
* @example
|
|
16084
|
+
* <DateRangeInput label="Stay" name="stay" min={new Date()} />
|
|
16085
|
+
*/
|
|
16086
|
+
const DateRangeInput = React.forwardRef((props, ref) => {
|
|
16087
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
16088
|
+
const insideField = useInsideField();
|
|
16089
|
+
const insideControl = useInsideControl();
|
|
16090
|
+
const ownField = rendersOwnField('DateRangeInput', {
|
|
16091
|
+
insideField,
|
|
16092
|
+
insideControl,
|
|
16093
|
+
label,
|
|
16094
|
+
message,
|
|
16095
|
+
horizontal,
|
|
16096
|
+
fieldClassName,
|
|
16097
|
+
});
|
|
16098
|
+
// Inline mode renders a bare calendar with no input, so the Control's
|
|
16099
|
+
// icon containers have nothing to anchor to, and it renders no Control
|
|
16100
|
+
// inside one or out. The left icon is checked as the caller passed it:
|
|
16101
|
+
// the default glyph is this component's own choice, not a prop the
|
|
16102
|
+
// caller set, so it goes in only for the advice.
|
|
16103
|
+
const ownControl = rendersOwnControl('DateRangeInput', {
|
|
16104
|
+
insideControl,
|
|
16105
|
+
inline: baseProps.inline,
|
|
16106
|
+
isLoading,
|
|
16107
|
+
iconLeft,
|
|
16108
|
+
iconLeftName: props.iconLeftName,
|
|
16109
|
+
defaultIconLeftName: iconLeftName,
|
|
16110
|
+
iconLeftSize,
|
|
16111
|
+
iconRight,
|
|
16112
|
+
iconRightName,
|
|
16113
|
+
iconRightSize,
|
|
16114
|
+
hasIconsLeft,
|
|
16115
|
+
hasIconsRight,
|
|
16116
|
+
isExpanded,
|
|
16117
|
+
controlSize,
|
|
16118
|
+
controlClassName,
|
|
16119
|
+
});
|
|
16120
|
+
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
16121
|
+
label,
|
|
16122
|
+
labelProps,
|
|
16123
|
+
rendersLabel: ownField,
|
|
16124
|
+
});
|
|
16125
|
+
const helpClass = usePrefixedClassNames('help', {
|
|
16126
|
+
[`is-${messageColor}`]: !!messageColor,
|
|
16127
|
+
});
|
|
16128
|
+
let content = (jsxRuntime.jsx(DateRangeInputBase, { ref: ref, "aria-labelledby": ariaLabelledBy, ...baseProps }));
|
|
16129
|
+
if (ownControl) {
|
|
16130
|
+
content = (jsxRuntime.jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
16131
|
+
}
|
|
16132
|
+
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
16133
|
+
if (ownField) {
|
|
16134
|
+
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
16135
|
+
}
|
|
16136
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
16137
|
+
});
|
|
16138
|
+
DateRangeInput.displayName = 'DateRangeInput';
|
|
16139
|
+
|
|
15264
16140
|
/**
|
|
15265
16141
|
* The `Cell` component provides a single Bulma grid cell for use inside the [`Grid`](./grid.md) component.
|
|
15266
16142
|
*
|
|
@@ -16667,6 +17543,8 @@ exports.Content = Content;
|
|
|
16667
17543
|
exports.Control = Control;
|
|
16668
17544
|
exports.DateInput = DateInput;
|
|
16669
17545
|
exports.DateInputBase = DateInputBase;
|
|
17546
|
+
exports.DateRangeInput = DateRangeInput;
|
|
17547
|
+
exports.DateRangeInputBase = DateRangeInputBase;
|
|
16670
17548
|
exports.DateTimeInput = DateTimeInput;
|
|
16671
17549
|
exports.DateTimeInputBase = DateTimeInputBase;
|
|
16672
17550
|
exports.Delete = Delete;
|