@startupjs-ui/div 0.3.0 → 0.3.4

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.4](https://github.com/startupjs/startupjs-ui/compare/v0.3.3...v0.3.4) (2026-06-18)
7
+
8
+
9
+ ### Features
10
+
11
+ * **div, span:** supportTextNodes auto-wrap on Div + pure mode on Span ([#35](https://github.com/startupjs/startupjs-ui/issues/35)) ([d7bf159](https://github.com/startupjs/startupjs-ui/commit/d7bf159e73060a2531f2a1d66ec1932e9ddc3e6f))
12
+
13
+
14
+
15
+
16
+
17
+ ## [0.3.1](https://github.com/startupjs/startupjs-ui/compare/v0.3.0...v0.3.1) (2026-06-08)
18
+
19
+
20
+ ### Features
21
+
22
+ * 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))
23
+
24
+
25
+
26
+
27
+
6
28
  # [0.3.0](https://github.com/startupjs/startupjs-ui/compare/v0.2.3...v0.3.0) (2026-05-27)
7
29
 
8
30
 
package/index.d.ts CHANGED
@@ -19,6 +19,13 @@ export interface DivProps extends Omit<ViewProps, 'role'> {
19
19
  style?: StyleProp<ViewStyle>;
20
20
  /** Content rendered inside Div */
21
21
  children?: ReactNode;
22
+ /** Auto-wrap bare text children. When true, runs of consecutive string/number
23
+ * children (including those inside arrays/fragments) are each wrapped into a
24
+ * single text node so they render correctly in a non-text container. @default false */
25
+ supportTextNodes?: boolean;
26
+ /** How to render an auto-wrapped text run (only used with supportTextNodes).
27
+ * Receives the merged text; should return a text element. Defaults to <Span/>. */
28
+ renderTextNode?: (text: string) => ReactNode;
22
29
  /** Visual feedback variant @default 'opacity' */
23
30
  variant?: 'opacity' | 'highlight';
24
31
  /** Render children in a horizontal row */
package/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useLayoutEffect, useState, useRef, type ReactNode, type RefObject } from 'react'
1
+ import { Children, cloneElement, createElement, Fragment, isValidElement, useContext, useLayoutEffect, useMemo, useState, useRef, type ReactNode, type RefObject } from 'react'
2
2
  import {
3
3
  View,
4
4
  Pressable,
@@ -11,6 +11,13 @@ import {
11
11
  import Animated from 'react-native-reanimated'
12
12
  import { pug, observer, u, useDidUpdate } from 'startupjs'
13
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'
20
+ import Span from '@startupjs-ui/span'
14
21
  import { useDecorateTooltipProps } from './useTooltip'
15
22
  import STYLES from './index.cssx.styl'
16
23
 
@@ -45,6 +52,13 @@ export interface DivProps extends Omit<ViewProps, 'role'> {
45
52
  style?: StyleProp<ViewStyle>
46
53
  /** Content rendered inside Div */
47
54
  children?: ReactNode
55
+ /** Auto-wrap bare text children. When true, runs of consecutive string/number
56
+ * children (including those inside arrays/fragments) are each wrapped into a
57
+ * single text node so they render correctly in a non-text container. @default false */
58
+ supportTextNodes?: boolean
59
+ /** How to render an auto-wrapped text run (only used with supportTextNodes).
60
+ * Receives the merged text; should return a text element. Defaults to <Span/>. */
61
+ renderTextNode?: (text: string) => ReactNode
48
62
  /** Visual feedback variant @default 'opacity' */
49
63
  variant?: 'opacity' | 'highlight'
50
64
  /** Render children in a horizontal row */
@@ -98,8 +112,10 @@ export interface DivProps extends Omit<ViewProps, 'role'> {
98
112
  }
99
113
 
100
114
  function Div ({
101
- style = [],
115
+ style: rawStyle = [],
102
116
  children,
117
+ supportTextNodes = false,
118
+ renderTextNode,
103
119
  variant = 'opacity',
104
120
  row,
105
121
  wrap,
@@ -125,9 +141,20 @@ function Div ({
125
141
  ...props
126
142
  }: DivProps): ReactNode {
127
143
  assertDeprecatedValues({ pushed, renderTooltip })
128
- style = StyleSheet.flatten(style)
144
+ const renderedChildren = supportTextNodes ? wrapTextChildren(children, renderTextNode) : children
145
+ let style = StyleSheet.flatten(rawStyle) as ViewStyle | undefined
129
146
  // on RN row-reverse switches margins and paddings sides, so we switch them back
130
147
  if (isNative && reverse) style = reverseMarginPaddingSides(style)
148
+
149
+ const inheritedTextStyle = useContext(TextStyleContext)
150
+ const ownTextStyle = getInheritedTextStyle(style)
151
+ const nextInheritedTextStyleKey = simpleNumericHash(JSON.stringify([inheritedTextStyle, ownTextStyle]))
152
+ const nextInheritedTextStyle = useMemo(() => {
153
+ if (!ownTextStyle) return undefined
154
+ return mergeInheritedTextStyles(inheritedTextStyle, ownTextStyle)
155
+ }, [nextInheritedTextStyleKey]) // eslint-disable-line react-hooks/exhaustive-deps
156
+ omitInheritedTextStyle(style)
157
+
131
158
  if (gap === true) gap = 2
132
159
  const isPressable = hasPressHandler(props)
133
160
  const fallbackRef = useRef<any>(null)
@@ -214,15 +241,50 @@ function Div ({
214
241
  ]
215
242
  accessible=accessible
216
243
  ...renderProps
217
- )= children
244
+ )= renderedChildren
218
245
  `
246
+ const styledDivElement = nextInheritedTextStyle
247
+ ? pug`
248
+ TextStyleContext.Provider(value=nextInheritedTextStyle)
249
+ = divElement
250
+ `
251
+ : divElement
219
252
 
220
253
  if (tooltipElement) {
221
254
  return pug`
222
- = divElement
255
+ = styledDivElement
223
256
  = tooltipElement
224
257
  `
225
- } else return divElement
258
+ } else return styledDivElement
259
+ }
260
+
261
+ // Auto-wrap bare text so it renders in a non-text container. Each maximal run of
262
+ // consecutive string/number children is merged into one text node (so 'a {x} b'
263
+ // becomes one line, not three stacked nodes); element children break a run;
264
+ // fragments are traversed inline; arrays are already flattened by React.Children.
265
+ function wrapTextChildren (children: ReactNode, renderTextNode?: (text: string) => ReactNode): ReactNode {
266
+ const out: ReactNode[] = []
267
+ let run = ''
268
+ let key = 0
269
+ const flush = () => {
270
+ if (run === '') return
271
+ const text = run
272
+ run = ''
273
+ const node = renderTextNode ? renderTextNode(text) : createElement(Span, null, text)
274
+ out.push(isValidElement(node) ? cloneElement(node, { key: `__t${key++}` }) : node)
275
+ }
276
+ const walk = (nodes: ReactNode) => {
277
+ Children.forEach(nodes, child => {
278
+ if (child == null || typeof child === 'boolean') return
279
+ if (typeof child === 'string' || typeof child === 'number') { run += String(child); return }
280
+ if (isValidElement(child) && child.type === Fragment) { walk((child.props as { children?: ReactNode }).children); return }
281
+ flush()
282
+ out.push(isValidElement(child) && child.key == null ? cloneElement(child, { key: `__e${key++}` }) : child)
283
+ })
284
+ }
285
+ walk(children)
286
+ flush()
287
+ return out
226
288
  }
227
289
 
228
290
  function isWebOnlyRole (role: unknown): role is Exclude<UIRole, ViewProps['role']> {
@@ -436,8 +498,8 @@ function hasPressHandler (props: Record<string, any>): boolean {
436
498
  return PRESSABLE_PROPS.some(prop => props[prop])
437
499
  }
438
500
 
439
- function reverseMarginPaddingSides (style: StyleProp<ViewStyle>) {
440
- style = StyleSheet.flatten(style)
501
+ function reverseMarginPaddingSides (style: ViewStyle | undefined): ViewStyle | undefined {
502
+ if (!style) return style
441
503
  const { paddingLeft, paddingRight, marginLeft, marginRight } = style
442
504
  style.marginLeft = marginRight
443
505
  style.marginRight = marginLeft
@@ -446,6 +508,12 @@ function reverseMarginPaddingSides (style: StyleProp<ViewStyle>) {
446
508
  return style
447
509
  }
448
510
 
511
+ function simpleNumericHash (s: string): number {
512
+ let h = 0
513
+ for (let i = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0
514
+ return h
515
+ }
516
+
449
517
  function assertDeprecatedValues ({ pushed, renderTooltip }: { pushed?: any, renderTooltip?: any }) {
450
518
  if (DEPRECATED_PUSHED_VALUES.includes(pushed)) console.warn(ERRORS.DEPRECATED_PUSHED(pushed))
451
519
  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.3.0",
3
+ "version": "0.3.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -14,7 +14,7 @@
14
14
  "dependencies": {
15
15
  "@startupjs-ui/abstract-popover": "^0.3.0",
16
16
  "@startupjs-ui/core": "^0.3.0",
17
- "@startupjs-ui/span": "^0.3.0"
17
+ "@startupjs-ui/span": "^0.3.4"
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": "8d212b47680af1dfe582f9759b38724b46488e25"
25
+ "gitHead": "ad84ad14d189280a9ce4618b3223093fd09ba60e"
26
26
  }