@startupjs-ui/div 0.1.22 → 0.2.0-alpha.1
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/CHANGELOG.md +19 -0
- package/index.d.ts +2 -0
- package/index.tsx +113 -13
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [0.2.0-alpha.1](https://github.com/startupjs/startupjs-ui/compare/v0.2.0-alpha.0...v0.2.0-alpha.1) (2026-04-10)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @startupjs-ui/div
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [0.2.0-alpha.0](https://github.com/startupjs/startupjs-ui/compare/v0.1.22...v0.2.0-alpha.0) (2026-03-27)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* fix and improve accessibility of various components. Add storybook with tests. ([#21](https://github.com/startupjs/startupjs-ui/issues/21)) ([83b6576](https://github.com/startupjs/startupjs-ui/commit/83b65767ed61b24209f71b143ba1c2986170ab58))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
## [0.1.22](https://github.com/startupjs/startupjs-ui/compare/v0.1.21...v0.1.22) (2026-03-25)
|
|
7
26
|
|
|
8
27
|
**Note:** Version bump only for package @startupjs-ui/div
|
package/index.d.ts
CHANGED
|
@@ -63,6 +63,8 @@ export interface DivProps extends ViewProps {
|
|
|
63
63
|
accessibilityRole?: AccessibilityRole;
|
|
64
64
|
/** Deprecated custom tooltip renderer @deprecated */
|
|
65
65
|
renderTooltip?: any;
|
|
66
|
+
/** Internal: render a native <button> host on web when the resolved role is button */
|
|
67
|
+
_webNativeButton?: boolean;
|
|
66
68
|
/** Test ID for testing purposes */
|
|
67
69
|
'data-testid'?: string;
|
|
68
70
|
}
|
package/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useRef, type ReactNode, type RefObject } from 'react'
|
|
1
|
+
import { useLayoutEffect, useState, useRef, type ReactNode, type RefObject } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Pressable,
|
|
@@ -17,11 +17,6 @@ import STYLES from './index.cssx.styl'
|
|
|
17
17
|
|
|
18
18
|
const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
|
|
19
19
|
|
|
20
|
-
function hasAnimatedProperty (style: any): boolean {
|
|
21
|
-
if (!style) return false
|
|
22
|
-
return Object.keys(style).some(key => key.startsWith('animation') || key.startsWith('transition'))
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
const DEPRECATED_PUSHED_VALUES = ['xs', 'xl', 'xxl']
|
|
26
21
|
const PRESSABLE_PROPS = ['onPress', 'onLongPress', 'onPressIn', 'onPressOut']
|
|
27
22
|
const isWeb = Platform.OS === 'web'
|
|
@@ -95,6 +90,8 @@ export interface DivProps extends ViewProps {
|
|
|
95
90
|
accessibilityRole?: AccessibilityRole
|
|
96
91
|
/** Deprecated custom tooltip renderer @deprecated */
|
|
97
92
|
renderTooltip?: any // Deprecated
|
|
93
|
+
/** Internal: render a native <button> host on web when the resolved role is button */
|
|
94
|
+
_webNativeButton?: boolean
|
|
98
95
|
/** Test ID for testing purposes */
|
|
99
96
|
'data-testid'?: string
|
|
100
97
|
}
|
|
@@ -123,6 +120,7 @@ function Div ({
|
|
|
123
120
|
tooltip,
|
|
124
121
|
tooltipStyle,
|
|
125
122
|
renderTooltip,
|
|
123
|
+
_webNativeButton = false,
|
|
126
124
|
ref,
|
|
127
125
|
...props
|
|
128
126
|
}: DivProps): ReactNode {
|
|
@@ -136,10 +134,12 @@ function Div ({
|
|
|
136
134
|
const rootRef = ref ?? fallbackRef
|
|
137
135
|
|
|
138
136
|
let pressableStyle: StyleProp<ViewStyle> = {}
|
|
137
|
+
let deferredRole: AccessibilityRole | string | undefined
|
|
139
138
|
;({
|
|
140
139
|
props,
|
|
141
140
|
pressableStyle,
|
|
142
|
-
accessibilityRole
|
|
141
|
+
accessibilityRole,
|
|
142
|
+
deferredRole
|
|
143
143
|
} = useDecoratePressableProps({
|
|
144
144
|
props,
|
|
145
145
|
style,
|
|
@@ -149,7 +149,24 @@ function Div ({
|
|
|
149
149
|
isPressable,
|
|
150
150
|
disabled,
|
|
151
151
|
accessibilityRole,
|
|
152
|
-
feedback
|
|
152
|
+
feedback,
|
|
153
|
+
webNativeButton: _webNativeButton
|
|
154
|
+
}))
|
|
155
|
+
|
|
156
|
+
;({
|
|
157
|
+
props,
|
|
158
|
+
accessible,
|
|
159
|
+
accessibilityRole,
|
|
160
|
+
deferredRole
|
|
161
|
+
} = useDecorateAccessibilityProps({
|
|
162
|
+
props,
|
|
163
|
+
rootRef,
|
|
164
|
+
disabled,
|
|
165
|
+
accessible,
|
|
166
|
+
accessibilityRole,
|
|
167
|
+
isPressable,
|
|
168
|
+
deferredRole,
|
|
169
|
+
webNativeButton: _webNativeButton
|
|
153
170
|
}))
|
|
154
171
|
|
|
155
172
|
let tooltipElement
|
|
@@ -172,8 +189,6 @@ function Div ({
|
|
|
172
189
|
let levelModifier
|
|
173
190
|
if (level) levelModifier = `shadow-${level}`
|
|
174
191
|
|
|
175
|
-
if (!accessible) accessibilityRole = undefined
|
|
176
|
-
|
|
177
192
|
const isAnimated = hasAnimatedProperty(style) || hasAnimatedProperty(pressableStyle)
|
|
178
193
|
const Component = isPressable
|
|
179
194
|
? (isAnimated ? AnimatedPressable : Pressable)
|
|
@@ -217,6 +232,78 @@ function Div ({
|
|
|
217
232
|
} else return divElement
|
|
218
233
|
}
|
|
219
234
|
|
|
235
|
+
function hasAnimatedProperty (style: any): boolean {
|
|
236
|
+
if (!style) return false
|
|
237
|
+
return Object.keys(style).some(key => key.startsWith('animation') || key.startsWith('transition'))
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function useDecorateAccessibilityProps ({
|
|
241
|
+
props,
|
|
242
|
+
rootRef,
|
|
243
|
+
disabled,
|
|
244
|
+
accessible,
|
|
245
|
+
accessibilityRole,
|
|
246
|
+
isPressable,
|
|
247
|
+
deferredRole,
|
|
248
|
+
webNativeButton
|
|
249
|
+
}: {
|
|
250
|
+
props: Record<string, any>
|
|
251
|
+
rootRef: RefObject<any>
|
|
252
|
+
disabled?: boolean
|
|
253
|
+
accessible?: boolean
|
|
254
|
+
accessibilityRole?: AccessibilityRole
|
|
255
|
+
isPressable: boolean
|
|
256
|
+
deferredRole?: AccessibilityRole | string
|
|
257
|
+
webNativeButton?: boolean
|
|
258
|
+
}): {
|
|
259
|
+
props: Record<string, any>
|
|
260
|
+
accessible?: boolean
|
|
261
|
+
accessibilityRole?: AccessibilityRole
|
|
262
|
+
deferredRole?: AccessibilityRole | string
|
|
263
|
+
} {
|
|
264
|
+
if (accessible == null && isPressable) accessible = true
|
|
265
|
+
if (accessible === false) {
|
|
266
|
+
accessibilityRole = undefined
|
|
267
|
+
deferredRole = undefined
|
|
268
|
+
props.role = undefined
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (props['aria-disabled'] == null && disabled != null) {
|
|
272
|
+
props['aria-disabled'] = disabled
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const roleProp = props.role
|
|
276
|
+
const ariaDisabled = props['aria-disabled']
|
|
277
|
+
|
|
278
|
+
useLayoutEffect(() => {
|
|
279
|
+
if (!isWeb) return
|
|
280
|
+
const node = rootRef.current
|
|
281
|
+
if (!node || typeof node.setAttribute !== 'function') return
|
|
282
|
+
// Keep role declarative by default too. This manual patch is only for the
|
|
283
|
+
// deferred-role web path where we intentionally avoid a native <button>
|
|
284
|
+
// host to prevent invalid nested-button markup, but still need button
|
|
285
|
+
// semantics on the resulting DOM node.
|
|
286
|
+
if (deferredRole != null) {
|
|
287
|
+
node.setAttribute('role', deferredRole)
|
|
288
|
+
} else if (roleProp == null) {
|
|
289
|
+
node.removeAttribute('role')
|
|
290
|
+
}
|
|
291
|
+
// Keep the RN / aria props declarative by default. This manual patch is only
|
|
292
|
+
// for the current RN Web bug where disabled semantics are dropped on the
|
|
293
|
+
// deferred-role pressable path that we use to avoid nested native buttons.
|
|
294
|
+
if (ariaDisabled != null) {
|
|
295
|
+
node.setAttribute('aria-disabled', String(ariaDisabled))
|
|
296
|
+
} else {
|
|
297
|
+
node.removeAttribute('aria-disabled')
|
|
298
|
+
}
|
|
299
|
+
if (webNativeButton && 'disabled' in node) {
|
|
300
|
+
node.disabled = !!disabled
|
|
301
|
+
}
|
|
302
|
+
}, [rootRef, deferredRole, roleProp, ariaDisabled, webNativeButton, disabled])
|
|
303
|
+
|
|
304
|
+
return { props, accessible, accessibilityRole, deferredRole }
|
|
305
|
+
}
|
|
306
|
+
|
|
220
307
|
function useDecoratePressableProps ({
|
|
221
308
|
props,
|
|
222
309
|
style,
|
|
@@ -226,7 +313,8 @@ function useDecoratePressableProps ({
|
|
|
226
313
|
isPressable,
|
|
227
314
|
disabled,
|
|
228
315
|
accessibilityRole,
|
|
229
|
-
feedback
|
|
316
|
+
feedback,
|
|
317
|
+
webNativeButton
|
|
230
318
|
}: {
|
|
231
319
|
props: Record<string, any>
|
|
232
320
|
style: StyleProp<ViewStyle>
|
|
@@ -237,12 +325,15 @@ function useDecoratePressableProps ({
|
|
|
237
325
|
disabled?: boolean
|
|
238
326
|
accessibilityRole?: AccessibilityRole
|
|
239
327
|
feedback?: boolean
|
|
328
|
+
webNativeButton?: boolean
|
|
240
329
|
}): {
|
|
241
330
|
props: Record<string, any>
|
|
242
331
|
pressableStyle?: StyleProp<ViewStyle>
|
|
243
332
|
accessibilityRole?: AccessibilityRole
|
|
333
|
+
deferredRole?: AccessibilityRole | string
|
|
244
334
|
} {
|
|
245
335
|
let pressableStyle: StyleProp<ViewStyle> = {}
|
|
336
|
+
let deferredRole: AccessibilityRole | string | undefined
|
|
246
337
|
const [hover, setHover] = useState(false)
|
|
247
338
|
const [active, setActive] = useState(false)
|
|
248
339
|
|
|
@@ -259,9 +350,18 @@ function useDecoratePressableProps ({
|
|
|
259
350
|
// decorate the element state (hover, active) only if it's pressable
|
|
260
351
|
if (!isPressable) return { props }
|
|
261
352
|
|
|
262
|
-
accessibilityRole
|
|
353
|
+
const resolvedRole = props.role ?? accessibilityRole ?? 'button'
|
|
354
|
+
accessibilityRole ??= typeof resolvedRole === 'string' ? resolvedRole as AccessibilityRole : undefined
|
|
263
355
|
props.focusable ??= true
|
|
264
356
|
|
|
357
|
+
if (isWeb && resolvedRole === 'button' && !webNativeButton) {
|
|
358
|
+
delete props.role
|
|
359
|
+
accessibilityRole = undefined
|
|
360
|
+
deferredRole = 'button'
|
|
361
|
+
} else {
|
|
362
|
+
props.role ??= accessibilityRole
|
|
363
|
+
}
|
|
364
|
+
|
|
265
365
|
// setup hover and active states styles and props
|
|
266
366
|
if (feedback) {
|
|
267
367
|
const { onPressIn, onPressOut } = props
|
|
@@ -306,7 +406,7 @@ function useDecoratePressableProps ({
|
|
|
306
406
|
}
|
|
307
407
|
}
|
|
308
408
|
|
|
309
|
-
return { props, pressableStyle, accessibilityRole }
|
|
409
|
+
return { props, pressableStyle, accessibilityRole, deferredRole }
|
|
310
410
|
}
|
|
311
411
|
|
|
312
412
|
function getDefaultStyle (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startupjs-ui/div",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.0-alpha.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"./useTooltip": "./useTooltip.tsx"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@startupjs-ui/abstract-popover": "^0.1
|
|
16
|
-
"@startupjs-ui/core": "^0.1
|
|
17
|
-
"@startupjs-ui/span": "^0.1
|
|
15
|
+
"@startupjs-ui/abstract-popover": "^0.2.0-alpha.1",
|
|
16
|
+
"@startupjs-ui/core": "^0.2.0-alpha.1",
|
|
17
|
+
"@startupjs-ui/span": "^0.2.0-alpha.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"react": "*",
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"react-native-reanimated": ">=4.0.0",
|
|
23
23
|
"startupjs": "*"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "b48004779559b16c96a2a1995dab13b998eafce9"
|
|
26
26
|
}
|