@djangocfg/ui-core 2.1.548 → 2.1.550
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.550",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
|
-
"@djangocfg/i18n": "^2.1.
|
|
133
|
+
"@djangocfg/i18n": "^2.1.550",
|
|
134
134
|
"consola": "^3.4.2",
|
|
135
135
|
"lucide-react": "^0.545.0",
|
|
136
136
|
"moment": "^2.30.1",
|
|
@@ -206,9 +206,9 @@
|
|
|
206
206
|
"vaul": "1.1.2"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@djangocfg/eslint-config": "^2.1.
|
|
210
|
-
"@djangocfg/i18n": "^2.1.
|
|
211
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
209
|
+
"@djangocfg/eslint-config": "^2.1.550",
|
|
210
|
+
"@djangocfg/i18n": "^2.1.550",
|
|
211
|
+
"@djangocfg/typescript-config": "^2.1.550",
|
|
212
212
|
"@storybook/react-vite": "^10.5.0",
|
|
213
213
|
"@types/node": "^24.13.3",
|
|
214
214
|
"@types/react": "19.2.15",
|
|
@@ -84,20 +84,25 @@ const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
|
|
|
84
84
|
)
|
|
85
85
|
}, [searchQuery])
|
|
86
86
|
|
|
87
|
-
//
|
|
87
|
+
// Mirror the controlled value, empty included. Skipping the empty case
|
|
88
|
+
// leaves the last number on screen after a parent resets the form, so the
|
|
89
|
+
// field shows digits the form no longer holds.
|
|
88
90
|
React.useEffect(() => {
|
|
89
|
-
if (value) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
if (!value) {
|
|
92
|
+
setInputValue('')
|
|
93
|
+
setSelectedCountry(defaultCountry)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const phoneNumber = parsePhoneNumberFromString(value)
|
|
98
|
+
if (phoneNumber) {
|
|
99
|
+
setSelectedCountry(phoneNumber.country || defaultCountry)
|
|
100
|
+
setInputValue(phoneNumber.nationalNumber)
|
|
101
|
+
} else {
|
|
99
102
|
setInputValue(value)
|
|
100
103
|
}
|
|
104
|
+
} catch {
|
|
105
|
+
setInputValue(value)
|
|
101
106
|
}
|
|
102
107
|
}, [value, defaultCountry])
|
|
103
108
|
|
|
@@ -242,7 +242,9 @@ export const MultiSelectProAsync = React.forwardRef<MultiSelectProAsyncRef, Mult
|
|
|
242
242
|
badgeAnimation: animationConfig?.badgeAnimation || (animation > 0 ? "bounce" : "none"),
|
|
243
243
|
popoverAnimation: animationConfig?.popoverAnimation || "scale",
|
|
244
244
|
optionHoverAnimation: animationConfig?.optionHoverAnimation || "highlight",
|
|
245
|
-
|
|
245
|
+
// `??`: a caller asking for duration 0 means "no animation",
|
|
246
|
+
// and `||` swallowed it into the 0.3 default.
|
|
247
|
+
duration: animationConfig?.duration ?? (animation > 0 ? animation : 0.3),
|
|
246
248
|
delay: animationConfig?.delay || 0,
|
|
247
249
|
}),
|
|
248
250
|
[animation, animationConfig]
|
|
@@ -263,9 +265,23 @@ export const MultiSelectProAsync = React.forwardRef<MultiSelectProAsyncRef, Mult
|
|
|
263
265
|
}, [defaultValueKey, resetOnDefaultValueChange])
|
|
264
266
|
|
|
265
267
|
// Announce changes for screen readers
|
|
268
|
+
// One timer, cleared on unmount: the announcement is transient UI state,
|
|
269
|
+
// and stacking a timer per change clears it early for the latest message.
|
|
270
|
+
const announceTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
271
|
+
React.useEffect(
|
|
272
|
+
() => () => {
|
|
273
|
+
if (announceTimerRef.current) clearTimeout(announceTimerRef.current)
|
|
274
|
+
},
|
|
275
|
+
[]
|
|
276
|
+
)
|
|
277
|
+
|
|
266
278
|
const announce = React.useCallback((message: string) => {
|
|
267
279
|
setAnnouncements(message)
|
|
268
|
-
|
|
280
|
+
if (announceTimerRef.current) clearTimeout(announceTimerRef.current)
|
|
281
|
+
announceTimerRef.current = setTimeout(() => {
|
|
282
|
+
announceTimerRef.current = null
|
|
283
|
+
setAnnouncements("")
|
|
284
|
+
}, 1000)
|
|
269
285
|
}, [])
|
|
270
286
|
|
|
271
287
|
// Toggle selection
|
|
@@ -233,7 +233,9 @@ export const MultiSelectPro = React.forwardRef<MultiSelectProRef, MultiSelectPro
|
|
|
233
233
|
badgeAnimation: animationConfig?.badgeAnimation || (animation > 0 ? "bounce" : "none"),
|
|
234
234
|
popoverAnimation: animationConfig?.popoverAnimation || "scale",
|
|
235
235
|
optionHoverAnimation: animationConfig?.optionHoverAnimation || "highlight",
|
|
236
|
-
|
|
236
|
+
// `??`: a caller asking for duration 0 means "no animation",
|
|
237
|
+
// and `||` swallowed it into the 0.3 default.
|
|
238
|
+
duration: animationConfig?.duration ?? (animation > 0 ? animation : 0.3),
|
|
237
239
|
delay: animationConfig?.delay || 0,
|
|
238
240
|
}),
|
|
239
241
|
[animation, animationConfig]
|
|
@@ -247,9 +249,23 @@ export const MultiSelectPro = React.forwardRef<MultiSelectProRef, MultiSelectPro
|
|
|
247
249
|
}, [defaultValue, resetOnDefaultValueChange])
|
|
248
250
|
|
|
249
251
|
// Announce changes for screen readers
|
|
252
|
+
// One timer, cleared on unmount: the announcement is transient UI state,
|
|
253
|
+
// and stacking a timer per change clears it early for the latest message.
|
|
254
|
+
const announceTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
255
|
+
React.useEffect(
|
|
256
|
+
() => () => {
|
|
257
|
+
if (announceTimerRef.current) clearTimeout(announceTimerRef.current)
|
|
258
|
+
},
|
|
259
|
+
[]
|
|
260
|
+
)
|
|
261
|
+
|
|
250
262
|
const announce = React.useCallback((message: string) => {
|
|
251
263
|
setAnnouncements(message)
|
|
252
|
-
|
|
264
|
+
if (announceTimerRef.current) clearTimeout(announceTimerRef.current)
|
|
265
|
+
announceTimerRef.current = setTimeout(() => {
|
|
266
|
+
announceTimerRef.current = null
|
|
267
|
+
setAnnouncements("")
|
|
268
|
+
}, 1000)
|
|
253
269
|
}, [])
|
|
254
270
|
|
|
255
271
|
// Toggle selection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect } from 'react';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useDeviceDetect } from '../device/useDeviceDetect';
|
|
6
6
|
|
|
@@ -95,12 +95,20 @@ function releaseLock() {
|
|
|
95
95
|
export function useBodyScrollLock(locked: boolean): void {
|
|
96
96
|
const device = useDeviceDetect();
|
|
97
97
|
|
|
98
|
+
// `isIOS` starts false and flips once the UA parse resolves. With it in the
|
|
99
|
+
// deps the effect re-ran on that flip: the cleanup dropped the shared
|
|
100
|
+
// lockCount to 0 and released the lock — unlocking the page under a second
|
|
101
|
+
// overlay that was still open. The lock strategy is chosen once, on the
|
|
102
|
+
// acquisition that takes the count from 0 to 1.
|
|
103
|
+
const isIOSRef = useRef(device.isIOS);
|
|
104
|
+
isIOSRef.current = device.isIOS;
|
|
105
|
+
|
|
98
106
|
useEffect(() => {
|
|
99
107
|
if (!locked) return;
|
|
100
108
|
if (typeof document === 'undefined') return;
|
|
101
109
|
|
|
102
110
|
if (lockCount === 0) {
|
|
103
|
-
applyLock(
|
|
111
|
+
applyLock(isIOSRef.current);
|
|
104
112
|
}
|
|
105
113
|
lockCount += 1;
|
|
106
114
|
|
|
@@ -110,5 +118,5 @@ export function useBodyScrollLock(locked: boolean): void {
|
|
|
110
118
|
releaseLock();
|
|
111
119
|
}
|
|
112
120
|
};
|
|
113
|
-
}, [locked
|
|
121
|
+
}, [locked]);
|
|
114
122
|
}
|