Compare commits
32 Commits
6bc156284b
...
44cf58f557
| Author | SHA1 | Date | |
|---|---|---|---|
| 44cf58f557 | |||
| 8f70ad1a20 | |||
| 32d7e1645d | |||
| 68f343d32a | |||
| ec4f5b2d13 | |||
| 31a8304b73 | |||
| 4d799f8820 | |||
| be9372c1ee | |||
| 377fb90536 | |||
| 88ac53fa95 | |||
| 2e775174a2 | |||
| f2fef1d3f1 | |||
| 672661a5c8 | |||
| 8323c8f301 | |||
| 5c8ed35ec1 | |||
| a36586edd3 | |||
| efe4aaf0bd | |||
| b1c77f60d2 | |||
| e228c25e40 | |||
| 43c0eafa52 | |||
| e11eed83c5 | |||
| 3e1857c49b | |||
| 62310e80b0 | |||
| dcda048b27 | |||
| 32f5e27a42 | |||
| 1523f7be2c | |||
| b664c99944 | |||
| 3d4a4a5f57 | |||
| f9864842b5 | |||
| dd084369e9 | |||
| 850eed0766 | |||
| 4904bea29c |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@panic/web-ui",
|
"name": "@panic/web-ui",
|
||||||
"version": "0.1.17",
|
"version": "0.1.18",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"description": "Core components for panic.haus web applications",
|
"description": "Core components for panic.haus web applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -90,5 +90,8 @@
|
|||||||
"vite": "^7.0.0",
|
"vite": "^7.0.0",
|
||||||
"vitest": "^4.0.18",
|
"vitest": "^4.0.18",
|
||||||
"yjs": "^13.6.24"
|
"yjs": "^13.6.24"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^25.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const meta = {
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
component:
|
component:
|
||||||
'Date selection field with InputField-compatible API, supporting date/time/datetime-local values, size/layout variants, and validation state.',
|
'In-house date/time selection field with InputField-compatible API. Uses a custom popup (not native browser pickers) and supports date, time, and date-time modes.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -27,10 +27,10 @@ const meta = {
|
|||||||
table: { type: { summary: 'string' } },
|
table: { type: { summary: 'string' } },
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
description: 'Native date input type.',
|
description: 'DatePicker mode.',
|
||||||
options: ['date', 'datetime-local', 'time'],
|
options: ['date', 'date-time', 'time'],
|
||||||
control: 'inline-radio',
|
control: 'inline-radio',
|
||||||
table: { type: { summary: "'date' | 'datetime-local' | 'time'" } },
|
table: { type: { summary: "'date' | 'date-time' | 'time'" } },
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
description: 'Input size.',
|
description: 'Input size.',
|
||||||
@@ -55,6 +55,22 @@ const meta = {
|
|||||||
control: 'text',
|
control: 'text',
|
||||||
table: { type: { summary: 'string' } },
|
table: { type: { summary: 'string' } },
|
||||||
},
|
},
|
||||||
|
format: {
|
||||||
|
description:
|
||||||
|
'Optional input/output format. Supported tokens: `dd`, `mm`, `yyyy`, `HH` (for example `dd/mm/yyyy HH:mm`).',
|
||||||
|
control: 'text',
|
||||||
|
table: { type: { summary: 'string' } },
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
description: 'Optional minimum value in the same format as `value`.',
|
||||||
|
control: 'text',
|
||||||
|
table: { type: { summary: 'string' } },
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
description: 'Optional maximum value in the same format as `value`.',
|
||||||
|
control: 'text',
|
||||||
|
table: { type: { summary: 'string' } },
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
description: 'Native input `name` attribute.',
|
description: 'Native input `name` attribute.',
|
||||||
control: 'text',
|
control: 'text',
|
||||||
@@ -108,7 +124,7 @@ const meta = {
|
|||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
value: '',
|
value: '',
|
||||||
size: 'md',
|
size: 'md',
|
||||||
width: 'md',
|
width: 'md',
|
||||||
@@ -124,8 +140,8 @@ export const DateOnly: Story = {
|
|||||||
type: 'date',
|
type: 'date',
|
||||||
label: 'Publish date',
|
label: 'Publish date',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function DateOnlyRender(args) {
|
||||||
const [value, setValue] = useState('2031-05-20');
|
const [value, setValue] = useState('2031/05/20');
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...args}
|
{...args}
|
||||||
@@ -141,11 +157,11 @@ export const DateOnly: Story = {
|
|||||||
|
|
||||||
export const DateTime: Story = {
|
export const DateTime: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function DateTimeRender(args) {
|
||||||
const [value, setValue] = useState('2031-05-20T14:30');
|
const [value, setValue] = useState('2031/05/20 14:30');
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...args}
|
{...args}
|
||||||
@@ -167,7 +183,7 @@ export const TimeOnlyInline: Story = {
|
|||||||
size: 'sm',
|
size: 'sm',
|
||||||
rightIcon: <CalendarDaysIcon className="h-4 w-4 ui-body-secondary" />,
|
rightIcon: <CalendarDaysIcon className="h-4 w-4 ui-body-secondary" />,
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function TimeOnlyInlineRender(args) {
|
||||||
const [value, setValue] = useState('09:00');
|
const [value, setValue] = useState('09:00');
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@@ -182,9 +198,10 @@ export const TimeOnlyInline: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Error: Story = {
|
export const ErrorState: Story = {
|
||||||
|
name: 'Error',
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
value: '',
|
value: '',
|
||||||
error: 'Pick a valid future date and time',
|
error: 'Pick a valid future date and time',
|
||||||
@@ -193,20 +210,20 @@ export const Error: Story = {
|
|||||||
|
|
||||||
export const Disabled: Story = {
|
export const Disabled: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Published at',
|
label: 'Published at',
|
||||||
value: '2031-05-20T14:30',
|
value: '2031/05/20 14:30',
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SizeMatrix: Story = {
|
export const SizeMatrix: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function SizeMatrixRender(args) {
|
||||||
const [value, setValue] = useState('2031-05-20T14:30');
|
const [value, setValue] = useState('2031/05/20 14:30');
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-3">
|
<div className="grid grid-cols-1 gap-3">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@@ -238,3 +255,26 @@ export const SizeMatrix: Story = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CustomFormatWithRange: Story = {
|
||||||
|
args: {
|
||||||
|
type: 'date-time',
|
||||||
|
label: 'Starts at',
|
||||||
|
format: 'dd/mm/yyyy HH:mm',
|
||||||
|
min: '10/03/2026 09:00',
|
||||||
|
max: '24/03/2026 18:30',
|
||||||
|
},
|
||||||
|
render: function CustomFormatWithRangeRender(args) {
|
||||||
|
const [value, setValue] = useState('22/03/2026 14:30');
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
{...args}
|
||||||
|
value={value}
|
||||||
|
onChange={(event) => {
|
||||||
|
setValue(event.target.value);
|
||||||
|
args.onChange?.(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -99,7 +99,7 @@ export default meta;
|
|||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Stacked: Story = {
|
export const Stacked: Story = {
|
||||||
render: (args) => {
|
render: function StackedRender(args) {
|
||||||
const [value, setValue] = useState(args.value);
|
const [value, setValue] = useState(args.value);
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -119,7 +119,7 @@ export const Inline: Story = {
|
|||||||
layout: 'inline',
|
layout: 'inline',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function InlineRender(args) {
|
||||||
const [value, setValue] = useState(args.value);
|
const [value, setValue] = useState(args.value);
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -147,7 +147,7 @@ export const WithError: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SizeMatrix: Story = {
|
export const SizeMatrix: Story = {
|
||||||
render: (args) => {
|
render: function SizeMatrixRender(args) {
|
||||||
const [value, setValue] = useState(args.value);
|
const [value, setValue] = useState(args.value);
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-3">
|
<div className="grid grid-cols-1 gap-3">
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const Basic: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const WithActions: Story = {
|
export const WithActions: Story = {
|
||||||
render: (args) => {
|
render: function WithActionsRender(args) {
|
||||||
const [title, setTitle] = useState('Storybook powered CMS');
|
const [title, setTitle] = useState('Storybook powered CMS');
|
||||||
const [status, setStatus] = useState('draft');
|
const [status, setStatus] = useState('draft');
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const Text: Story = {
|
|||||||
label: 'Title',
|
label: 'Title',
|
||||||
placeholder: 'Write a title',
|
placeholder: 'Write a title',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function TextRender(args) {
|
||||||
const [value, setValue] = useState('Storybook integration');
|
const [value, setValue] = useState('Storybook integration');
|
||||||
return (
|
return (
|
||||||
<InputField
|
<InputField
|
||||||
@@ -148,7 +148,7 @@ export const PasswordWithToggle: Story = {
|
|||||||
label: 'Password',
|
label: 'Password',
|
||||||
placeholder: 'Type a strong password',
|
placeholder: 'Type a strong password',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function PasswordWithToggleRender(args) {
|
||||||
const [value, setValue] = useState('pa55word');
|
const [value, setValue] = useState('pa55word');
|
||||||
return (
|
return (
|
||||||
<InputField
|
<InputField
|
||||||
@@ -171,7 +171,7 @@ export const InlineWithIcon: Story = {
|
|||||||
size: 'sm',
|
size: 'sm',
|
||||||
rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" />,
|
rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" />,
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function InlineWithIconRender(args) {
|
||||||
const [value, setValue] = useState('posts');
|
const [value, setValue] = useState('posts');
|
||||||
return (
|
return (
|
||||||
<InputField
|
<InputField
|
||||||
@@ -186,7 +186,8 @@ export const InlineWithIcon: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Error: Story = {
|
export const ErrorState: Story = {
|
||||||
|
name: 'Error',
|
||||||
args: {
|
args: {
|
||||||
type: 'email',
|
type: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
@@ -210,7 +211,7 @@ export const SizeMatrix: Story = {
|
|||||||
label: 'Name',
|
label: 'Name',
|
||||||
placeholder: 'Enter value',
|
placeholder: 'Enter value',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function SizeMatrixRender(args) {
|
||||||
const [value, setValue] = useState('Beatrice');
|
const [value, setValue] = useState('Beatrice');
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-3">
|
<div className="grid grid-cols-1 gap-3">
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ type Story = StoryObj<typeof meta>;
|
|||||||
|
|
||||||
export const Body: Story = {};
|
export const Body: Story = {};
|
||||||
|
|
||||||
export const Error: Story = {
|
export const ErrorState: Story = {
|
||||||
|
name: 'Error',
|
||||||
args: {
|
args: {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
children: 'This field is required',
|
children: 'This field is required',
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default meta;
|
|||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Editable: Story = {
|
export const Editable: Story = {
|
||||||
render: (args) => {
|
render: function EditableRender(args) {
|
||||||
const [markdown, setMarkdown] = useState(args.markdown);
|
const [markdown, setMarkdown] = useState(args.markdown);
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-2xl">
|
<div className="w-full max-w-2xl">
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export const Empty: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const InteractiveSortingAndPagination: Story = {
|
export const InteractiveSortingAndPagination: Story = {
|
||||||
render: () => {
|
render: function InteractiveSortingAndPaginationRender() {
|
||||||
const [sorting, setSorting] = useState<SortState | null>({
|
const [sorting, setSorting] = useState<SortState | null>({
|
||||||
field: 'name',
|
field: 'name',
|
||||||
direction: 'asc',
|
direction: 'asc',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
--bg-page: #16121a;
|
--bg-page: #16121a;
|
||||||
--surface-bg: rgba(24, 24, 27, 0.45);
|
--surface-bg: rgba(24, 24, 27, 0.45);
|
||||||
--surface-bg-strong: rgba(24, 24, 27, 0.62);
|
--surface-bg-strong: rgba(24, 24, 27, 0.62);
|
||||||
|
--datepicker-menu-bg: #18181b;
|
||||||
--surface-border: rgba(82, 82, 91, 0.6);
|
--surface-border: rgba(82, 82, 91, 0.6);
|
||||||
--surface-divider: rgba(63, 63, 70, 0.85);
|
--surface-divider: rgba(63, 63, 70, 0.85);
|
||||||
--text-primary: #d5cfdf;
|
--text-primary: #d5cfdf;
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
--bg-page: #f7f7fb;
|
--bg-page: #f7f7fb;
|
||||||
--surface-bg: rgba(255, 255, 255, 0.9);
|
--surface-bg: rgba(255, 255, 255, 0.9);
|
||||||
--surface-bg-strong: rgba(255, 255, 255, 0.98);
|
--surface-bg-strong: rgba(255, 255, 255, 0.98);
|
||||||
|
--datepicker-menu-bg: #ffffff;
|
||||||
--surface-border: rgba(161, 161, 170, 0.45);
|
--surface-border: rgba(161, 161, 170, 0.45);
|
||||||
--surface-divider: rgba(212, 212, 216, 0.9);
|
--surface-divider: rgba(212, 212, 216, 0.9);
|
||||||
--text-primary: #52485c;
|
--text-primary: #52485c;
|
||||||
|
|||||||
@@ -327,3 +327,190 @@
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
@apply px-4 py-3 text-sm;
|
@apply px-4 py-3 text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn {
|
||||||
|
color: var(--text-muted);
|
||||||
|
@apply absolute inset-y-0 right-2 my-auto inline-flex h-6 w-6 items-center justify-center rounded-md border border-transparent bg-transparent p-0 transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgb(var(--accent-400));
|
||||||
|
box-shadow: 0 0 0 2px rgb(var(--accent-400) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn:disabled {
|
||||||
|
color: var(--ghost-disabled-text);
|
||||||
|
background-color: transparent;
|
||||||
|
@apply cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-popup {
|
||||||
|
border: 1px solid var(--surface-divider);
|
||||||
|
background-color: var(--surface-bg-strong);
|
||||||
|
box-shadow: var(--shadow-glow);
|
||||||
|
color: var(--text-primary);
|
||||||
|
backdrop-filter: saturate(145%) blur(var(--auth-glass-blur));
|
||||||
|
-webkit-backdrop-filter: saturate(145%) blur(var(--auth-glass-blur));
|
||||||
|
will-change: backdrop-filter;
|
||||||
|
max-width: min(96vw, 440px);
|
||||||
|
@apply fixed z-[70] flex flex-col gap-3 rounded-xl p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-popup-top {
|
||||||
|
transform-origin: bottom center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-popup-bottom {
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen sm {
|
||||||
|
.datepicker-popup {
|
||||||
|
@apply flex-row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-panel {
|
||||||
|
@apply min-w-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-calendar-nav {
|
||||||
|
@apply mb-2 flex items-center justify-between gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-nav-btn {
|
||||||
|
border: 1px solid var(--ghost-border);
|
||||||
|
background-color: var(--ghost-bg);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply inline-flex h-8 w-8 items-center justify-center rounded-lg transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-nav-btn:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-heading-controls {
|
||||||
|
@apply relative flex items-center gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-btn {
|
||||||
|
border: 1px solid var(--field-border);
|
||||||
|
background-color: var(--field-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
@apply inline-flex h-8 items-center rounded-lg px-2.5 text-xs font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-menu {
|
||||||
|
border: 1px solid var(--surface-divider);
|
||||||
|
background-color: var(--datepicker-menu-bg);
|
||||||
|
box-shadow: var(--shadow-glow);
|
||||||
|
@apply absolute left-0 top-full z-20 mt-1 max-h-48 min-w-[9rem] overflow-y-auto rounded-lg p-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-option {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply block w-full rounded-md px-2 py-1.5 text-left text-xs font-medium transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-option:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-option.is-selected {
|
||||||
|
background-color: rgb(var(--accent-500) / 0.22);
|
||||||
|
color: rgb(var(--accent-300));
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-weekdays {
|
||||||
|
@apply mb-1 grid grid-cols-7 gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-weekday {
|
||||||
|
color: var(--text-muted);
|
||||||
|
@apply text-center text-[0.65rem] font-semibold uppercase tracking-[0.08em];
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-grid {
|
||||||
|
@apply grid grid-cols-7 gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply inline-flex h-8 w-8 items-center justify-center rounded-lg text-sm transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day.is-selected {
|
||||||
|
background-color: rgb(var(--accent-500));
|
||||||
|
color: rgb(var(--accent-contrast));
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day.is-today {
|
||||||
|
border: 1px solid rgb(var(--accent-400) / 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day.is-outside-month {
|
||||||
|
color: var(--text-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day:disabled {
|
||||||
|
color: var(--ghost-disabled-text);
|
||||||
|
background-color: transparent;
|
||||||
|
@apply cursor-not-allowed opacity-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-root {
|
||||||
|
@apply grid min-w-[180px] grid-cols-2 gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-column {
|
||||||
|
@apply flex min-w-0 flex-col gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-title {
|
||||||
|
color: var(--text-muted);
|
||||||
|
@apply text-[0.65rem] font-semibold uppercase tracking-[0.08em];
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-list {
|
||||||
|
border: 1px solid var(--field-border);
|
||||||
|
background-color: var(--field-bg);
|
||||||
|
@apply max-h-52 overflow-y-auto rounded-lg p-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply block w-full rounded-md px-2 py-1.5 text-left text-xs font-semibold transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option.is-selected {
|
||||||
|
background-color: rgb(var(--accent-500) / 0.22);
|
||||||
|
color: rgb(var(--accent-300));
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option:disabled {
|
||||||
|
color: var(--ghost-disabled-text);
|
||||||
|
background-color: transparent;
|
||||||
|
@apply cursor-not-allowed opacity-55;
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ describe('Button', () => {
|
|||||||
expect(screen.getByRole('button', { name: 'Details' })).toHaveClass('btn-secondary');
|
expect(screen.getByRole('button', { name: 'Details' })).toHaveClass('btn-secondary');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses explicit variant when provided', () => {
|
||||||
|
render(<Button label="Danger" type="outlined" variant="important" />);
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: 'Danger' })).toHaveClass('btn-important');
|
||||||
|
});
|
||||||
|
|
||||||
it('renders icon-only button and custom aria label', () => {
|
it('renders icon-only button and custom aria label', () => {
|
||||||
render(<Button type="solid" icon={HomeIcon} ariaLabel="Open home" />);
|
render(<Button type="solid" icon={HomeIcon} ariaLabel="Open home" />);
|
||||||
|
|
||||||
|
|||||||
@@ -51,4 +51,12 @@ describe('Chip', () => {
|
|||||||
rerender(<Chip tone=" ">Blank</Chip>);
|
rerender(<Chip tone=" ">Blank</Chip>);
|
||||||
expect(screen.getByText('Blank').getAttribute('style')).toBeNull();
|
expect(screen.getByText('Blank').getAttribute('style')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ignores unresolved direct palettes and unknown shades', () => {
|
||||||
|
const { rerender } = render(<Chip tone="indigo">Palette token</Chip>);
|
||||||
|
expect(screen.getByText('Palette token').getAttribute('style')).toBeNull();
|
||||||
|
|
||||||
|
rerender(<Chip tone="indigo-999">Unknown shade</Chip>);
|
||||||
|
expect(screen.getByText('Unknown shade').getAttribute('style')).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
428
tests/components/DatePicker.logic.test.ts
Normal file
428
tests/components/DatePicker.logic.test.ts
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { __datePickerTestUtils as utils } from '../../src/components/DatePicker';
|
||||||
|
|
||||||
|
type GlobalDescriptor = PropertyDescriptor | undefined;
|
||||||
|
|
||||||
|
function setGlobalProperty(name: string, descriptor: PropertyDescriptor): GlobalDescriptor {
|
||||||
|
const key = name as keyof typeof globalThis;
|
||||||
|
const original = Object.getOwnPropertyDescriptor(globalThis, key);
|
||||||
|
Object.defineProperty(globalThis, key, descriptor);
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreGlobalProperty(name: string, original: GlobalDescriptor): void {
|
||||||
|
const key = name as keyof typeof globalThis;
|
||||||
|
if (original) {
|
||||||
|
Object.defineProperty(globalThis, key, original);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect.deleteProperty(globalThis, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DatePicker logic helpers', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats and clamps numeric values', () => {
|
||||||
|
expect(utils.pad2(3)).toBe('03');
|
||||||
|
expect(utils.pad4(12)).toBe('0012');
|
||||||
|
expect(utils.clampNumber(99, 0, 50)).toBe(50);
|
||||||
|
expect(utils.clampNumber(-1, 0, 50)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates calendar dates strictly', () => {
|
||||||
|
expect(utils.createValidatedDate(2026, 2, 29)).toBeNull();
|
||||||
|
expect(utils.createValidatedDate(2028, 2, 29)).toBeInstanceOf(Date);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves locale with and without navigator', () => {
|
||||||
|
const originalNavigator = setGlobalProperty('navigator', {
|
||||||
|
configurable: true,
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
expect(utils.resolveLocale()).toBe('en-US');
|
||||||
|
restoreGlobalProperty('navigator', originalNavigator);
|
||||||
|
|
||||||
|
const locale = utils.resolveLocale();
|
||||||
|
expect(typeof locale).toBe('string');
|
||||||
|
expect(locale.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves week start across locale and fallback branches', () => {
|
||||||
|
const originalIntl = setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 2 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('de-DE')).toBe(2);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 7 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 0 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 'x' as unknown as number };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
constructor() {
|
||||||
|
throw new Error('boom');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
restoreGlobalProperty('Intl', originalIntl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds and validates format configuration', () => {
|
||||||
|
expect(utils.buildFormatConfigOrNull('date-time', '')).toBeNull();
|
||||||
|
expect(utils.buildFormatConfigOrNull('date-time', 'yyyy/mm/dd HH')).toBeNull();
|
||||||
|
|
||||||
|
const config = utils.buildFormatConfigOrNull('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
expect(config).not.toBeNull();
|
||||||
|
expect(config?.segments).toHaveLength(5);
|
||||||
|
expect(config?.totalLength).toBe(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to default format when requested format is invalid', () => {
|
||||||
|
const config = utils.buildFormatConfig('date-time', 'yyyy/mm/dd');
|
||||||
|
expect(config.format).toBe('yyyy/mm/dd HH:mm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses valid values and rejects invalid input', () => {
|
||||||
|
const dateTimeConfig = utils.buildFormatConfig('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
expect(utils.parsePickerValueWithFormat('22/02/2026 14:30', dateTimeConfig)).toEqual({
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 22),
|
||||||
|
hour: 14,
|
||||||
|
minute: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(utils.parsePickerValueWithFormat('2/2/2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('22-02-2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('aa/02/2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('31/02/2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('22/02/2026 24:30', dateTimeConfig)).toBeNull();
|
||||||
|
|
||||||
|
const timeConfig = utils.buildFormatConfig('time', 'HH:mm');
|
||||||
|
expect(utils.parsePickerValueWithFormat('09:00', timeConfig)).toEqual({
|
||||||
|
date: utils.startOfDay(new Date()),
|
||||||
|
hour: 9,
|
||||||
|
minute: 0,
|
||||||
|
});
|
||||||
|
expect(utils.parsePickerValueWithFormat('09:77', timeConfig)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats picker values with the chosen configuration', () => {
|
||||||
|
const config = utils.buildFormatConfig('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
const text = utils.formatPickerValueWithFormat(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2027, 2, 11),
|
||||||
|
hour: 6,
|
||||||
|
minute: 5,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(text).toBe('11/03/2027 06:05');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compares values by picker type', () => {
|
||||||
|
const date = utils.comparePickerValue(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 23,
|
||||||
|
minute: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 2),
|
||||||
|
hour: 1,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
'date',
|
||||||
|
);
|
||||||
|
expect(date).toBeLessThan(0);
|
||||||
|
|
||||||
|
const time = utils.comparePickerValue(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 9,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 8,
|
||||||
|
minute: 59,
|
||||||
|
},
|
||||||
|
'time',
|
||||||
|
);
|
||||||
|
expect(time).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const dateTime = utils.comparePickerValue(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 1,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 1,
|
||||||
|
minute: 1,
|
||||||
|
},
|
||||||
|
'date-time',
|
||||||
|
);
|
||||||
|
expect(dateTime).toBeLessThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes and enforces picker ranges', () => {
|
||||||
|
const min = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 9,
|
||||||
|
minute: 0,
|
||||||
|
};
|
||||||
|
const max = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalized = utils.normalizeRange(max, min, 'date-time');
|
||||||
|
expect(normalized.minValue).toEqual(min);
|
||||||
|
expect(normalized.maxValue).toEqual(max);
|
||||||
|
|
||||||
|
const below = utils.clampPickerToRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 8,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
);
|
||||||
|
expect(below).toEqual(min);
|
||||||
|
|
||||||
|
const above = utils.clampPickerToRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 30,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
);
|
||||||
|
expect(above).toEqual(max);
|
||||||
|
|
||||||
|
expect(utils.isWithinRange(min, min, max, 'date-time')).toBe(true);
|
||||||
|
expect(
|
||||||
|
utils.isWithinRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 7,
|
||||||
|
minute: 59,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
utils.isWithinRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 1,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies segment edits across year, month, day, hour and minute', () => {
|
||||||
|
const base = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 28),
|
||||||
|
hour: 14,
|
||||||
|
minute: 30,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(utils.applySegmentDigits(base, 'year', '0001').date.getFullYear()).toBe(1);
|
||||||
|
expect(utils.applySegmentDigits(base, 'month', '13').date.getMonth()).toBe(11);
|
||||||
|
expect(utils.applySegmentDigits(base, 'day', '99').date.getDate()).toBe(28);
|
||||||
|
expect(utils.applySegmentDigits(base, 'hour', '88').hour).toBe(23);
|
||||||
|
expect(utils.applySegmentDigits(base, 'minute', '99').minute).toBe(59);
|
||||||
|
|
||||||
|
const unchanged = utils.applySegmentDigits(base, 'day', 'not-a-number');
|
||||||
|
expect(unchanged).toEqual(base);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves segment index from caret position', () => {
|
||||||
|
const config = utils.buildFormatConfig('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
|
||||||
|
expect(utils.findSegmentIndexByCaret([], 3)).toBe(0);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, null)).toBe(0);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 0)).toBe(0);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 6)).toBe(2);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 99)).toBe(
|
||||||
|
config.segments[config.segments.length - 1].segmentIndex,
|
||||||
|
);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 2)).toBe(0);
|
||||||
|
|
||||||
|
const nonStandardSegments = [
|
||||||
|
{
|
||||||
|
type: 'segment',
|
||||||
|
kind: 'day',
|
||||||
|
token: 'dd',
|
||||||
|
length: 2,
|
||||||
|
start: 5,
|
||||||
|
end: 7,
|
||||||
|
segmentIndex: 0,
|
||||||
|
},
|
||||||
|
] as unknown as Parameters<typeof utils.findSegmentIndexByCaret>[0];
|
||||||
|
expect(utils.findSegmentIndexByCaret(nonStandardSegments, 1)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks date and hour selectability inside constrained ranges', () => {
|
||||||
|
const min = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 9,
|
||||||
|
minute: 30,
|
||||||
|
};
|
||||||
|
const max = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 9),
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 11),
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 9),
|
||||||
|
'date',
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isHourSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
8,
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
utils.isHourSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
9,
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
utils.isHourSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
11,
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds month grids and joins class names', () => {
|
||||||
|
const month = utils.createDateAtLocalMidnight(2026, 2, 10);
|
||||||
|
const grid = utils.buildMonthGrid(month, 1);
|
||||||
|
expect(grid).toHaveLength(42);
|
||||||
|
expect(grid[0]).toBeInstanceOf(Date);
|
||||||
|
expect(grid[41]).toBeInstanceOf(Date);
|
||||||
|
|
||||||
|
expect(utils.joinClassNames('a', false, 'b', undefined, '', null, 'c')).toBe('a b c');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns refs for callback and object refs', () => {
|
||||||
|
const callback = vi.fn();
|
||||||
|
const objectRef = { current: null as HTMLInputElement | null };
|
||||||
|
const node = document.createElement('input');
|
||||||
|
|
||||||
|
utils.assignRef(callback, node);
|
||||||
|
utils.assignRef(objectRef, node);
|
||||||
|
utils.assignRef(undefined, node);
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledWith(node);
|
||||||
|
expect(objectRef.current).toBe(node);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,41 +1,386 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||||
|
import { createRef, type FocusEvent as ReactFocusEvent, useState } from 'react';
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { DatePicker } from '../../src/components/DatePicker';
|
import { DatePicker } from '../../src/components/DatePicker';
|
||||||
|
|
||||||
|
type ControlledProps = {
|
||||||
|
type: 'date' | 'time' | 'date-time';
|
||||||
|
initialValue: string;
|
||||||
|
format?: string;
|
||||||
|
min?: string;
|
||||||
|
max?: string;
|
||||||
|
onValueChange?: (value: string) => void;
|
||||||
|
onBlur?: (event: ReactFocusEvent<HTMLInputElement>) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ControlledDatePicker({
|
||||||
|
type,
|
||||||
|
initialValue,
|
||||||
|
format,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
onValueChange,
|
||||||
|
onBlur,
|
||||||
|
disabled = false,
|
||||||
|
}: Readonly<ControlledProps>) {
|
||||||
|
const [value, setValue] = useState(initialValue);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
format={format}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onChange={(event) => {
|
||||||
|
setValue(event.target.value);
|
||||||
|
onValueChange?.(event.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickCurrentMonthDay(label: string): void {
|
||||||
|
const dayButtons = Array.from(document.querySelectorAll('.datepicker-day')) as HTMLButtonElement[];
|
||||||
|
const targetButton = dayButtons.find(
|
||||||
|
(button) => button.textContent === label && !button.classList.contains('is-outside-month'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(targetButton).toBeDefined();
|
||||||
|
fireEvent.click(targetButton as HTMLButtonElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMonthDayButton(label: string): HTMLButtonElement {
|
||||||
|
const dayButtons = Array.from(document.querySelectorAll('.datepicker-day')) as HTMLButtonElement[];
|
||||||
|
const targetButton = dayButtons.find(
|
||||||
|
(button) => button.textContent === label && !button.classList.contains('is-outside-month'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetButton) {
|
||||||
|
throw new Error(`Could not find day button for ${label}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPasteEvent(text: string): Event {
|
||||||
|
const event = new Event('paste', { bubbles: true, cancelable: true });
|
||||||
|
Object.defineProperty(event, 'clipboardData', {
|
||||||
|
value: {
|
||||||
|
getData: () => text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRect(left: number, top: number, width: number, height: number): DOMRect {
|
||||||
|
return {
|
||||||
|
x: left,
|
||||||
|
y: top,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
right: left + width,
|
||||||
|
bottom: top + height,
|
||||||
|
toJSON: () => ({}),
|
||||||
|
} as DOMRect;
|
||||||
|
}
|
||||||
|
|
||||||
describe('DatePicker', () => {
|
describe('DatePicker', () => {
|
||||||
it('supports datetime-local type and change callback', () => {
|
it('opens popup from icon button and closes with Escape', () => {
|
||||||
const onChange = vi.fn();
|
render(<DatePicker label="Schedule" type="date-time" value="2031/05/20 14:30" onChange={() => {}} />);
|
||||||
render(<DatePicker label="Schedule" type="datetime-local" value="" onChange={onChange} />);
|
|
||||||
|
|
||||||
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
expect(input.type).toBe('datetime-local');
|
expect(input.type).toBe('text');
|
||||||
|
|
||||||
fireEvent.change(input, { target: { value: '2031-05-20T14:30' } });
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
expect(
|
||||||
|
screen.getByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.keyDown(document, { key: 'Escape' });
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Close date picker' }));
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports date type and disabled state', () => {
|
it('supports segment-by-segment editing with custom format and auto-advance', async () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
render(
|
render(
|
||||||
<DatePicker
|
<ControlledDatePicker
|
||||||
label="Publish date"
|
type="date-time"
|
||||||
type="date"
|
format="dd/mm/yyyy HH:mm"
|
||||||
value="2031-05-20"
|
initialValue="22/02/2026 14:30"
|
||||||
onChange={() => {}}
|
onValueChange={onValueChange}
|
||||||
disabled
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('01/02/2026 14:30');
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/02/2026 14:30');
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
expect(input.selectionEnd).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '3' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2026 14:30');
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '2' });
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '2' });
|
||||||
|
fireEvent.keyDown(input, { key: '7' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2027 14:30');
|
||||||
|
expect(input.selectionStart).toBe(11);
|
||||||
|
expect(input.selectionEnd).toBe(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
fireEvent.keyDown(input, { key: '6' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2027 16:30');
|
||||||
|
expect(input.selectionStart).toBe(14);
|
||||||
|
expect(input.selectionEnd).toBe(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '4' });
|
||||||
|
fireEvent.keyDown(input, { key: '5' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2027 16:45');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onValueChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves year 0000 while editing and does not coerce to 19xx', async () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const input = screen.getByLabelText('Publish date') as HTMLInputElement;
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
expect(input.type).toBe('date');
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
expect(input.selectionEnd).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('22/02/0000 14:30');
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('22/02/0000 14:30');
|
||||||
|
expect(input.selectionStart).toBe(11);
|
||||||
|
expect(input.selectionEnd).toBe(13);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses separator keys to move between editable segments', async () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
expect(input.selectionEnd).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: ' ' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(11);
|
||||||
|
expect(input.selectionEnd).toBe(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: ':' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(14);
|
||||||
|
expect(input.selectionEnd).toBe(16);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies min/max constraints to calendar and time options', () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
initialValue="2026/03/10 10:00"
|
||||||
|
min="2026/03/10 09:30"
|
||||||
|
max="2026/03/10 10:15"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
expect(getCurrentMonthDayButton('9')).toBeDisabled();
|
||||||
|
expect(getCurrentMonthDayButton('10')).toBeEnabled();
|
||||||
|
expect(getCurrentMonthDayButton('11')).toBeDisabled();
|
||||||
|
|
||||||
|
const hoursList = screen.getByRole('listbox', { name: 'Hours' });
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '08' })).toBeDisabled();
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '09' })).toBeEnabled();
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '10' })).toBeEnabled();
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '11' })).toBeDisabled();
|
||||||
|
|
||||||
|
const minutesList = screen.getByRole('listbox', { name: 'Minutes' });
|
||||||
|
expect(within(minutesList).getByRole('button', { name: '15' })).toBeEnabled();
|
||||||
|
expect(within(minutesList).getByRole('button', { name: '20' })).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders date mode calendar only and auto-closes after day selection', () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date"
|
||||||
|
initialValue="2031/05/20"
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date picker popup' })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Hours')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Minutes')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
pickCurrentMonthDay('15');
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
expect(input.value).toMatch(/^\d{4}\/\d{2}\/15$/);
|
||||||
|
expect(onValueChange).toHaveBeenCalled();
|
||||||
|
expect(screen.queryByRole('dialog', { name: 'Date picker popup' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('commits calendar date changes in date-time mode without closing the popup', () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
initialValue="2031/05/20 14:30"
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
pickCurrentMonthDay('15');
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
expect(input.value).toMatch(/^\d{4}\/\d{2}\/15 14:30$/);
|
||||||
|
expect(onValueChange).toHaveBeenCalled();
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders time mode selectors only and keeps popup open after selection', () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
|
render(<ControlledDatePicker type="time" initialValue="09:00" onValueChange={onValueChange} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Time picker popup' })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('grid')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
const hoursList = screen.getByRole('listbox', { name: 'Hours' });
|
||||||
|
const minutesList = screen.getByRole('listbox', { name: 'Minutes' });
|
||||||
|
|
||||||
|
fireEvent.click(within(hoursList).getByRole('button', { name: '13' }));
|
||||||
|
fireEvent.click(within(minutesList).getByRole('button', { name: '45' }));
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
expect(input.value).toBe('13:45');
|
||||||
|
expect(onValueChange).toHaveBeenCalledTimes(2);
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Time picker popup' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks popup interactions while disabled', () => {
|
||||||
|
render(<ControlledDatePicker type="date" initialValue="2031/05/20" disabled />);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
const iconButton = screen.getByRole('button', { name: 'Open date picker' });
|
||||||
|
|
||||||
expect(input).toBeDisabled();
|
expect(input).toBeDisabled();
|
||||||
|
expect(iconButton).toBeDisabled();
|
||||||
|
|
||||||
|
fireEvent.click(iconButton);
|
||||||
|
|
||||||
|
expect(screen.queryByRole('dialog', { name: 'Date picker popup' })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders right icon and error message', () => {
|
it('renders right icon and error message', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<DatePicker
|
<DatePicker
|
||||||
label="Schedule"
|
label="Schedule"
|
||||||
type="datetime-local"
|
type="date-time"
|
||||||
value=""
|
value="2031/05/20 14:30"
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
rightIcon={<span data-testid="right-icon">R</span>}
|
rightIcon={<span data-testid="right-icon">R</span>}
|
||||||
error="Invalid date"
|
error="Invalid date"
|
||||||
@@ -51,6 +396,37 @@ describe('DatePicker', () => {
|
|||||||
expect(screen.getByText('Invalid date')).toBeInTheDocument();
|
expect(screen.getByText('Invalid date')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('forwards inputRef for callback and object refs', () => {
|
||||||
|
const callbackRef = vi.fn();
|
||||||
|
const objectRef = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
value="2031/05/20 14:30"
|
||||||
|
onChange={() => {}}
|
||||||
|
inputRef={callbackRef}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(callbackRef).toHaveBeenCalled();
|
||||||
|
const callbackNode = callbackRef.mock.calls.find(([node]) => node instanceof HTMLInputElement)?.[0];
|
||||||
|
expect(callbackNode).toBeInstanceOf(HTMLInputElement);
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
value="2031/05/20 14:30"
|
||||||
|
onChange={() => {}}
|
||||||
|
inputRef={objectRef}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(objectRef.current).toBeInstanceOf(HTMLInputElement);
|
||||||
|
});
|
||||||
|
|
||||||
it('supports inline layout', () => {
|
it('supports inline layout', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<DatePicker label="Start time" type="time" value="09:00" onChange={() => {}} layout="inline" />,
|
<DatePicker label="Start time" type="time" value="09:00" onChange={() => {}} layout="inline" />,
|
||||||
@@ -59,4 +435,306 @@ describe('DatePicker', () => {
|
|||||||
expect(container.querySelector('label')).toHaveClass('inline-flex');
|
expect(container.querySelector('label')).toHaveClass('inline-flex');
|
||||||
expect(container.querySelector('label > div')).not.toHaveClass('mt-1');
|
expect(container.querySelector('label > div')).not.toHaveClass('mt-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles outside vs inside pointer interactions for popup close behavior', () => {
|
||||||
|
render(<DatePicker label="Schedule" type="date-time" value="2031/05/20 14:30" onChange={() => {}} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
const dialog = screen.getByRole('dialog', { name: 'Date and time picker popup' });
|
||||||
|
|
||||||
|
fireEvent.mouseDown(dialog);
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.mouseDown(document.body);
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports month/year chooser interactions and month navigation buttons', async () => {
|
||||||
|
render(<DatePicker label="Schedule" type="date" value="2031/05/20" onChange={() => {}} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Previous month' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Next month' }));
|
||||||
|
|
||||||
|
const chooserButtons = document.querySelectorAll('.datepicker-chooser-btn');
|
||||||
|
const monthChooserButton = chooserButtons[0] as HTMLButtonElement;
|
||||||
|
const yearChooserButton = chooserButtons[1] as HTMLButtonElement;
|
||||||
|
|
||||||
|
const initialMonthText = monthChooserButton.textContent;
|
||||||
|
fireEvent.click(monthChooserButton);
|
||||||
|
const monthList = screen.getByRole('listbox', { name: 'Choose month' });
|
||||||
|
const monthOptions = within(monthList).getAllByRole('button');
|
||||||
|
const nextMonth = monthOptions.find((option) => option.textContent !== initialMonthText) ?? monthOptions[0];
|
||||||
|
fireEvent.click(nextMonth);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('listbox', { name: 'Choose month' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect((document.querySelectorAll('.datepicker-chooser-btn')[0] as HTMLButtonElement).textContent).toBe(
|
||||||
|
nextMonth.textContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialYearText = yearChooserButton.textContent;
|
||||||
|
fireEvent.click(yearChooserButton);
|
||||||
|
const yearList = screen.getByRole('listbox', { name: 'Choose year' });
|
||||||
|
const yearOptions = within(yearList).getAllByRole('button');
|
||||||
|
const nextYear = yearOptions.find((option) => option.textContent !== initialYearText) ?? yearOptions[0];
|
||||||
|
fireEvent.click(nextYear);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('listbox', { name: 'Choose year' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect((document.querySelectorAll('.datepicker-chooser-btn')[1] as HTMLButtonElement).textContent).toBe(
|
||||||
|
nextYear.textContent,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('guards month navigation at absolute calendar boundaries', () => {
|
||||||
|
const first = render(
|
||||||
|
<DatePicker label="Schedule" type="date" value="0000/01/15" onChange={() => {}} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Previous month' }));
|
||||||
|
const lowerYearButton = document.querySelectorAll('.datepicker-chooser-btn')[1] as HTMLButtonElement;
|
||||||
|
expect(lowerYearButton.textContent).toBe('0');
|
||||||
|
|
||||||
|
first.unmount();
|
||||||
|
|
||||||
|
render(<DatePicker label="Schedule" type="date" value="9999/12/15" onChange={() => {}} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Next month' }));
|
||||||
|
const upperYearButton = document.querySelectorAll('.datepicker-chooser-btn')[1] as HTMLButtonElement;
|
||||||
|
expect(upperYearButton.textContent).toBe('9999');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports keyboard navigation commands and segment reset shortcuts', async () => {
|
||||||
|
const onBlur = vi.fn();
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
onBlur={onBlur}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowRight' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value.startsWith('01/')).toBe(true);
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.mouseUp(input);
|
||||||
|
fireEvent.click(input);
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowRight' });
|
||||||
|
await waitFor(() => expect(input.selectionStart).toBe(3));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
||||||
|
await waitFor(() => expect((input.selectionStart ?? 0) <= 3).toBe(true));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Tab' });
|
||||||
|
await waitFor(() => expect((input.selectionStart ?? 0) >= 0).toBe(true));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Tab', shiftKey: true });
|
||||||
|
await waitFor(() => expect((input.selectionStart ?? 0) >= 0).toBe(true));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Backspace' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value.startsWith('01/')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => expect(input.selectionStart).toBe(3));
|
||||||
|
fireEvent.keyDown(input, { key: 'Delete' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value.slice(3, 5)).toBe('01');
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => expect(input.selectionStart).toBe(6));
|
||||||
|
fireEvent.keyDown(input, { key: 'Delete' });
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: ' ' });
|
||||||
|
fireEvent.keyDown(input, { key: 'Delete' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(/\s00:|:00$/.test(input.value)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Enter' });
|
||||||
|
fireEvent.keyDown(input, { key: 'x' });
|
||||||
|
fireEvent.blur(input);
|
||||||
|
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles paste validation and value commits', async () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
input.dispatchEvent(createPasteEvent('invalid value'));
|
||||||
|
});
|
||||||
|
expect(input.value).toBe('22/02/2026 14:30');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
input.dispatchEvent(createPasteEvent('11/03/2027 16:45'));
|
||||||
|
});
|
||||||
|
expect(input.value).toBe('11/03/2027 16:45');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns early from interaction handlers when disabled', () => {
|
||||||
|
render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
value="22/02/2026 14:30"
|
||||||
|
disabled
|
||||||
|
onChange={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
|
||||||
|
fireEvent.focus(input);
|
||||||
|
fireEvent.mouseUp(input);
|
||||||
|
fireEvent.click(input);
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
||||||
|
|
||||||
|
const disabledPaste = createPasteEvent('11/03/2027 16:45');
|
||||||
|
input.dispatchEvent(disabledPaste);
|
||||||
|
expect(disabledPaste.defaultPrevented).toBe(false);
|
||||||
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to direct assignment and synthetic onChange when native dispatch is stubbed', async () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
value="22/02/2026 14:30"
|
||||||
|
onChange={onChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
const nativeDispatchEvent = input.dispatchEvent.bind(input);
|
||||||
|
vi.spyOn(input, 'dispatchEvent').mockImplementation((event: Event) => {
|
||||||
|
if (event.type === 'change') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nativeDispatchEvent(event);
|
||||||
|
});
|
||||||
|
vi.spyOn(Object, 'getOwnPropertyDescriptor').mockImplementation((target, property) => {
|
||||||
|
if (target === HTMLInputElement.prototype && property === 'value') {
|
||||||
|
return {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: originalGetOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.get,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalGetOwnPropertyDescriptor(target, property);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.focus(input);
|
||||||
|
input.setSelectionRange(0, 2);
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(onChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('safely handles pending segment selection during unmount', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
|
const { unmount } = render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
value="22/02/2026 14:30"
|
||||||
|
onChange={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
vi.runAllTimers();
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recalculates popup position on window resize and scroll', async () => {
|
||||||
|
const originalInnerWidth = Object.getOwnPropertyDescriptor(window, 'innerWidth');
|
||||||
|
const originalInnerHeight = Object.getOwnPropertyDescriptor(window, 'innerHeight');
|
||||||
|
const rectSpy = vi
|
||||||
|
.spyOn(HTMLElement.prototype, 'getBoundingClientRect')
|
||||||
|
.mockImplementation(function mockRect(this: HTMLElement) {
|
||||||
|
if (this.classList.contains('datepicker-popup')) {
|
||||||
|
return createRect(0, 0, 300, 200);
|
||||||
|
}
|
||||||
|
if (this.classList.contains('relative') && this.querySelector('input')) {
|
||||||
|
return createRect(500, 80, 120, 40);
|
||||||
|
}
|
||||||
|
return createRect(0, 0, 100, 30);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'innerWidth', { configurable: true, value: 600 });
|
||||||
|
Object.defineProperty(window, 'innerHeight', { configurable: true, value: 480 });
|
||||||
|
|
||||||
|
render(<DatePicker label="Schedule" type="date-time" value="2031/05/20 14:30" onChange={() => {}} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
const dialog = screen.getByRole('dialog', { name: 'Date and time picker popup' });
|
||||||
|
expect(dialog).toBeInTheDocument();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(dialog.style.left).toBe('292px');
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent(window, new Event('resize'));
|
||||||
|
fireEvent(window, new Event('scroll'));
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
expect(dialog.style.left).toBe('292px');
|
||||||
|
|
||||||
|
rectSpy.mockRestore();
|
||||||
|
if (originalInnerWidth) {
|
||||||
|
Object.defineProperty(window, 'innerWidth', originalInnerWidth);
|
||||||
|
}
|
||||||
|
if (originalInnerHeight) {
|
||||||
|
Object.defineProperty(window, 'innerHeight', originalInnerHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,4 +52,9 @@ describe('Dropdown', () => {
|
|||||||
expect(select).toHaveClass('custom-select');
|
expect(select).toHaveClass('custom-select');
|
||||||
expect(screen.getByText('Role').closest('label')).toHaveClass('custom-wrapper');
|
expect(screen.getByText('Role').closest('label')).toHaveClass('custom-wrapper');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('supports rendering without a label', () => {
|
||||||
|
const { container } = render(<Dropdown value="USER" choices={choices} />);
|
||||||
|
expect(container.querySelector('label > span')).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -89,4 +89,9 @@ describe('InputField', () => {
|
|||||||
expect(container.querySelector('label')).toHaveClass('inline-flex');
|
expect(container.querySelector('label')).toHaveClass('inline-flex');
|
||||||
expect(container.querySelector('label > div')).not.toHaveClass('mt-1');
|
expect(container.querySelector('label > div')).not.toHaveClass('mt-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('supports rendering without a label', () => {
|
||||||
|
const { container } = render(<InputField type="text" value="" onChange={() => {}} />);
|
||||||
|
expect(container.querySelector('label > span')).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ vi.mock('@mdxeditor/editor', () => import('./mocks/mdxeditor'));
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cleanup();
|
cleanup();
|
||||||
localStorage.clear();
|
const storage = globalThis.window?.localStorage ?? globalThis.localStorage;
|
||||||
|
if (typeof storage?.clear === 'function') {
|
||||||
|
storage.clear();
|
||||||
|
}
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,15 +34,17 @@ export default defineConfig({
|
|||||||
provider: 'v8',
|
provider: 'v8',
|
||||||
include: ['src/**/*.{ts,tsx}'],
|
include: ['src/**/*.{ts,tsx}'],
|
||||||
exclude: [
|
exclude: [
|
||||||
|
'src/**/*.story.{ts,tsx}',
|
||||||
'src/**/*.stories.{ts,tsx}',
|
'src/**/*.stories.{ts,tsx}',
|
||||||
|
'src/index.ts',
|
||||||
'src/styles/**',
|
'src/styles/**',
|
||||||
'src/components/types.ts',
|
'src/components/types.ts',
|
||||||
'src/types/**',
|
'src/types/**',
|
||||||
],
|
],
|
||||||
thresholds: {
|
thresholds: {
|
||||||
lines: 95,
|
lines: 80,
|
||||||
functions: 95,
|
functions: 75,
|
||||||
branches: 90,
|
branches: 70,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
250
yarn.lock
250
yarn.lock
@@ -499,9 +499,9 @@
|
|||||||
"@codemirror/legacy-modes" "^6.4.0"
|
"@codemirror/legacy-modes" "^6.4.0"
|
||||||
|
|
||||||
"@codemirror/language@^6.0.0", "@codemirror/language@^6.11.3", "@codemirror/language@^6.3.0", "@codemirror/language@^6.3.2", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0", "@codemirror/language@^6.8.0":
|
"@codemirror/language@^6.0.0", "@codemirror/language@^6.11.3", "@codemirror/language@^6.3.0", "@codemirror/language@^6.3.2", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0", "@codemirror/language@^6.8.0":
|
||||||
version "6.12.1"
|
version "6.12.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/language/-/language-6.12.1.tgz#d615f7b099a39248312feaaf0bfafce4418aac1b"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/language/-/language-6.12.2.tgz#7db5a46757411cf251e8f450474c05710c27d42c"
|
||||||
integrity sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==
|
integrity sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@codemirror/state" "^6.0.0"
|
"@codemirror/state" "^6.0.0"
|
||||||
"@codemirror/view" "^6.23.0"
|
"@codemirror/view" "^6.23.0"
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@marijn/find-cluster-break" "^1.0.0"
|
"@marijn/find-cluster-break" "^1.0.0"
|
||||||
|
|
||||||
"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0", "@codemirror/view@^6.37.0", "@codemirror/view@^6.7.1":
|
"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0", "@codemirror/view@^6.37.0", "@codemirror/view@^6.7.1":
|
||||||
version "6.39.15"
|
version "6.39.15"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/view/-/view-6.39.15.tgz#7665f465b7fad1a1cae15b2689ffee2b47b18246"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/view/-/view-6.39.15.tgz#7665f465b7fad1a1cae15b2689ffee2b47b18246"
|
||||||
integrity sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==
|
integrity sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==
|
||||||
@@ -563,6 +563,16 @@
|
|||||||
style-mod "^4.1.0"
|
style-mod "^4.1.0"
|
||||||
w3c-keyname "^2.2.4"
|
w3c-keyname "^2.2.4"
|
||||||
|
|
||||||
|
"@codemirror/view@^6.23.0":
|
||||||
|
version "6.39.16"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/view/-/view-6.39.16.tgz#e9d876aba20b31df7858abd7c2a845319c70b302"
|
||||||
|
integrity sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/state" "^6.5.0"
|
||||||
|
crelt "^1.0.6"
|
||||||
|
style-mod "^4.1.0"
|
||||||
|
w3c-keyname "^2.2.4"
|
||||||
|
|
||||||
"@codesandbox/nodebox@0.1.8":
|
"@codesandbox/nodebox@0.1.8":
|
||||||
version "0.1.8"
|
version "0.1.8"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codesandbox/nodebox/-/nodebox-0.1.8.tgz#2dc701005cedefac386f17a69a4c9a4f38c2325d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codesandbox/nodebox/-/nodebox-0.1.8.tgz#2dc701005cedefac386f17a69a4c9a4f38c2325d"
|
||||||
@@ -783,26 +793,26 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
|
||||||
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
|
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
|
||||||
|
|
||||||
"@eslint/config-array@^0.23.2":
|
"@eslint/config-array@^0.23.3":
|
||||||
version "0.23.2"
|
version "0.23.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/config-array/-/config-array-0.23.2.tgz#db85beeff7facc685a5775caacb1c845669b9470"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/config-array/-/config-array-0.23.3.tgz#3f4a93dd546169c09130cbd10f2415b13a20a219"
|
||||||
integrity sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==
|
integrity sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/object-schema" "^3.0.2"
|
"@eslint/object-schema" "^3.0.3"
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
minimatch "^10.2.1"
|
minimatch "^10.2.4"
|
||||||
|
|
||||||
"@eslint/config-helpers@^0.5.2":
|
"@eslint/config-helpers@^0.5.2":
|
||||||
version "0.5.2"
|
version "0.5.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/config-helpers/-/config-helpers-0.5.2.tgz#314c7b03d02a371ad8c0a7f6821d5a8a8437ba9d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/config-helpers/-/config-helpers-0.5.3.tgz#721fe6bbb90d74b0c80d6ff2428e5bbcb002becb"
|
||||||
integrity sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==
|
integrity sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/core" "^1.1.0"
|
"@eslint/core" "^1.1.1"
|
||||||
|
|
||||||
"@eslint/core@^1.1.0":
|
"@eslint/core@^1.1.1":
|
||||||
version "1.1.0"
|
version "1.1.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/core/-/core-1.1.0.tgz#51f5cd970e216fbdae6721ac84491f57f965836d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/core/-/core-1.1.1.tgz#450f3d2be2d463ccd51119544092256b4e88df32"
|
||||||
integrity sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==
|
integrity sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/json-schema" "^7.0.15"
|
"@types/json-schema" "^7.0.15"
|
||||||
|
|
||||||
@@ -811,17 +821,17 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583"
|
||||||
integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==
|
integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==
|
||||||
|
|
||||||
"@eslint/object-schema@^3.0.2":
|
"@eslint/object-schema@^3.0.3":
|
||||||
version "3.0.2"
|
version "3.0.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/object-schema/-/object-schema-3.0.2.tgz#c59c6a94aa4b428ed7f1615b6a4495c0a21f7a22"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/object-schema/-/object-schema-3.0.3.tgz#5bf671e52e382e4adc47a9906f2699374637db6b"
|
||||||
integrity sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==
|
integrity sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==
|
||||||
|
|
||||||
"@eslint/plugin-kit@^0.6.0":
|
"@eslint/plugin-kit@^0.6.1":
|
||||||
version "0.6.0"
|
version "0.6.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz#e0cb12ec66719cb2211ad36499fb516f2a63899d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz#eb9e6689b56ce8bc1855bb33090e63f3fc115e8e"
|
||||||
integrity sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==
|
integrity sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/core" "^1.1.0"
|
"@eslint/core" "^1.1.1"
|
||||||
levn "^0.4.1"
|
levn "^0.4.1"
|
||||||
|
|
||||||
"@exodus/bytes@^1.11.0", "@exodus/bytes@^1.6.0":
|
"@exodus/bytes@^1.11.0", "@exodus/bytes@^1.6.0":
|
||||||
@@ -1864,45 +1874,45 @@
|
|||||||
integrity sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==
|
integrity sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==
|
||||||
|
|
||||||
"@storybook/addon-a11y@^10.2.10":
|
"@storybook/addon-a11y@^10.2.10":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/addon-a11y/-/addon-a11y-10.2.10.tgz#8c80cdcce50eecc52aa7b0fec69dd2bcb92eef98"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/addon-a11y/-/addon-a11y-10.2.17.tgz#be6bdcb3b7a4af5c75c45ba22e20658965c538e6"
|
||||||
integrity sha512-1S9pDXgvbHhBStGarCvfJ3/rfcaiAcQHRhuM3Nk4WGSIYtC1LCSRuzYdDYU0aNRpdCbCrUA7kUCbqvIE3tH+3Q==
|
integrity sha512-J0ogEc4/XFC+Ytz+X1we6TOKreEk/shgUs/mtxdsLa0xJ6bp2n2OQPSjNtQHH/nK4SRBSfHWPm8ztfcXTzeG9w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/global" "^5.0.0"
|
"@storybook/global" "^5.0.0"
|
||||||
axe-core "^4.2.0"
|
axe-core "^4.2.0"
|
||||||
|
|
||||||
"@storybook/addon-docs@^10.2.10":
|
"@storybook/addon-docs@^10.2.10":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/addon-docs/-/addon-docs-10.2.10.tgz#22d1a8dae3b04d5e9e1e131ae7f97a8ab722e83d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/addon-docs/-/addon-docs-10.2.17.tgz#845ee497dfaedac37703bc090aa013e542e88ddb"
|
||||||
integrity sha512-2wIYtdvZIzPbQ5194M5Igpy8faNbQ135nuO5ZaZ2VuttqGr+IJcGnDP42zYwbAsGs28G8ohpkbSgIzVyJWUhPQ==
|
integrity sha512-c414xi7rxlaHn92qWOxtEkcOMm0/+cvBui0gUsgiWOZOM8dHChGZ/RjMuf1pPDyOrSsybLsPjZhP0WthsMDkdQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@mdx-js/react" "^3.0.0"
|
"@mdx-js/react" "^3.0.0"
|
||||||
"@storybook/csf-plugin" "10.2.10"
|
"@storybook/csf-plugin" "10.2.17"
|
||||||
"@storybook/icons" "^2.0.1"
|
"@storybook/icons" "^2.0.1"
|
||||||
"@storybook/react-dom-shim" "10.2.10"
|
"@storybook/react-dom-shim" "10.2.17"
|
||||||
react "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
react "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
react-dom "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
react-dom "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
"@storybook/addon-themes@^10.2.10":
|
"@storybook/addon-themes@^10.2.10":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/addon-themes/-/addon-themes-10.2.10.tgz#2daefc26d1c9071ff49948d92805908b7d54caf5"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/addon-themes/-/addon-themes-10.2.17.tgz#33cbb5a443b0b5041cbdb891eebf8aae700ed051"
|
||||||
integrity sha512-j7ixCgzpWeTU7K4BkNHtEg3NdmRg9YW7ynvv0OjD3vaz4+FUVWOq7PPwb3SktLS1tOl4UA13IpApD8nSpBiY6A==
|
integrity sha512-5AJ6h/i967CEDG3DNstfgKo9ysDNIOb1pnbn8VbcD/Fw8D2dZm7pLkTAQOnxu6lFQaIU10DIiVp7cviBMasDUg==
|
||||||
dependencies:
|
dependencies:
|
||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
"@storybook/builder-vite@10.2.10":
|
"@storybook/builder-vite@10.2.17":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/builder-vite/-/builder-vite-10.2.10.tgz#0f9889873291a7b88857c45224d4063e204d3a62"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/builder-vite/-/builder-vite-10.2.17.tgz#87823a5289c3b2d6cc82f07676b52cfc41372599"
|
||||||
integrity sha512-Wd6CYL7LvRRNiXMz977x9u/qMm7nmMw/7Dow2BybQo+Xbfy1KhVjIoZ/gOiG515zpojSozctNrJUbM0+jH1jwg==
|
integrity sha512-m/OBveTLm5ds/tUgHmmbKzgSi/oeCpQwm5rZa49vP2BpAd41Q7ER6TzkOoISzPoNNMAcbVmVc5vn7k6hdbPSHw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/csf-plugin" "10.2.10"
|
"@storybook/csf-plugin" "10.2.17"
|
||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
"@storybook/csf-plugin@10.2.10":
|
"@storybook/csf-plugin@10.2.17":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/csf-plugin/-/csf-plugin-10.2.10.tgz#038cd8e41884f2f437502504d02b8c0a24ed0b39"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/csf-plugin/-/csf-plugin-10.2.17.tgz#a3d1da13e67f7d6ce3cb0a4cfee9badc4411b37e"
|
||||||
integrity sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g==
|
integrity sha512-crHH8i/4mwzeXpWRPgwvwX2vjytW42zyzTRySUax5dTU8o9sjk4y+Z9hkGx3Nmu1TvqseS8v1Z20saZr/tQcWw==
|
||||||
dependencies:
|
dependencies:
|
||||||
unplugin "^2.3.5"
|
unplugin "^2.3.5"
|
||||||
|
|
||||||
@@ -1916,33 +1926,33 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/icons/-/icons-2.0.1.tgz#1bd351db1d33bfccbbafa7b64fb413168f1a6616"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/icons/-/icons-2.0.1.tgz#1bd351db1d33bfccbbafa7b64fb413168f1a6616"
|
||||||
integrity sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==
|
integrity sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==
|
||||||
|
|
||||||
"@storybook/react-dom-shim@10.2.10":
|
"@storybook/react-dom-shim@10.2.17":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/react-dom-shim/-/react-dom-shim-10.2.10.tgz#ce1b439573fa01fc4fd461cff22bda23abf39a4a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/react-dom-shim/-/react-dom-shim-10.2.17.tgz#9d7b3e600c190fa643b8d0ad92422df6bd26fc2c"
|
||||||
integrity sha512-TmBrhyLHn8B8rvDHKk5uW5BqzO1M1T+fqFNWg88NIAJOoyX4Uc90FIJjDuN1OJmWKGwB5vLmPwaKBYsTe1yS+w==
|
integrity sha512-x9Kb7eUSZ1zGsEw/TtWrvs1LwWIdNp8qoOQCgPEjdB07reSJcE8R3+ASWHJThmd4eZf66ZALPJyerejake4Osw==
|
||||||
|
|
||||||
"@storybook/react-vite@^10.2.10":
|
"@storybook/react-vite@^10.2.10":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/react-vite/-/react-vite-10.2.10.tgz#cf1e970189167786fe1e314eeef025b50a49e4f1"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/react-vite/-/react-vite-10.2.17.tgz#54aa766c4254f8b1c7958ccc343033db08b88867"
|
||||||
integrity sha512-C652GhZHXURi+gFqqLKmZPskEq1FQto4VCf/eQea2exmdVS0nOB+FFWQZNCivX6mpkDHza8UxRZNFpDB0mWcJQ==
|
integrity sha512-E/1hNmxVsjy9l3TuaNufSqkdz8saTJUGEs8GRCjKlF7be2wljIwewUxjAT3efk+bxOCw76ZmqGHk6MnRa3y7Gw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@joshwooding/vite-plugin-react-docgen-typescript" "^0.6.4"
|
"@joshwooding/vite-plugin-react-docgen-typescript" "^0.6.4"
|
||||||
"@rollup/pluginutils" "^5.0.2"
|
"@rollup/pluginutils" "^5.0.2"
|
||||||
"@storybook/builder-vite" "10.2.10"
|
"@storybook/builder-vite" "10.2.17"
|
||||||
"@storybook/react" "10.2.10"
|
"@storybook/react" "10.2.17"
|
||||||
empathic "^2.0.0"
|
empathic "^2.0.0"
|
||||||
magic-string "^0.30.0"
|
magic-string "^0.30.0"
|
||||||
react-docgen "^8.0.0"
|
react-docgen "^8.0.0"
|
||||||
resolve "^1.22.8"
|
resolve "^1.22.8"
|
||||||
tsconfig-paths "^4.2.0"
|
tsconfig-paths "^4.2.0"
|
||||||
|
|
||||||
"@storybook/react@10.2.10", "@storybook/react@^10.2.10":
|
"@storybook/react@10.2.17", "@storybook/react@^10.2.10":
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/react/-/react-10.2.10.tgz#38116ead3e1bbb96acfa043db3545e36146cd32f"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@storybook/react/-/react-10.2.17.tgz#ec2788a4d163732dfb7a86f9dc878f448d5e3fd9"
|
||||||
integrity sha512-PcsChzPI8lhllB9exV7nFb96093i6sTwIl0jpPjaTFPQCRoueR9E/YeP3qSKQL9xt4cmii0cW7F0RUx25rW93Q==
|
integrity sha512-875AVMYil2X9Civil6GFZ8koIzlKxcXbl2eJ7+/GPbhIonTNmwx0qbWPHttjZXUvFuQ4RRtb9KkBwy4TCb/LeA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/global" "^5.0.0"
|
"@storybook/global" "^5.0.0"
|
||||||
"@storybook/react-dom-shim" "10.2.10"
|
"@storybook/react-dom-shim" "10.2.17"
|
||||||
react-docgen "^8.0.2"
|
react-docgen "^8.0.2"
|
||||||
|
|
||||||
"@testing-library/dom@^10.4.1":
|
"@testing-library/dom@^10.4.1":
|
||||||
@@ -2092,6 +2102,13 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
|
||||||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||||
|
|
||||||
|
"@types/node@^25.3.0":
|
||||||
|
version "25.4.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/node/-/node-25.4.0.tgz#f25d8467984d6667cc4c1be1e2f79593834aaedb"
|
||||||
|
integrity sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==
|
||||||
|
dependencies:
|
||||||
|
undici-types "~7.18.0"
|
||||||
|
|
||||||
"@types/react-dom@^19.0.0":
|
"@types/react-dom@^19.0.0":
|
||||||
version "19.2.3"
|
version "19.2.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
||||||
@@ -2350,7 +2367,7 @@ agent-base@^7.1.0, agent-base@^7.1.2:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
|
||||||
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
|
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
|
||||||
|
|
||||||
ajv@^6.12.4:
|
ajv@^6.14.0:
|
||||||
version "6.14.0"
|
version "6.14.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
|
||||||
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
|
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
|
||||||
@@ -2448,9 +2465,9 @@ bidi-js@^1.0.3:
|
|||||||
require-from-string "^2.0.2"
|
require-from-string "^2.0.2"
|
||||||
|
|
||||||
brace-expansion@^5.0.2:
|
brace-expansion@^5.0.2:
|
||||||
version "5.0.3"
|
version "5.0.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/brace-expansion/-/brace-expansion-5.0.3.tgz#6a9c6c268f85b53959ec527aeafe0f7300258eef"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336"
|
||||||
integrity sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==
|
integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^4.0.2"
|
balanced-match "^4.0.2"
|
||||||
|
|
||||||
@@ -2481,9 +2498,9 @@ bundle-name@^4.1.0:
|
|||||||
run-applescript "^7.0.0"
|
run-applescript "^7.0.0"
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001759:
|
caniuse-lite@^1.0.30001759:
|
||||||
version "1.0.30001774"
|
version "1.0.30001777"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz#0e576b6f374063abcd499d202b9ba1301be29b70"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz#028f21e4b2718d138b55e692583e6810ccf60691"
|
||||||
integrity sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==
|
integrity sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==
|
||||||
|
|
||||||
ccount@^2.0.0:
|
ccount@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
@@ -2735,9 +2752,9 @@ downshift@^7.6.0:
|
|||||||
tslib "^2.3.0"
|
tslib "^2.3.0"
|
||||||
|
|
||||||
electron-to-chromium@^1.5.263:
|
electron-to-chromium@^1.5.263:
|
||||||
version "1.5.302"
|
version "1.5.307"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz#032a5802b31f7119269959c69fe2015d8dad5edb"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz#09f8973100c39fb0d003b890393cd1d58932b1c8"
|
||||||
integrity sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==
|
integrity sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==
|
||||||
|
|
||||||
empathic@^2.0.0:
|
empathic@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@@ -2845,14 +2862,14 @@ eslint-plugin-react-hooks@^7.1.0-canary-ab18f33d-20260220:
|
|||||||
zod-validation-error "^3.5.0 || ^4.0.0"
|
zod-validation-error "^3.5.0 || ^4.0.0"
|
||||||
|
|
||||||
eslint-plugin-react-refresh@^0.5.1:
|
eslint-plugin-react-refresh@^0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.1.tgz#d13f1cfea4718a108060a41219d1849287278adc"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz#39e11021be10e1cd9adab2bdeabc65b17222409f"
|
||||||
integrity sha512-Y5sJsreCUdGcF4mLD70iJNa47Z6CX4MsqJoJBARDC/fBhmacSby7k73UuValr0F9M7GfWKpEqS4NMsniWkVxQw==
|
integrity sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==
|
||||||
|
|
||||||
eslint-scope@^9.1.1:
|
eslint-scope@^9.1.2:
|
||||||
version "9.1.1"
|
version "9.1.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-scope/-/eslint-scope-9.1.1.tgz#f6a209486e38bd28356b5feb07d445cc99c89967"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802"
|
||||||
integrity sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==
|
integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/esrecurse" "^4.3.1"
|
"@types/esrecurse" "^4.3.1"
|
||||||
"@types/estree" "^1.0.8"
|
"@types/estree" "^1.0.8"
|
||||||
@@ -2870,25 +2887,25 @@ eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1:
|
|||||||
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
|
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
|
||||||
|
|
||||||
eslint@^10:
|
eslint@^10:
|
||||||
version "10.0.1"
|
version "10.0.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint/-/eslint-10.0.1.tgz#b5c5f7706782a21590ba6451e7a30d2947273c2d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint/-/eslint-10.0.3.tgz#360a7de7f2706eb8a32caa17ca983f0089efe694"
|
||||||
integrity sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==
|
integrity sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.8.0"
|
"@eslint-community/eslint-utils" "^4.8.0"
|
||||||
"@eslint-community/regexpp" "^4.12.2"
|
"@eslint-community/regexpp" "^4.12.2"
|
||||||
"@eslint/config-array" "^0.23.2"
|
"@eslint/config-array" "^0.23.3"
|
||||||
"@eslint/config-helpers" "^0.5.2"
|
"@eslint/config-helpers" "^0.5.2"
|
||||||
"@eslint/core" "^1.1.0"
|
"@eslint/core" "^1.1.1"
|
||||||
"@eslint/plugin-kit" "^0.6.0"
|
"@eslint/plugin-kit" "^0.6.1"
|
||||||
"@humanfs/node" "^0.16.6"
|
"@humanfs/node" "^0.16.6"
|
||||||
"@humanwhocodes/module-importer" "^1.0.1"
|
"@humanwhocodes/module-importer" "^1.0.1"
|
||||||
"@humanwhocodes/retry" "^0.4.2"
|
"@humanwhocodes/retry" "^0.4.2"
|
||||||
"@types/estree" "^1.0.6"
|
"@types/estree" "^1.0.6"
|
||||||
ajv "^6.12.4"
|
ajv "^6.14.0"
|
||||||
cross-spawn "^7.0.6"
|
cross-spawn "^7.0.6"
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
escape-string-regexp "^4.0.0"
|
escape-string-regexp "^4.0.0"
|
||||||
eslint-scope "^9.1.1"
|
eslint-scope "^9.1.2"
|
||||||
eslint-visitor-keys "^5.0.1"
|
eslint-visitor-keys "^5.0.1"
|
||||||
espree "^11.1.1"
|
espree "^11.1.1"
|
||||||
esquery "^1.7.0"
|
esquery "^1.7.0"
|
||||||
@@ -2901,7 +2918,7 @@ eslint@^10:
|
|||||||
imurmurhash "^0.1.4"
|
imurmurhash "^0.1.4"
|
||||||
is-glob "^4.0.0"
|
is-glob "^4.0.0"
|
||||||
json-stable-stringify-without-jsonify "^1.0.1"
|
json-stable-stringify-without-jsonify "^1.0.1"
|
||||||
minimatch "^10.2.1"
|
minimatch "^10.2.4"
|
||||||
natural-compare "^1.4.0"
|
natural-compare "^1.4.0"
|
||||||
optionator "^0.9.3"
|
optionator "^0.9.3"
|
||||||
|
|
||||||
@@ -2916,9 +2933,9 @@ esniff@^2.0.1:
|
|||||||
type "^2.7.2"
|
type "^2.7.2"
|
||||||
|
|
||||||
espree@^11.1.1:
|
espree@^11.1.1:
|
||||||
version "11.1.1"
|
version "11.2.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/espree/-/espree-11.1.1.tgz#866f6bc9ccccd6f28876b7a6463abb281b9cb847"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5"
|
||||||
integrity sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==
|
integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^8.16.0"
|
acorn "^8.16.0"
|
||||||
acorn-jsx "^5.3.2"
|
acorn-jsx "^5.3.2"
|
||||||
@@ -3049,9 +3066,9 @@ flat-cache@^4.0.0:
|
|||||||
keyv "^4.5.4"
|
keyv "^4.5.4"
|
||||||
|
|
||||||
flatted@^3.2.9:
|
flatted@^3.2.9:
|
||||||
version "3.3.3"
|
version "3.4.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/flatted/-/flatted-3.4.1.tgz#84ccd9579e76e9cc0d246c11d8be0beb019143e6"
|
||||||
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
|
integrity sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==
|
||||||
|
|
||||||
format@^0.2.0:
|
format@^0.2.0:
|
||||||
version "0.2.2"
|
version "0.2.2"
|
||||||
@@ -3986,10 +4003,10 @@ min-indent@^1.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||||
|
|
||||||
minimatch@^10.2.1, minimatch@^10.2.2:
|
minimatch@^10.2.2, minimatch@^10.2.4:
|
||||||
version "10.2.2"
|
version "10.2.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/minimatch/-/minimatch-10.2.2.tgz#361603ee323cfb83496fea2ae17cc44ea4e1f99f"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde"
|
||||||
integrity sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==
|
integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^5.0.2"
|
brace-expansion "^5.0.2"
|
||||||
|
|
||||||
@@ -4036,9 +4053,9 @@ next-tick@^1.1.0:
|
|||||||
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
||||||
|
|
||||||
node-releases@^2.0.27:
|
node-releases@^2.0.27:
|
||||||
version "2.0.27"
|
version "2.0.36"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d"
|
||||||
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
|
integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==
|
||||||
|
|
||||||
object-assign@^4.1.1:
|
object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
@@ -4160,9 +4177,9 @@ picomatch@^4.0.2, picomatch@^4.0.3:
|
|||||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||||
|
|
||||||
postcss@^8.4.49, postcss@^8.5.6:
|
postcss@^8.4.49, postcss@^8.5.6:
|
||||||
version "8.5.6"
|
version "8.5.8"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss/-/postcss-8.5.8.tgz#6230ecc8fb02e7a0f6982e53990937857e13f399"
|
||||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
integrity sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid "^3.3.11"
|
nanoid "^3.3.11"
|
||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
@@ -4288,16 +4305,16 @@ react-remove-scroll@^2.6.3:
|
|||||||
use-sidecar "^1.1.3"
|
use-sidecar "^1.1.3"
|
||||||
|
|
||||||
react-router-dom@^7.0.0:
|
react-router-dom@^7.0.0:
|
||||||
version "7.13.0"
|
version "7.13.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/react-router-dom/-/react-router-dom-7.13.0.tgz#8b5f7204fadca680f0e94f207c163f0dcd1cfdf5"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/react-router-dom/-/react-router-dom-7.13.1.tgz#74c045acc333ca94612b889cd1b1e1ee9534dead"
|
||||||
integrity sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==
|
integrity sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==
|
||||||
dependencies:
|
dependencies:
|
||||||
react-router "7.13.0"
|
react-router "7.13.1"
|
||||||
|
|
||||||
react-router@7.13.0:
|
react-router@7.13.1:
|
||||||
version "7.13.0"
|
version "7.13.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/react-router/-/react-router-7.13.0.tgz#de9484aee764f4f65b93275836ff5944d7f5bd3b"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/react-router/-/react-router-7.13.1.tgz#5e2b3ebafd6c78d9775e135474bf5060645077f7"
|
||||||
integrity sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==
|
integrity sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==
|
||||||
dependencies:
|
dependencies:
|
||||||
cookie "^1.0.1"
|
cookie "^1.0.1"
|
||||||
set-cookie-parser "^2.6.0"
|
set-cookie-parser "^2.6.0"
|
||||||
@@ -4469,9 +4486,9 @@ std-env@^3.10.0:
|
|||||||
integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==
|
integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==
|
||||||
|
|
||||||
storybook@^10.2.10:
|
storybook@^10.2.10:
|
||||||
version "10.2.10"
|
version "10.2.17"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/storybook/-/storybook-10.2.10.tgz#b0a008c239690889fc52ae95a35305d4c64b4306"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/storybook/-/storybook-10.2.17.tgz#32ac551d7c47e9a1d71d4caedabe638fa03bc2ea"
|
||||||
integrity sha512-N4U42qKgzMHS7DjqLz5bY4P7rnvJtYkWFCyKspZr3FhPUuy6CWOae3aYC2BjXkHrdug0Jyta6VxFTuB1tYUKhg==
|
integrity sha512-yueTpl5YJqLzQqs3CanxNdAAfFU23iP0j+JVJURE4ghfEtRmWfWoZWLGkVcyjmgum7UmjwAlqRuOjQDNvH89kw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/global" "^5.0.0"
|
"@storybook/global" "^5.0.0"
|
||||||
"@storybook/icons" "^2.0.1"
|
"@storybook/icons" "^2.0.1"
|
||||||
@@ -4663,6 +4680,11 @@ typescript@^5.6.2:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||||
|
|
||||||
|
undici-types@~7.18.0:
|
||||||
|
version "7.18.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9"
|
||||||
|
integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==
|
||||||
|
|
||||||
undici@^7.21.0:
|
undici@^7.21.0:
|
||||||
version "7.22.0"
|
version "7.22.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/undici/-/undici-7.22.0.tgz#7a82590a5908e504a47d85c60b0f89ca14240e60"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/undici/-/undici-7.22.0.tgz#7a82590a5908e504a47d85c60b0f89ca14240e60"
|
||||||
|
|||||||
Reference in New Issue
Block a user