@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 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 ??= 'button'
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.22",
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.22",
16
- "@startupjs-ui/core": "^0.1.22",
17
- "@startupjs-ui/span": "^0.1.22"
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": "3bd18c16f0f203ee3d940bf2e09381edc0034665"
25
+ "gitHead": "b48004779559b16c96a2a1995dab13b998eafce9"
26
26
  }