@startupjs-ui/div 0.2.0 → 0.3.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,28 @@
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.3.1](https://github.com/startupjs/startupjs-ui/compare/v0.3.0...v0.3.1) (2026-06-08)
7
+
8
+
9
+ ### Features
10
+
11
+ * support inherited text styles from Div, support relative lineHeight ([#32](https://github.com/startupjs/startupjs-ui/issues/32)) ([47b56ab](https://github.com/startupjs/startupjs-ui/commit/47b56abca03b1d3ef1d977309deffd95a2de709d))
12
+
13
+
14
+
15
+
16
+
17
+ # [0.3.0](https://github.com/startupjs/startupjs-ui/compare/v0.2.3...v0.3.0) (2026-05-27)
18
+
19
+
20
+ ### Features
21
+
22
+ * [BREAKING] [0.3] improve accessibility props for E2E tests. Support testID everywhere ([#31](https://github.com/startupjs/startupjs-ui/issues/31)) ([882588c](https://github.com/startupjs/startupjs-ui/commit/882588ca37d5e1fd14b5717b5697cf9ed47042e4))
23
+
24
+
25
+
26
+
27
+
6
28
  # [0.2.0](https://github.com/startupjs/startupjs-ui/compare/v0.1.23...v0.2.0) (2026-05-04)
7
29
 
8
30
 
package/README.mdx CHANGED
@@ -8,7 +8,7 @@ import Icon from '@startupjs-ui/icon'
8
8
 
9
9
  # Div
10
10
 
11
- Div is a basic building block for layouts. It is a wrapper around `View` with extra features like press handling (`onPress`, `onLongPress`, `onPressIn`, `onPressOut`), layout helpers (`row`, `wrap`, `reverse`, `align`, `vAlign`, `gap`), shadows (`level`), corner shapes (`shape`), spacing (`pushed`, `bleed`), tooltips (`tooltip`, `tooltipStyle`), and built-in animation support. You can pass custom `style`, provide content as `children`, and use `ref` to access the underlying native view. Accessibility is supported via `accessible` and `accessibilityRole`, and `data-testid` can be used for testing.
11
+ Div is a basic building block for layouts. It is a wrapper around `View` with extra features like press handling (`onPress`, `onLongPress`, `onPressIn`, `onPressOut`), layout helpers (`row`, `wrap`, `reverse`, `align`, `vAlign`, `gap`), shadows (`level`), corner shapes (`shape`), spacing (`pushed`, `bleed`), tooltips (`tooltip`, `tooltipStyle`), and built-in animation support. You can pass custom `style`, provide content as `children`, and use `ref` to access the underlying native view. Accessibility and E2E semantics are exposed with `role`, `aria-*`, and `testID`.
12
12
 
13
13
  ```jsx
14
14
  import { Div } from 'startupjs-ui'
package/index.d.ts CHANGED
@@ -2,13 +2,19 @@
2
2
  // DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
3
3
 
4
4
  import { type ReactNode, type RefObject } from 'react';
5
- import { type StyleProp, type ViewStyle, type ViewProps, type AccessibilityRole } from 'react-native';
5
+ import { type StyleProp, type ViewStyle, type ViewProps } from 'react-native';
6
+ import { type UIRole } from '@startupjs-ui/core';
7
+ type AriaHasPopup = boolean | 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree';
6
8
  declare const _default: import("react").ComponentType<DivProps>;
7
9
  export default _default;
8
10
  export declare const _PropsJsonSchema: {};
9
- export interface DivProps extends ViewProps {
11
+ export interface DivProps extends Omit<ViewProps, 'role'> {
10
12
  /** Ref to access underlying <View> or <Pressable> */
11
13
  ref?: RefObject<any>;
14
+ /** Accessibility role. Includes RN roles plus web-only ARIA roles used by RNW. */
15
+ role?: UIRole;
16
+ /** Web popup type exposed through aria-haspopup */
17
+ 'aria-haspopup'?: AriaHasPopup;
12
18
  /** Custom styles applied to the root view */
13
19
  style?: StyleProp<ViewStyle>;
14
20
  /** Content rendered inside Div */
@@ -59,12 +65,8 @@ export interface DivProps extends ViewProps {
59
65
  onPressOut?: (e: any) => void;
60
66
  /** Whether view is accessible and focusable (if you can press it it's focusable by default) */
61
67
  accessible?: boolean;
62
- /** Accessibility role passed to native view (if you can press it it's a 'button') */
63
- accessibilityRole?: AccessibilityRole;
64
68
  /** Deprecated custom tooltip renderer @deprecated */
65
69
  renderTooltip?: any;
66
70
  /** Internal: render a native <button> host on web when the resolved role is button */
67
71
  _webNativeButton?: boolean;
68
- /** Test ID for testing purposes */
69
- 'data-testid'?: string;
70
72
  }
package/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useLayoutEffect, useState, useRef, type ReactNode, type RefObject } from 'react'
1
+ import { useContext, useLayoutEffect, useMemo, useState, useRef, type ReactNode, type RefObject } from 'react'
2
2
  import {
3
3
  View,
4
4
  Pressable,
@@ -6,17 +6,24 @@ import {
6
6
  StyleSheet,
7
7
  type StyleProp,
8
8
  type ViewStyle,
9
- type ViewProps,
10
- type AccessibilityRole
9
+ type ViewProps
11
10
  } from 'react-native'
12
11
  import Animated from 'react-native-reanimated'
13
12
  import { pug, observer, u, useDidUpdate } from 'startupjs'
14
- import { colorToRGBA, getCssVariable, themed } from '@startupjs-ui/core'
13
+ import { colorToRGBA, getCssVariable, themed, type UIRole } from '@startupjs-ui/core'
14
+ import {
15
+ TextStyleContext,
16
+ getInheritedTextStyle,
17
+ mergeInheritedTextStyles,
18
+ omitInheritedTextStyle
19
+ } from '@startupjs-ui/span/textStyleContext'
15
20
  import { useDecorateTooltipProps } from './useTooltip'
16
21
  import STYLES from './index.cssx.styl'
17
22
 
18
23
  const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
19
24
 
25
+ type AriaHasPopup = boolean | 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree'
26
+
20
27
  const DEPRECATED_PUSHED_VALUES = ['xs', 'xl', 'xxl']
21
28
  const PRESSABLE_PROPS = ['onPress', 'onLongPress', 'onPressIn', 'onPressOut']
22
29
  const isWeb = Platform.OS === 'web'
@@ -33,9 +40,13 @@ export default observer(themed('Div', Div))
33
40
 
34
41
  export const _PropsJsonSchema = {/* DivProps */}
35
42
 
36
- export interface DivProps extends ViewProps {
43
+ export interface DivProps extends Omit<ViewProps, 'role'> {
37
44
  /** Ref to access underlying <View> or <Pressable> */
38
45
  ref?: RefObject<any>
46
+ /** Accessibility role. Includes RN roles plus web-only ARIA roles used by RNW. */
47
+ role?: UIRole
48
+ /** Web popup type exposed through aria-haspopup */
49
+ 'aria-haspopup'?: AriaHasPopup
39
50
  /** Custom styles applied to the root view */
40
51
  style?: StyleProp<ViewStyle>
41
52
  /** Content rendered inside Div */
@@ -86,18 +97,14 @@ export interface DivProps extends ViewProps {
86
97
  onPressOut?: (e: any) => void
87
98
  /** Whether view is accessible and focusable (if you can press it it's focusable by default) */
88
99
  accessible?: boolean
89
- /** Accessibility role passed to native view (if you can press it it's a 'button') */
90
- accessibilityRole?: AccessibilityRole
91
100
  /** Deprecated custom tooltip renderer @deprecated */
92
101
  renderTooltip?: any // Deprecated
93
102
  /** Internal: render a native <button> host on web when the resolved role is button */
94
103
  _webNativeButton?: boolean
95
- /** Test ID for testing purposes */
96
- 'data-testid'?: string
97
104
  }
98
105
 
99
106
  function Div ({
100
- style = [],
107
+ style: rawStyle = [],
101
108
  children,
102
109
  variant = 'opacity',
103
110
  row,
@@ -116,7 +123,6 @@ function Div ({
116
123
  bleed,
117
124
  full,
118
125
  accessible,
119
- accessibilityRole,
120
126
  tooltip,
121
127
  tooltipStyle,
122
128
  renderTooltip,
@@ -125,20 +131,29 @@ function Div ({
125
131
  ...props
126
132
  }: DivProps): ReactNode {
127
133
  assertDeprecatedValues({ pushed, renderTooltip })
128
- style = StyleSheet.flatten(style)
134
+ let style = StyleSheet.flatten(rawStyle) as ViewStyle | undefined
129
135
  // on RN row-reverse switches margins and paddings sides, so we switch them back
130
136
  if (isNative && reverse) style = reverseMarginPaddingSides(style)
137
+
138
+ const inheritedTextStyle = useContext(TextStyleContext)
139
+ const ownTextStyle = getInheritedTextStyle(style)
140
+ const nextInheritedTextStyleKey = simpleNumericHash(JSON.stringify([inheritedTextStyle, ownTextStyle]))
141
+ const nextInheritedTextStyle = useMemo(() => {
142
+ if (!ownTextStyle) return undefined
143
+ return mergeInheritedTextStyles(inheritedTextStyle, ownTextStyle)
144
+ }, [nextInheritedTextStyleKey]) // eslint-disable-line react-hooks/exhaustive-deps
145
+ omitInheritedTextStyle(style)
146
+
131
147
  if (gap === true) gap = 2
132
148
  const isPressable = hasPressHandler(props)
133
149
  const fallbackRef = useRef<any>(null)
134
150
  const rootRef = ref ?? fallbackRef
135
151
 
136
152
  let pressableStyle: StyleProp<ViewStyle> = {}
137
- let deferredRole: AccessibilityRole | string | undefined
153
+ let deferredRole: string | undefined
138
154
  ;({
139
155
  props,
140
156
  pressableStyle,
141
- accessibilityRole,
142
157
  deferredRole
143
158
  } = useDecoratePressableProps({
144
159
  props,
@@ -148,22 +163,18 @@ function Div ({
148
163
  variant,
149
164
  isPressable,
150
165
  disabled,
151
- accessibilityRole,
152
166
  feedback,
153
167
  webNativeButton: _webNativeButton
154
168
  }))
155
169
 
156
170
  ;({
157
171
  props,
158
- accessible,
159
- accessibilityRole,
160
- deferredRole
172
+ accessible
161
173
  } = useDecorateAccessibilityProps({
162
174
  props,
163
175
  rootRef,
164
176
  disabled,
165
177
  accessible,
166
- accessibilityRole,
167
178
  isPressable,
168
179
  deferredRole,
169
180
  webNativeButton: _webNativeButton
@@ -193,7 +204,7 @@ function Div ({
193
204
  const Component = isPressable
194
205
  ? (isAnimated ? AnimatedPressable : Pressable)
195
206
  : (isAnimated ? Animated.View : View)
196
- const testID = props.testID ?? props['data-testid']
207
+ const renderProps = props as Omit<typeof props, 'role'> & { role?: ViewProps['role'] }
197
208
  const divElement = pug`
198
209
  Component.root(
199
210
  ref=rootRef
@@ -218,18 +229,26 @@ function Div ({
218
229
  levelModifier
219
230
  ]
220
231
  accessible=accessible
221
- accessibilityRole=accessibilityRole
222
- testID=testID
223
- ...props
232
+ ...renderProps
224
233
  )= children
225
234
  `
235
+ const styledDivElement = nextInheritedTextStyle
236
+ ? pug`
237
+ TextStyleContext.Provider(value=nextInheritedTextStyle)
238
+ = divElement
239
+ `
240
+ : divElement
226
241
 
227
242
  if (tooltipElement) {
228
243
  return pug`
229
- = divElement
244
+ = styledDivElement
230
245
  = tooltipElement
231
246
  `
232
- } else return divElement
247
+ } else return styledDivElement
248
+ }
249
+
250
+ function isWebOnlyRole (role: unknown): role is Exclude<UIRole, ViewProps['role']> {
251
+ return role === 'listbox' || role === 'gridcell'
233
252
  }
234
253
 
235
254
  function hasAnimatedProperty (style: any): boolean {
@@ -242,7 +261,6 @@ function useDecorateAccessibilityProps ({
242
261
  rootRef,
243
262
  disabled,
244
263
  accessible,
245
- accessibilityRole,
246
264
  isPressable,
247
265
  deferredRole,
248
266
  webNativeButton
@@ -251,19 +269,15 @@ function useDecorateAccessibilityProps ({
251
269
  rootRef: RefObject<any>
252
270
  disabled?: boolean
253
271
  accessible?: boolean
254
- accessibilityRole?: AccessibilityRole
255
272
  isPressable: boolean
256
- deferredRole?: AccessibilityRole | string
273
+ deferredRole?: string
257
274
  webNativeButton?: boolean
258
275
  }): {
259
276
  props: Record<string, any>
260
277
  accessible?: boolean
261
- accessibilityRole?: AccessibilityRole
262
- deferredRole?: AccessibilityRole | string
263
278
  } {
264
279
  if (accessible == null && isPressable) accessible = true
265
280
  if (accessible === false) {
266
- accessibilityRole = undefined
267
281
  deferredRole = undefined
268
282
  props.role = undefined
269
283
  }
@@ -272,6 +286,9 @@ function useDecorateAccessibilityProps ({
272
286
  props['aria-disabled'] = disabled
273
287
  }
274
288
 
289
+ if (isNative && isWebOnlyRole(props.role)) delete props.role
290
+ if (isNative) delete props['aria-haspopup']
291
+
275
292
  const roleProp = props.role
276
293
  const ariaDisabled = props['aria-disabled']
277
294
 
@@ -301,7 +318,7 @@ function useDecorateAccessibilityProps ({
301
318
  }
302
319
  }, [rootRef, deferredRole, roleProp, ariaDisabled, webNativeButton, disabled])
303
320
 
304
- return { props, accessible, accessibilityRole, deferredRole }
321
+ return { props, accessible }
305
322
  }
306
323
 
307
324
  function useDecoratePressableProps ({
@@ -312,7 +329,6 @@ function useDecoratePressableProps ({
312
329
  variant,
313
330
  isPressable,
314
331
  disabled,
315
- accessibilityRole,
316
332
  feedback,
317
333
  webNativeButton
318
334
  }: {
@@ -323,17 +339,15 @@ function useDecoratePressableProps ({
323
339
  variant: 'opacity' | 'highlight'
324
340
  isPressable: boolean
325
341
  disabled?: boolean
326
- accessibilityRole?: AccessibilityRole
327
342
  feedback?: boolean
328
343
  webNativeButton?: boolean
329
344
  }): {
330
345
  props: Record<string, any>
331
346
  pressableStyle?: StyleProp<ViewStyle>
332
- accessibilityRole?: AccessibilityRole
333
- deferredRole?: AccessibilityRole | string
347
+ deferredRole?: string
334
348
  } {
335
349
  let pressableStyle: StyleProp<ViewStyle> = {}
336
- let deferredRole: AccessibilityRole | string | undefined
350
+ let deferredRole: string | undefined
337
351
  const [hover, setHover] = useState(false)
338
352
  const [active, setActive] = useState(false)
339
353
 
@@ -350,16 +364,14 @@ function useDecoratePressableProps ({
350
364
  // decorate the element state (hover, active) only if it's pressable
351
365
  if (!isPressable) return { props }
352
366
 
353
- const resolvedRole = props.role ?? accessibilityRole ?? 'button'
354
- accessibilityRole ??= typeof resolvedRole === 'string' ? resolvedRole as AccessibilityRole : undefined
367
+ const resolvedRole = props.role ?? 'button'
355
368
  props.focusable ??= true
356
369
 
357
370
  if (isWeb && resolvedRole === 'button' && !webNativeButton) {
358
371
  delete props.role
359
- accessibilityRole = undefined
360
372
  deferredRole = 'button'
361
373
  } else {
362
- props.role ??= accessibilityRole
374
+ props.role ??= resolvedRole
363
375
  }
364
376
 
365
377
  // setup hover and active states styles and props
@@ -406,7 +418,7 @@ function useDecoratePressableProps ({
406
418
  }
407
419
  }
408
420
 
409
- return { props, pressableStyle, accessibilityRole, deferredRole }
421
+ return { props, pressableStyle, deferredRole }
410
422
  }
411
423
 
412
424
  function getDefaultStyle (
@@ -446,8 +458,8 @@ function hasPressHandler (props: Record<string, any>): boolean {
446
458
  return PRESSABLE_PROPS.some(prop => props[prop])
447
459
  }
448
460
 
449
- function reverseMarginPaddingSides (style: StyleProp<ViewStyle>) {
450
- style = StyleSheet.flatten(style)
461
+ function reverseMarginPaddingSides (style: ViewStyle | undefined): ViewStyle | undefined {
462
+ if (!style) return style
451
463
  const { paddingLeft, paddingRight, marginLeft, marginRight } = style
452
464
  style.marginLeft = marginRight
453
465
  style.marginRight = marginLeft
@@ -456,6 +468,12 @@ function reverseMarginPaddingSides (style: StyleProp<ViewStyle>) {
456
468
  return style
457
469
  }
458
470
 
471
+ function simpleNumericHash (s: string): number {
472
+ let h = 0
473
+ for (let i = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0
474
+ return h
475
+ }
476
+
459
477
  function assertDeprecatedValues ({ pushed, renderTooltip }: { pushed?: any, renderTooltip?: any }) {
460
478
  if (DEPRECATED_PUSHED_VALUES.includes(pushed)) console.warn(ERRORS.DEPRECATED_PUSHED(pushed))
461
479
  if (renderTooltip) console.warn(ERRORS.DEPRECATED_RENDER_TOOLTIP)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startupjs-ui/div",
3
- "version": "0.2.0",
3
+ "version": "0.3.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.2.0",
16
- "@startupjs-ui/core": "^0.2.0",
17
- "@startupjs-ui/span": "^0.2.0"
15
+ "@startupjs-ui/abstract-popover": "^0.3.0",
16
+ "@startupjs-ui/core": "^0.3.0",
17
+ "@startupjs-ui/span": "^0.3.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": "0c586b841cba1c9d820542f6eca07470f5ea2659"
25
+ "gitHead": "60311773bdc83f354c797a272774304502d28c58"
26
26
  }