(bugfix): HTTP-safe toast IDs, drop lodash, single date-fns dependency on core.

This commit is contained in:
Maksym Sadovnychyy 2026-05-30 11:33:47 +02:00
parent 769e7ecbb9
commit 6567e01553
34 changed files with 123 additions and 76 deletions

View File

@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v0.3.1] - 2026-05-30
### Fixed
- Toast IDs no longer use `crypto.randomUUID()` (requires HTTPS/localhost). IDs are generated with a counter + timestamp via `createToastId()` so toasts work over HTTP in Docker and other non-secure contexts.
### Changed
- `@maks-it.com/webui-components`: shared helpers (`debounce`, `colSpanClass`, `GridColSpan`) are imported from the `functions` barrel instead of subpaths.
- `@maks-it.com/webui-core`: re-exports `date-fns` primitives (`parseISO`, `formatISO`, `format`, `getDaysInMonth`, `addMonths`, `subMonths`) for consumers.
- `@maks-it.com/webui-components` `DateTimePickerComponent` imports date helpers from `@maks-it.com/webui-core` instead of `date-fns` directly.
### Removed
- `lodash` and `@types/lodash` from `@maks-it.com/webui-components`; filter debouncing uses a local `debounce()` helper.
- Duplicate `date-fns` dependency from `@maks-it.com/webui-components` (`date-fns` remains on `@maks-it.com/webui-core` only).
## [v0.3.0] - 2026-05-25
### Added

View File

@ -1,21 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="137" height="20" role="img" aria-label="Line Coverage: 42.7%">
<title>Line Coverage: 42.7%</title>
<svg xmlns="http://www.w3.org/2000/svg" width="134.5" height="20" role="img" aria-label="Line Coverage: 42%">
<title>Line Coverage: 42%</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="137" height="20" rx="3" fill="#fff"/>
<rect width="134.5" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="94.5" height="20" fill="#555"/>
<rect x="94.5" width="42.5" height="20" fill="#a4a61d"/>
<rect width="137" height="20" fill="url(#s)"/>
<rect x="94.5" width="40" height="20" fill="#a4a61d"/>
<rect width="134.5" height="20" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
<text aria-hidden="true" x="47.25" y="15" fill="#010101" fill-opacity=".3">Line Coverage</text>
<text x="47.25" y="14" fill="#fff">Line Coverage</text>
<text aria-hidden="true" x="115.75" y="15" fill="#010101" fill-opacity=".3">42.7%</text>
<text x="115.75" y="14" fill="#fff">42.7%</text>
<text aria-hidden="true" x="114.5" y="15" fill="#010101" fill-opacity=".3">42%</text>
<text x="114.5" y="14" fill="#fff">42%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="20" role="img" aria-label="Method Coverage: 21.5%">
<title>Method Coverage: 21.5%</title>
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="20" role="img" aria-label="Method Coverage: 20.3%">
<title>Method Coverage: 20.3%</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
@ -15,7 +15,7 @@
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
<text aria-hidden="true" x="53.75" y="15" fill="#010101" fill-opacity=".3">Method Coverage</text>
<text x="53.75" y="14" fill="#fff">Method Coverage</text>
<text aria-hidden="true" x="128.75" y="15" fill="#010101" fill-opacity=".3">21.5%</text>
<text x="128.75" y="14" fill="#fff">21.5%</text>
<text aria-hidden="true" x="128.75" y="15" fill="#010101" fill-opacity=".3">20.3%</text>
<text x="128.75" y="14" fill="#fff">20.3%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,4 +1,4 @@
{"total": {"lines":{"total":824,"covered":352,"skipped":0,"pct":42.71},"statements":{"total":889,"covered":377,"skipped":0,"pct":42.4},"functions":{"total":200,"covered":43,"skipped":0,"pct":21.5},"branches":{"total":404,"covered":194,"skipped":0,"pct":48.01},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
{"total": {"lines":{"total":837,"covered":352,"skipped":0,"pct":42.05},"statements":{"total":902,"covered":377,"skipped":0,"pct":41.79},"functions":{"total":212,"covered":43,"skipped":0,"pct":20.28},"branches":{"total":404,"covered":194,"skipped":0,"pct":48.01},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\contracts\\src\\PagedRequest.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\contracts\\src\\PatchOperation.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\contracts\\src\\PatchRequestModelBase.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
@ -9,7 +9,7 @@
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\contracts\\src\\identity\\login\\RefreshTokenRequest.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\contracts\\src\\identity\\logout\\LogoutRequest.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\index.ts": {"lines":{"total":5,"covered":0,"skipped":0,"pct":0},"functions":{"total":4,"covered":0,"skipped":0,"pct":0},"statements":{"total":9,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\index.ts": {"lines":{"total":41,"covered":0,"skipped":0,"pct":0},"functions":{"total":32,"covered":0,"skipped":0,"pct":0},"statements":{"total":41,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\index.ts": {"lines":{"total":47,"covered":0,"skipped":0,"pct":0},"functions":{"total":38,"covered":0,"skipped":0,"pct":0},"statements":{"total":47,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\acl\\index.ts": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":3,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\acl\\parseAclEntry.ts": {"lines":{"total":17,"covered":17,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":18,"covered":18,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\dataTable\\dataTableFilters.ts": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
@ -17,7 +17,7 @@
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\dataTable\\index.ts": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":4,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\date\\dateTimeToUtcIsoSchema.ts": {"lines":{"total":8,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":8,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\date\\formatISODateString.ts": {"lines":{"total":10,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":10,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\date\\index.ts": {"lines":{"total":5,"covered":0,"skipped":0,"pct":0},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":6,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\date\\index.ts": {"lines":{"total":12,"covered":0,"skipped":0,"pct":0},"functions":{"total":9,"covered":0,"skipped":0,"pct":0},"statements":{"total":13,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\date\\isValidDateString.ts": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":7,"covered":7,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\deep\\deepCopy.ts": {"lines":{"total":17,"covered":16,"skipped":0,"pct":94.11},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":17,"covered":16,"skipped":0,"pct":94.11},"branches":{"total":12,"covered":11,"skipped":0,"pct":91.66}}
,"E:\\Users\\maksym\\source\\repos\\MaksIT\\maksit-webui\\src\\packages\\core\\src\\functions\\deep\\deepDelta.ts": {"lines":{"total":183,"covered":119,"skipped":0,"pct":65.02},"functions":{"total":18,"covered":14,"skipped":0,"pct":77.77},"statements":{"total":199,"covered":130,"skipped":0,"pct":65.32},"branches":{"total":137,"covered":92,"skipped":0,"pct":67.15}}

34
src/package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "maksit-webui",
"version": "0.3.0",
"version": "0.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "maksit-webui",
"version": "0.3.0",
"version": "0.3.1",
"license": "MIT",
"workspaces": [
"packages/*"
@ -3520,13 +3520,6 @@
"pretty-format": "^30.0.0"
}
},
"node_modules/@types/lodash": {
"version": "4.17.24",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz",
"integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/mdx": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
@ -7234,12 +7227,6 @@
"node": ">=8"
}
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -10427,24 +10414,21 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"packages/components": {
"name": "@maks-it.com/webui-components",
"version": "0.3.0",
"version": "0.3.1",
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.0",
"@maks-it.com/webui-core": "^0.3.0",
"date-fns": "^4.3.0",
"lodash": "^4.18.1"
"@maks-it.com/webui-contracts": "^0.3.1",
"@maks-it.com/webui-core": "^0.3.1"
},
"devDependencies": {
"@tanstack/react-table": "^8.21.3",
"@types/lodash": "^4.17.24",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@types/react-virtualized": "^9.22.3",
@ -10469,7 +10453,7 @@
},
"packages/contracts": {
"name": "@maks-it.com/webui-contracts",
"version": "0.3.0",
"version": "0.3.1",
"devDependencies": {
"tsup": "^8.5.1",
"typescript": "^6.0.3",
@ -10481,9 +10465,9 @@
},
"packages/core": {
"name": "@maks-it.com/webui-core",
"version": "0.3.0",
"version": "0.3.1",
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.0",
"@maks-it.com/webui-contracts": "^0.3.1",
"date-fns": "^4.3.0"
},
"devDependencies": {

View File

@ -1,7 +1,7 @@
{
"name": "maksit-webui",
"private": true,
"version": "0.3.0",
"version": "0.3.1",
"description": "Shared React UI library for MaksIT Certs UI and Vault WebUI",
"workspaces": [
"packages/*"

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui-components",
"version": "0.3.0",
"version": "0.3.1",
"description": "Shared React components for MaksIT WebUI apps",
"type": "module",
"main": "./dist/index.cjs",
@ -33,10 +33,8 @@
"directory": "src/packages/components"
},
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.0",
"@maks-it.com/webui-core": "^0.3.0",
"date-fns": "^4.3.0",
"lodash": "^4.18.1"
"@maks-it.com/webui-contracts": "^0.3.1",
"@maks-it.com/webui-core": "^0.3.1"
},
"peerDependencies": {
"@tanstack/react-table": "^8.0.0",
@ -49,7 +47,6 @@
},
"devDependencies": {
"@tanstack/react-table": "^8.21.3",
"@types/lodash": "^4.17.24",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@types/react-virtualized": "^9.22.3",

View File

@ -3,8 +3,7 @@ import { AutoSizer, MultiGrid, GridCellProps } from 'react-virtualized'
import { mapPagedToDataTable, type DataTablePageView, type PagedResponse } from '@maks-it.com/webui-core'
import { Plus, Trash2, Edit } from 'lucide-react'
import debounce from 'lodash/debounce'
import { colSpanClass, type GridColSpan } from '../../functions/tailwind'
import { debounce, colSpanClass, type GridColSpan } from '../../functions'
interface FilterProps {

View File

@ -1,5 +1,5 @@
import { useMemo, useState } from 'react'
import debounce from 'lodash/debounce'
import { debounce } from '../../functions'
interface FilterPropsBase {
filterId?: string

View File

@ -1,5 +1,5 @@
import { FC, type ReactNode, useEffect, useRef, useState } from 'react'
import { colSpanClass, type GridColSpan } from '../functions/tailwind'
import { colSpanClass, type GridColSpan } from '../functions'
interface LazyLoadTableColumnProps {
key: string

View File

@ -1,5 +1,5 @@
import { FC, ReactNode, useCallback, useEffect } from 'react'
import { colSpanClass, type GridColSpan } from '../functions/tailwind'
import { colSpanClass, type GridColSpan } from '../functions'
export interface OffcanvasProps {
children: ReactNode

View File

@ -0,0 +1,7 @@
let toastIdSeq = 0
/** Works on plain HTTP; crypto.randomUUID() requires a secure context. */
export const createToastId = (): string => {
toastIdSeq += 1
return `toast-${toastIdSeq}-${Date.now()}`
}

View File

@ -1,4 +1,5 @@
import { useState, useEffect, FC } from 'react'
import { createToastId } from './createToastId'
// Define types for a toast
interface Toast {
@ -8,8 +9,6 @@ interface Toast {
duration?: number;
}
const createToastId = (): string => crypto.randomUUID()
const Toast: FC = () => {
const [toasts, setToasts] = useState<Toast[]>([])

View File

@ -1,6 +1,6 @@
import { type FC, type MouseEvent, type ReactNode } from 'react'
import { Link } from 'react-router-dom'
import { colSpanClass, type GridColSpan } from '../../functions/tailwind'
import { colSpanClass, type GridColSpan } from '../../functions'
interface CommonButtonProps {
colspan?: GridColSpan;

View File

@ -1,5 +1,5 @@
import { type ChangeEvent, type FC, useEffect, useRef } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
interface CheckBoxComponentProps {

View File

@ -1,9 +1,16 @@
import { ChangeEvent, FC, useState, useEffect, useRef } from 'react'
import { parseISO, formatISO, format, getDaysInMonth, addMonths, subMonths } from 'date-fns'
import {
parseISO,
formatISO,
format,
getDaysInMonth,
addMonths,
subMonths,
} from '@maks-it.com/webui-core'
import { CircleX } from 'lucide-react'
import { ButtonComponent } from './ButtonComponent'
import { TextBoxComponent } from './TextBoxComponent'
import { CircleX } from 'lucide-react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
import { getInputClasses } from './editorStyles'

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
interface DualListboxComponentProps {

View File

@ -1,5 +1,5 @@
import { FC, ReactNode } from 'react'
import { colSpanClass, type GridColSpan } from '../../functions/tailwind'
import { colSpanClass, type GridColSpan } from '../../functions'
interface FieldContainerProps {
colspan?: GridColSpan;

View File

@ -1,5 +1,5 @@
import React, { useRef, useState } from 'react'
import { colSpanClass, type GridColSpan } from '../../functions/tailwind'
import { colSpanClass, type GridColSpan } from '../../functions'
import { ButtonComponent } from './ButtonComponent'
import { Trash2 } from 'lucide-react'

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
interface ListboxComponentProps {

View File

@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
interface RadioOption {

View File

@ -2,7 +2,7 @@ import { useState, useCallback, ChangeEvent, useEffect, useRef } from 'react'
import type { PagedRequest } from '@maks-it.com/webui-contracts'
import type { SearchResponseBase } from '@maks-it.com/webui-contracts'
import { deepEqual } from '@maks-it.com/webui-core'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { SelectBoxComponent } from './SelectBoxComponent'
export type RemoteSelectSearchDataSource<TRequest extends PagedRequest> = (

View File

@ -1,6 +1,6 @@
import { Copy, Dices, Eye, EyeOff } from 'lucide-react'
import { ChangeEvent, FC, useRef, useState } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
import { getInputClasses } from './editorStyles'

View File

@ -1,7 +1,6 @@
import debounce from 'lodash/debounce'
import { CircleX } from 'lucide-react'
import { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import { debounce, type GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
import { getInputClasses } from './editorStyles'

View File

@ -1,6 +1,6 @@
import { Eye, EyeOff } from 'lucide-react'
import { ChangeEvent, FC, useEffect, useRef, useState } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
import { getInputClasses } from './editorStyles'

View File

@ -1,5 +1,5 @@
import React, { useState, ReactNode } from 'react'
import type { GridColSpan } from '../../functions/tailwind'
import type { GridColSpan } from '../../functions'
import { FieldContainer } from './FieldContainer'
interface TreeNode {

View File

@ -0,0 +1,17 @@
/** Trailing-edge debounce; lodash/debounce was only used for this pattern. */
export const debounce = <Args extends unknown[]>(
fn: (...args: Args) => void,
waitMs: number
): ((...args: Args) => void) => {
let timeoutId: ReturnType<typeof setTimeout> | undefined
return (...args: Args) => {
if (timeoutId !== undefined) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
timeoutId = undefined
fn(...args)
}, waitMs)
}
}

View File

@ -1,2 +1,3 @@
export { debounce } from './debounce'
export { colSpanClass } from './tailwind'
export type { GridColSpan } from './tailwind'

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui-contracts",
"version": "0.3.0",
"version": "0.3.1",
"description": "Shared TypeScript contracts for MaksIT WebUI apps",
"type": "module",
"main": "./dist/index.cjs",

View File

@ -1,6 +1,6 @@
{
"name": "@maks-it.com/webui-core",
"version": "0.3.0",
"version": "0.3.1",
"description": "Shared utilities and hooks for MaksIT WebUI apps",
"type": "module",
"main": "./dist/index.cjs",
@ -34,7 +34,7 @@
"directory": "src/packages/core"
},
"dependencies": {
"@maks-it.com/webui-contracts": "^0.3.0",
"@maks-it.com/webui-contracts": "^0.3.1",
"date-fns": "^4.3.0"
},
"peerDependencies": {

View File

@ -5,5 +5,14 @@ export { dateTimeToUtcIsoSchema } from './dateTimeToUtcIsoSchema'
export {
isValidISODateString,
formatISODateString
}
formatISODateString,
}
export {
parseISO,
formatISO,
format,
getDaysInMonth,
addMonths,
subMonths,
} from 'date-fns'

View File

@ -2,6 +2,12 @@ import {
isValidISODateString,
formatISODateString,
dateTimeToUtcIsoSchema,
parseISO,
formatISO,
format,
getDaysInMonth,
addMonths,
subMonths,
} from './date'
import {
@ -47,6 +53,12 @@ export {
isValidISODateString,
formatISODateString,
dateTimeToUtcIsoSchema,
parseISO,
formatISO,
format,
getDaysInMonth,
addMonths,
subMonths,
deepCopy,
deepDelta,
deltaHasOperations,

View File

@ -2,7 +2,7 @@ import { useState, type JSX, type ReactNode } from 'react'
import type { Meta, StoryObj } from '@storybook/react-vite'
import { fn } from 'storybook/test'
import { ButtonComponent } from '@webui/components/components/editors/ButtonComponent'
import type { GridColSpan } from '@webui/components/functions/tailwind'
import type { GridColSpan } from '@webui/components/functions'
import { Offcanvas } from '@webui/components/components/Offcanvas'
import { FormContainer } from '@webui/components/components/FormLayout/FormContainer'
import { FormContent } from '@webui/components/components/FormLayout/FormContent'

View File

@ -19,7 +19,6 @@ export default defineConfig({
'react-router-dom',
'storybook/test',
'lucide-react',
'lodash/debounce',
],
},
resolve: {