@hzblj/kara-components 0.13.1 → 0.14.0
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 +22 -0
- package/lib/module/atoms/text/spans.js +44 -0
- package/lib/module/atoms/text/spans.js.map +1 -0
- package/lib/module/atoms/text/text.android.js +68 -3
- package/lib/module/atoms/text/text.android.js.map +1 -1
- package/lib/module/atoms/text/text.ios.js +15 -4
- package/lib/module/atoms/text/text.ios.js.map +1 -1
- package/lib/typescript/atoms/text/spans.d.ts +14 -0
- package/lib/typescript/atoms/text/text.types.d.ts +8 -1
- package/package.json +4 -4
- package/src/atoms/text/spans.ts +45 -0
- package/src/atoms/text/text.android.tsx +69 -3
- package/src/atoms/text/text.ios.tsx +36 -4
- package/src/atoms/text/text.types.ts +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @hzblj/kara-components
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#38](https://github.com/hzblj/kara-ui/pull/38) [`40ddb25`](https://github.com/hzblj/kara-ui/commit/40ddb254b85e95c4e86d79f5ce71c3c004598a59) Thanks [@hzblj](https://github.com/hzblj)! - `Text` takes spans and eases their colours. Nested `Text` children are runs of one paragraph, each in a `color` of its own (`<Text>Box, box. <Text color="content.accent">Box this lap</Text></Text>`), and with `animate` a colour that changes, the text's or a span's, eases to its new value on the motion tokens instead of switching: a word lit as it is said, a phrase brought forward. SwiftUI interpolates the text; on Android each span animates its colour natively. Colours keep easing with Reduce Motion on. Rebuild the native app.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`40ddb25`](https://github.com/hzblj/kara-ui/commit/40ddb254b85e95c4e86d79f5ce71c3c004598a59)]:
|
|
12
|
+
- @hzblj/kara-native@0.14.0
|
|
13
|
+
- @hzblj/kara-sx@0.14.0
|
|
14
|
+
- @hzblj/kara-theme@0.14.0
|
|
15
|
+
|
|
16
|
+
## 0.13.2
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [[`cef8ad3`](https://github.com/hzblj/kara-ui/commit/cef8ad3f70d2dbf598c31a91bc524dde8f85a761)]:
|
|
21
|
+
- @hzblj/kara-native@0.13.2
|
|
22
|
+
- @hzblj/kara-theme@0.13.2
|
|
23
|
+
- @hzblj/kara-sx@0.13.2
|
|
24
|
+
|
|
3
25
|
## 0.13.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Children, isValidElement } from 'react';
|
|
4
|
+
|
|
5
|
+
/** A run of the text: a string child, or a nested `Text` with a colour of its own. */
|
|
6
|
+
|
|
7
|
+
const textOf = children => Children.toArray(children).map(child => typeof child === 'string' || typeof child === 'number' ? String(child) : '').join('');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The children as runs when any of them is a nested `Text` (`type`, the platform's own component), `null` when they
|
|
11
|
+
* are only text. A span keeps its `color`; anything else it sets is the outer text's.
|
|
12
|
+
*/
|
|
13
|
+
export const spansOf = (children, type) => {
|
|
14
|
+
const items = Children.toArray(children);
|
|
15
|
+
if (!items.some(child => /*#__PURE__*/isValidElement(child) && child.type === type)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return items.flatMap(child => {
|
|
19
|
+
if (typeof child === 'string' || typeof child === 'number') {
|
|
20
|
+
return [{
|
|
21
|
+
text: String(child)
|
|
22
|
+
}];
|
|
23
|
+
}
|
|
24
|
+
if (/*#__PURE__*/isValidElement(child) && child.type === type) {
|
|
25
|
+
return [{
|
|
26
|
+
color: child.props.color,
|
|
27
|
+
text: textOf(child.props.children)
|
|
28
|
+
}];
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** The colours and the text folded into one number, for SwiftUI's `.animation(_:value:)`. A 32-bit FNV-1a hash. */
|
|
35
|
+
export const spansKey = (color, spans, text) => {
|
|
36
|
+
let hash = 0x811c9dc5;
|
|
37
|
+
const value = spans ? spans.map(span => `${span.color ?? ''}:${span.text}`).join('|') : text;
|
|
38
|
+
for (const character of `${color}|${value}`) {
|
|
39
|
+
hash ^= character.charCodeAt(0);
|
|
40
|
+
hash = Math.imul(hash, 0x01000193);
|
|
41
|
+
}
|
|
42
|
+
return hash >>> 0;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=spans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["Children","isValidElement","textOf","children","toArray","map","child","String","join","spansOf","type","items","some","flatMap","text","color","props","spansKey","spans","hash","value","span","character","charCodeAt","Math","imul"],"sourceRoot":"../../../../src","sources":["atoms/text/spans.ts"],"mappings":";;AACA,SAAQA,QAAQ,EAAEC,cAAc,QAAuB,OAAO;;AAE9D;;AAGA,MAAMC,MAAM,GAAIC,QAAmB,IACjCH,QAAQ,CAACI,OAAO,CAACD,QAAQ,CAAC,CACvBE,GAAG,CAACC,KAAK,IAAK,OAAOA,KAAK,KAAK,QAAQ,IAAI,OAAOA,KAAK,KAAK,QAAQ,GAAGC,MAAM,CAACD,KAAK,CAAC,GAAG,EAAG,CAAC,CAC3FE,IAAI,CAAC,EAAE,CAAC;;AAEb;AACA;AACA;AACA;AACA,OAAO,MAAMC,OAAO,GAAGA,CAACN,QAAmB,EAAEO,IAAa,KAAwB;EAChF,MAAMC,KAAK,GAAGX,QAAQ,CAACI,OAAO,CAACD,QAAQ,CAAC;EAExC,IAAI,CAACQ,KAAK,CAACC,IAAI,CAACN,KAAK,IAAI,aAAAL,cAAc,CAACK,KAAK,CAAC,IAAIA,KAAK,CAACI,IAAI,KAAKA,IAAI,CAAC,EAAE;IACtE,OAAO,IAAI;EACb;EAEA,OAAOC,KAAK,CAACE,OAAO,CAAWP,KAAK,IAAI;IACtC,IAAI,OAAOA,KAAK,KAAK,QAAQ,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;MAC1D,OAAO,CAAC;QAACQ,IAAI,EAAEP,MAAM,CAACD,KAAK;MAAC,CAAC,CAAC;IAChC;IACA,IAAI,aAAAL,cAAc,CAA6CK,KAAK,CAAC,IAAIA,KAAK,CAACI,IAAI,KAAKA,IAAI,EAAE;MAC5F,OAAO,CAAC;QAACK,KAAK,EAAET,KAAK,CAACU,KAAK,CAACD,KAAK;QAAED,IAAI,EAAEZ,MAAM,CAACI,KAAK,CAACU,KAAK,CAACb,QAAQ;MAAC,CAAC,CAAC;IACzE;IACA,OAAO,EAAE;EACX,CAAC,CAAC;AACJ,CAAC;;AAED;AACA,OAAO,MAAMc,QAAQ,GAAGA,CAACF,KAAiB,EAAEG,KAAiC,EAAEJ,IAAY,KAAa;EACtG,IAAIK,IAAI,GAAG,UAAU;EACrB,MAAMC,KAAK,GAAGF,KAAK,GAAGA,KAAK,CAACb,GAAG,CAACgB,IAAI,IAAI,GAAGA,IAAI,CAACN,KAAK,IAAI,EAAE,IAAIM,IAAI,CAACP,IAAI,EAAE,CAAC,CAACN,IAAI,CAAC,GAAG,CAAC,GAAGM,IAAI;EAE5F,KAAK,MAAMQ,SAAS,IAAI,GAAGP,KAAK,IAAIK,KAAK,EAAE,EAAE;IAC3CD,IAAI,IAAIG,SAAS,CAACC,UAAU,CAAC,CAAC,CAAC;IAC/BJ,IAAI,GAAGK,IAAI,CAACC,IAAI,CAACN,IAAI,EAAE,UAAU,CAAC;EACpC;EAEA,OAAOA,IAAI,KAAK,CAAC;AACnB,CAAC","ignoreList":[]}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { Text as ComposeText } from '@expo/ui/jetpack-compose';
|
|
4
4
|
import { testID as testIdModifier } from '@expo/ui/jetpack-compose/modifiers';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { AnimatedText } from '@hzblj/kara-native/jetpack-compose';
|
|
6
|
+
import { animateConfig, splitAccessibility, useAccessibilityModifiers, useSxModifiers } from '@hzblj/kara-sx';
|
|
7
|
+
import { duration, easing, springPhysics, useAppTheme } from '@hzblj/kara-theme';
|
|
7
8
|
import { useMemo } from 'react';
|
|
8
9
|
import { useHostGuard } from "../../host-context.js";
|
|
10
|
+
import { spansOf } from "./spans.js";
|
|
9
11
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
12
|
const DECORATION = {
|
|
11
13
|
none: 'none',
|
|
@@ -19,6 +21,41 @@ const applyTransform = (children, transform) => {
|
|
|
19
21
|
return transform === 'uppercase' ? children.toUpperCase() : children.toLowerCase();
|
|
20
22
|
};
|
|
21
23
|
const decorationOf = decoration => decoration === undefined ? undefined : DECORATION[decoration];
|
|
24
|
+
const transformed = (text, transform) => transform === 'uppercase' ? text.toUpperCase() : transform === 'lowercase' ? text.toLowerCase() : text;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `animate` for the spans' colours, as `@hzblj/kara-sx` animates the rest on Compose: a spring token's physics, and
|
|
28
|
+
* `smoothOut` as a no-bounce spring over its duration; the other curves as tweens along their béziers.
|
|
29
|
+
*/
|
|
30
|
+
const textAnimation = config => {
|
|
31
|
+
if (config.spring) {
|
|
32
|
+
const {
|
|
33
|
+
dampingRatio,
|
|
34
|
+
stiffness
|
|
35
|
+
} = springPhysics(config.spring);
|
|
36
|
+
return {
|
|
37
|
+
dampingRatio,
|
|
38
|
+
stiffness,
|
|
39
|
+
type: 'spring'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const milliseconds = duration[config.duration ?? 'fast'];
|
|
43
|
+
const curve = config.easing ?? 'smoothOut';
|
|
44
|
+
if (curve === 'smoothOut') {
|
|
45
|
+
const omega = 2 * Math.PI * 1000 / milliseconds;
|
|
46
|
+
return {
|
|
47
|
+
dampingRatio: 1,
|
|
48
|
+
stiffness: omega * omega,
|
|
49
|
+
type: 'spring'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
delayMillis: config.delay,
|
|
54
|
+
durationMillis: milliseconds,
|
|
55
|
+
easing: easing[curve],
|
|
56
|
+
type: 'tween'
|
|
57
|
+
};
|
|
58
|
+
};
|
|
22
59
|
export const Text = ({
|
|
23
60
|
align,
|
|
24
61
|
children,
|
|
@@ -37,6 +74,31 @@ export const Text = ({
|
|
|
37
74
|
const sxModifiers = useSxModifiers(sx);
|
|
38
75
|
const accessibilityModifiers = useAccessibilityModifiers(accessibility);
|
|
39
76
|
const modifiers = useMemo(() => [...sxModifiers, ...(testID ? [testIdModifier(testID)] : []), ...accessibilityModifiers], [accessibilityModifiers, sxModifiers, testID]);
|
|
77
|
+
const spans = useMemo(() => spansOf(children, Text), [children]);
|
|
78
|
+
const animate = sx.animate;
|
|
79
|
+
|
|
80
|
+
// `@expo/ui`'s text takes spans but animates none: with `animate`, kara's own Compose text eases each span's colour.
|
|
81
|
+
if (animate !== undefined && animate !== false) {
|
|
82
|
+
const runs = spans ?? [{
|
|
83
|
+
text: typeof children === 'string' || typeof children === 'number' ? String(children) : ''
|
|
84
|
+
}];
|
|
85
|
+
return /*#__PURE__*/_jsx(AnimatedText, {
|
|
86
|
+
animation: textAnimation(animateConfig(animate)),
|
|
87
|
+
fontFamily: style.fontFamily,
|
|
88
|
+
fontSize: style.fontSize,
|
|
89
|
+
fontWeight: style.fontWeight,
|
|
90
|
+
lineHeight: style.lineHeight,
|
|
91
|
+
maxLines: numberOfLines,
|
|
92
|
+
modifiers: modifiers,
|
|
93
|
+
spans: runs.map(span => ({
|
|
94
|
+
color: span.color ? theme.colorString(span.color) : undefined,
|
|
95
|
+
text: transformed(span.text, transform)
|
|
96
|
+
})),
|
|
97
|
+
textAlign: align,
|
|
98
|
+
textColor: theme.colorString(color),
|
|
99
|
+
textDecoration: decorationOf(decoration)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
40
102
|
return /*#__PURE__*/_jsx(ComposeText, {
|
|
41
103
|
color: theme.colorString(color),
|
|
42
104
|
maxLines: numberOfLines,
|
|
@@ -49,7 +111,10 @@ export const Text = ({
|
|
|
49
111
|
textAlign: align,
|
|
50
112
|
textDecoration: decorationOf(decoration)
|
|
51
113
|
},
|
|
52
|
-
children:
|
|
114
|
+
children: spans ? spans.map((span, index) => /*#__PURE__*/_jsx(ComposeText, {
|
|
115
|
+
color: span.color ? theme.colorString(span.color) : undefined,
|
|
116
|
+
children: transformed(span.text, transform)
|
|
117
|
+
}, index)) : applyTransform(children, transform)
|
|
53
118
|
});
|
|
54
119
|
};
|
|
55
120
|
//# sourceMappingURL=text.android.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Text","ComposeText","testID","testIdModifier","splitAccessibility","useAccessibilityModifiers","useSxModifiers","useAppTheme","useMemo","useHostGuard","jsx","_jsx","DECORATION","none","strikethrough","underline","applyTransform","children","transform","toUpperCase","toLowerCase","decorationOf","decoration","undefined","align","color","numberOfLines","variant","props","theme","style","typography","accessibility","sx","sxModifiers","accessibilityModifiers","modifiers","
|
|
1
|
+
{"version":3,"names":["Text","ComposeText","testID","testIdModifier","AnimatedText","animateConfig","splitAccessibility","useAccessibilityModifiers","useSxModifiers","duration","easing","springPhysics","useAppTheme","useMemo","useHostGuard","spansOf","jsx","_jsx","DECORATION","none","strikethrough","underline","applyTransform","children","transform","toUpperCase","toLowerCase","decorationOf","decoration","undefined","transformed","text","textAnimation","config","spring","dampingRatio","stiffness","type","milliseconds","curve","omega","Math","PI","delayMillis","delay","durationMillis","align","color","numberOfLines","variant","props","theme","style","typography","accessibility","sx","sxModifiers","accessibilityModifiers","modifiers","spans","animate","runs","String","animation","fontFamily","fontSize","fontWeight","lineHeight","maxLines","map","span","colorString","textAlign","textColor","textDecoration","index"],"sourceRoot":"../../../../src","sources":["atoms/text/text.android.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import { Text as SwiftUIText } from '@expo/ui/swift-ui';
|
|
4
4
|
import { foregroundStyle, lineLimit, lineSpacing, multilineTextAlignment, strikethrough, textCase, underline } from '@expo/ui/swift-ui/modifiers';
|
|
5
5
|
import { scaledFont } from '@hzblj/kara-native/swift-ui';
|
|
6
|
-
import { splitAccessibility, useAccessibilityModifiers, useSxModifiers } from '@hzblj/kara-sx';
|
|
6
|
+
import { splitAccessibility, textColorTransition, useAccessibilityModifiers, useSxModifiers } from '@hzblj/kara-sx';
|
|
7
7
|
import { useAppTheme } from '@hzblj/kara-theme';
|
|
8
8
|
import { useMemo } from 'react';
|
|
9
9
|
import { useHostGuard } from "../../host-context.js";
|
|
10
|
+
import { spansKey, spansOf } from "./spans.js";
|
|
10
11
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
12
|
const ALIGNMENT = {
|
|
12
13
|
center: 'center',
|
|
@@ -38,6 +39,10 @@ export const Text = ({
|
|
|
38
39
|
const [accessibility, sx] = splitAccessibility(props);
|
|
39
40
|
const sxModifiers = useSxModifiers(sx, FRAME_ALIGNMENT[align ?? 'left']);
|
|
40
41
|
const accessibilityModifiers = useAccessibilityModifiers(accessibility);
|
|
42
|
+
const spans = useMemo(() => spansOf(children, Text), [children]);
|
|
43
|
+
const animate = sx.animate;
|
|
44
|
+
// With `animate`, SwiftUI interpolates the text when a colour changes: the text's own or a span's.
|
|
45
|
+
const transition = useMemo(() => textColorTransition(animate, spansKey(color, spans, typeof children === 'string' ? children : '')), [animate, children, color, spans]);
|
|
41
46
|
const modifiers = useMemo(() => {
|
|
42
47
|
const list = [
|
|
43
48
|
// Not `@expo/ui`'s `font()`: for the platform's face it gives either Apple's size or a size that ignores Dynamic
|
|
@@ -69,12 +74,18 @@ export const Text = ({
|
|
|
69
74
|
if (numberOfLines !== undefined) {
|
|
70
75
|
list.push(lineLimit(numberOfLines));
|
|
71
76
|
}
|
|
72
|
-
return [...list, ...sxModifiers, ...accessibilityModifiers];
|
|
73
|
-
}, [accessibilityModifiers, align, color, decoration, numberOfLines, style, sxModifiers, theme, transform]);
|
|
77
|
+
return [...list, ...transition, ...sxModifiers, ...accessibilityModifiers];
|
|
78
|
+
}, [accessibilityModifiers, align, color, decoration, numberOfLines, style, sxModifiers, theme, transform, transition]);
|
|
74
79
|
return /*#__PURE__*/_jsx(SwiftUIText, {
|
|
75
80
|
modifiers: modifiers,
|
|
76
81
|
testID: testID,
|
|
77
|
-
children:
|
|
82
|
+
children: spans ? spans.map((span, index) =>
|
|
83
|
+
/*#__PURE__*/
|
|
84
|
+
// Concatenated into one SwiftUI `Text`: a span takes its colour, the font and the rest are the outer text's.
|
|
85
|
+
_jsx(SwiftUIText, {
|
|
86
|
+
modifiers: span.color ? [foregroundStyle(theme.color(span.color))] : undefined,
|
|
87
|
+
children: span.text
|
|
88
|
+
}, index)) : children
|
|
78
89
|
});
|
|
79
90
|
};
|
|
80
91
|
//# sourceMappingURL=text.ios.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Text","SwiftUIText","foregroundStyle","lineLimit","lineSpacing","multilineTextAlignment","strikethrough","textCase","underline","scaledFont","splitAccessibility","useAccessibilityModifiers","useSxModifiers","useAppTheme","useMemo","useHostGuard","jsx","_jsx","ALIGNMENT","center","left","right","FRAME_ALIGNMENT","align","children","color","decoration","numberOfLines","testID","transform","variant","props","theme","style","typography","accessibility","sx","sxModifiers","accessibilityModifiers","modifiers","list","family","fontFamily","size","fontSize","textStyle","scalesWith","weight","Math","max","lineHeight","push","isActive","pattern","undefined"],"sourceRoot":"../../../../src","sources":["atoms/text/text.ios.tsx"],"mappings":";;AAAA,SAAQA,IAAI,IAAIC,WAAW,QAAO,mBAAmB;AACrD,SACEC,eAAe,EACfC,SAAS,EACTC,WAAW,EAEXC,sBAAsB,EACtBC,aAAa,EACbC,QAAQ,EACRC,SAAS,QACJ,6BAA6B;AACpC,SAAQC,UAAU,QAAO,6BAA6B;AACtD,
|
|
1
|
+
{"version":3,"names":["Text","SwiftUIText","foregroundStyle","lineLimit","lineSpacing","multilineTextAlignment","strikethrough","textCase","underline","scaledFont","splitAccessibility","textColorTransition","useAccessibilityModifiers","useSxModifiers","useAppTheme","useMemo","useHostGuard","spansKey","spansOf","jsx","_jsx","ALIGNMENT","center","left","right","FRAME_ALIGNMENT","align","children","color","decoration","numberOfLines","testID","transform","variant","props","theme","style","typography","accessibility","sx","sxModifiers","accessibilityModifiers","spans","animate","transition","modifiers","list","family","fontFamily","size","fontSize","textStyle","scalesWith","weight","Math","max","lineHeight","push","isActive","pattern","undefined","map","span","index","text"],"sourceRoot":"../../../../src","sources":["atoms/text/text.ios.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ColorToken } from '@hzblj/kara-theme';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
/** A run of the text: a string child, or a nested `Text` with a colour of its own. */
|
|
4
|
+
export type TextSpan = {
|
|
5
|
+
color?: ColorToken;
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The children as runs when any of them is a nested `Text` (`type`, the platform's own component), `null` when they
|
|
10
|
+
* are only text. A span keeps its `color`; anything else it sets is the outer text's.
|
|
11
|
+
*/
|
|
12
|
+
export declare const spansOf: (children: ReactNode, type: unknown) => TextSpan[] | null;
|
|
13
|
+
/** The colours and the text folded into one number, for SwiftUI's `.animation(_:value:)`. A 32-bit FNV-1a hash. */
|
|
14
|
+
export declare const spansKey: (color: ColorToken, spans: readonly TextSpan[] | null, text: string) => number;
|
|
@@ -8,6 +8,10 @@ export type TextTransform = 'lowercase' | 'uppercase';
|
|
|
8
8
|
* SwiftUI `Text` / Compose `Text` in a theme typography variant. A native atom: outside a host, use `RnText`. It takes
|
|
9
9
|
* the native `sx` surface too: padding, a frame (`wide` lines it up by `align`), a background, and the `ios` bucket's
|
|
10
10
|
* `contentTransition`, `animation` and `monospacedDigit` for a number that changes in place.
|
|
11
|
+
*
|
|
12
|
+
* Nested `Text` children are spans: runs of the same text in a colour of their own, one paragraph that wraps as one.
|
|
13
|
+
* With `animate`, a colour that changes (the text's or a span's) eases to its new value instead of switching: a word lit
|
|
14
|
+
* as it is said, a phrase brought forward. SwiftUI interpolates the text, Compose animates each span's colour.
|
|
11
15
|
*/
|
|
12
16
|
export type TextProps = ResponsiveSx & AccessibilityProps & {
|
|
13
17
|
/**
|
|
@@ -15,7 +19,10 @@ export type TextProps = ResponsiveSx & AccessibilityProps & {
|
|
|
15
19
|
* layout; Android keeps them as they are.
|
|
16
20
|
*/
|
|
17
21
|
align?: TextAlign;
|
|
18
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The text: a string, or strings and nested `Text` spans, each in a `color` of its own. A span takes only its
|
|
24
|
+
* colour; the variant, alignment and everything else are the outer text's.
|
|
25
|
+
*/
|
|
19
26
|
children?: ReactNode;
|
|
20
27
|
/** The text's colour. `content.primary` by default. */
|
|
21
28
|
color?: ColorToken;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hzblj/kara-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "The atoms, molecules, organisms and templates of @hzblj/kara-ui — real SwiftUI and Jetpack Compose views through @expo/ui.",
|
|
5
5
|
"author": "Jan Blazej",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@hzblj/expo-native-icons": "^0.1.0",
|
|
63
63
|
"@hzblj/expo-rich-haptics": "^0.1.0",
|
|
64
|
-
"@hzblj/kara-native": "^0.
|
|
65
|
-
"@hzblj/kara-sx": "^0.
|
|
66
|
-
"@hzblj/kara-theme": "^0.
|
|
64
|
+
"@hzblj/kara-native": "^0.14.0",
|
|
65
|
+
"@hzblj/kara-sx": "^0.14.0",
|
|
66
|
+
"@hzblj/kara-theme": "^0.14.0",
|
|
67
67
|
"sf-symbols-typescript": "^2.2.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {ColorToken} from '@hzblj/kara-theme'
|
|
2
|
+
import {Children, isValidElement, type ReactNode} from 'react'
|
|
3
|
+
|
|
4
|
+
/** A run of the text: a string child, or a nested `Text` with a colour of its own. */
|
|
5
|
+
export type TextSpan = {color?: ColorToken; text: string}
|
|
6
|
+
|
|
7
|
+
const textOf = (children: ReactNode): string =>
|
|
8
|
+
Children.toArray(children)
|
|
9
|
+
.map(child => (typeof child === 'string' || typeof child === 'number' ? String(child) : ''))
|
|
10
|
+
.join('')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The children as runs when any of them is a nested `Text` (`type`, the platform's own component), `null` when they
|
|
14
|
+
* are only text. A span keeps its `color`; anything else it sets is the outer text's.
|
|
15
|
+
*/
|
|
16
|
+
export const spansOf = (children: ReactNode, type: unknown): TextSpan[] | null => {
|
|
17
|
+
const items = Children.toArray(children)
|
|
18
|
+
|
|
19
|
+
if (!items.some(child => isValidElement(child) && child.type === type)) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return items.flatMap<TextSpan>(child => {
|
|
24
|
+
if (typeof child === 'string' || typeof child === 'number') {
|
|
25
|
+
return [{text: String(child)}]
|
|
26
|
+
}
|
|
27
|
+
if (isValidElement<{children?: ReactNode; color?: ColorToken}>(child) && child.type === type) {
|
|
28
|
+
return [{color: child.props.color, text: textOf(child.props.children)}]
|
|
29
|
+
}
|
|
30
|
+
return []
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The colours and the text folded into one number, for SwiftUI's `.animation(_:value:)`. A 32-bit FNV-1a hash. */
|
|
35
|
+
export const spansKey = (color: ColorToken, spans: readonly TextSpan[] | null, text: string): number => {
|
|
36
|
+
let hash = 0x811c9dc5
|
|
37
|
+
const value = spans ? spans.map(span => `${span.color ?? ''}:${span.text}`).join('|') : text
|
|
38
|
+
|
|
39
|
+
for (const character of `${color}|${value}`) {
|
|
40
|
+
hash ^= character.charCodeAt(0)
|
|
41
|
+
hash = Math.imul(hash, 0x01000193)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return hash >>> 0
|
|
45
|
+
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {Text as ComposeText} from '@expo/ui/jetpack-compose'
|
|
2
2
|
import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import {AnimatedText, type TextAnimation} from '@hzblj/kara-native/jetpack-compose'
|
|
4
|
+
import {
|
|
5
|
+
animateConfig,
|
|
6
|
+
type SxAnimateConfig,
|
|
7
|
+
splitAccessibility,
|
|
8
|
+
useAccessibilityModifiers,
|
|
9
|
+
useSxModifiers,
|
|
10
|
+
} from '@hzblj/kara-sx'
|
|
11
|
+
import {duration, easing, springPhysics, useAppTheme} from '@hzblj/kara-theme'
|
|
5
12
|
import {useMemo} from 'react'
|
|
6
13
|
|
|
7
14
|
import {useHostGuard} from '../../host-context'
|
|
15
|
+
import {spansOf} from './spans'
|
|
8
16
|
import type {TextComponent, TextDecoration, TextProps, TextTransform} from './text.types'
|
|
9
17
|
|
|
10
18
|
const DECORATION = {
|
|
@@ -24,6 +32,32 @@ const applyTransform = (children: TextProps['children'], transform: TextTransfor
|
|
|
24
32
|
const decorationOf = (decoration: TextDecoration | undefined) =>
|
|
25
33
|
decoration === undefined ? undefined : DECORATION[decoration]
|
|
26
34
|
|
|
35
|
+
const transformed = (text: string, transform: TextTransform | undefined) =>
|
|
36
|
+
transform === 'uppercase' ? text.toUpperCase() : transform === 'lowercase' ? text.toLowerCase() : text
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `animate` for the spans' colours, as `@hzblj/kara-sx` animates the rest on Compose: a spring token's physics, and
|
|
40
|
+
* `smoothOut` as a no-bounce spring over its duration; the other curves as tweens along their béziers.
|
|
41
|
+
*/
|
|
42
|
+
const textAnimation = (config: SxAnimateConfig): TextAnimation => {
|
|
43
|
+
if (config.spring) {
|
|
44
|
+
const {dampingRatio, stiffness} = springPhysics(config.spring)
|
|
45
|
+
|
|
46
|
+
return {dampingRatio, stiffness, type: 'spring'}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const milliseconds = duration[config.duration ?? 'fast']
|
|
50
|
+
const curve = config.easing ?? 'smoothOut'
|
|
51
|
+
|
|
52
|
+
if (curve === 'smoothOut') {
|
|
53
|
+
const omega = (2 * Math.PI * 1000) / milliseconds
|
|
54
|
+
|
|
55
|
+
return {dampingRatio: 1, stiffness: omega * omega, type: 'spring'}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {delayMillis: config.delay, durationMillis: milliseconds, easing: easing[curve], type: 'tween'}
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
export const Text: TextComponent = ({
|
|
28
62
|
align,
|
|
29
63
|
children,
|
|
@@ -45,6 +79,32 @@ export const Text: TextComponent = ({
|
|
|
45
79
|
() => [...sxModifiers, ...(testID ? [testIdModifier(testID)] : []), ...accessibilityModifiers],
|
|
46
80
|
[accessibilityModifiers, sxModifiers, testID]
|
|
47
81
|
)
|
|
82
|
+
const spans = useMemo(() => spansOf(children, Text), [children])
|
|
83
|
+
const animate = sx.animate
|
|
84
|
+
|
|
85
|
+
// `@expo/ui`'s text takes spans but animates none: with `animate`, kara's own Compose text eases each span's colour.
|
|
86
|
+
if (animate !== undefined && animate !== false) {
|
|
87
|
+
const runs = spans ?? [{text: typeof children === 'string' || typeof children === 'number' ? String(children) : ''}]
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<AnimatedText
|
|
91
|
+
animation={textAnimation(animateConfig(animate))}
|
|
92
|
+
fontFamily={style.fontFamily}
|
|
93
|
+
fontSize={style.fontSize}
|
|
94
|
+
fontWeight={style.fontWeight}
|
|
95
|
+
lineHeight={style.lineHeight}
|
|
96
|
+
maxLines={numberOfLines}
|
|
97
|
+
modifiers={modifiers}
|
|
98
|
+
spans={runs.map(span => ({
|
|
99
|
+
color: span.color ? theme.colorString(span.color) : undefined,
|
|
100
|
+
text: transformed(span.text, transform),
|
|
101
|
+
}))}
|
|
102
|
+
textAlign={align}
|
|
103
|
+
textColor={theme.colorString(color)}
|
|
104
|
+
textDecoration={decorationOf(decoration)}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
48
108
|
|
|
49
109
|
return (
|
|
50
110
|
<ComposeText
|
|
@@ -60,7 +120,13 @@ export const Text: TextComponent = ({
|
|
|
60
120
|
textDecoration: decorationOf(decoration),
|
|
61
121
|
}}
|
|
62
122
|
>
|
|
63
|
-
{
|
|
123
|
+
{spans
|
|
124
|
+
? spans.map((span, index) => (
|
|
125
|
+
<ComposeText color={span.color ? theme.colorString(span.color) : undefined} key={index}>
|
|
126
|
+
{transformed(span.text, transform)}
|
|
127
|
+
</ComposeText>
|
|
128
|
+
))
|
|
129
|
+
: applyTransform(children, transform)}
|
|
64
130
|
</ComposeText>
|
|
65
131
|
)
|
|
66
132
|
}
|
|
@@ -10,11 +10,18 @@ import {
|
|
|
10
10
|
underline,
|
|
11
11
|
} from '@expo/ui/swift-ui/modifiers'
|
|
12
12
|
import {scaledFont} from '@hzblj/kara-native/swift-ui'
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type FrameAlignment,
|
|
15
|
+
splitAccessibility,
|
|
16
|
+
textColorTransition,
|
|
17
|
+
useAccessibilityModifiers,
|
|
18
|
+
useSxModifiers,
|
|
19
|
+
} from '@hzblj/kara-sx'
|
|
14
20
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
15
21
|
import {useMemo} from 'react'
|
|
16
22
|
|
|
17
23
|
import {useHostGuard} from '../../host-context'
|
|
24
|
+
import {spansKey, spansOf} from './spans'
|
|
18
25
|
import type {TextAlign, TextComponent, TextProps} from './text.types'
|
|
19
26
|
|
|
20
27
|
const ALIGNMENT = {
|
|
@@ -48,6 +55,13 @@ export const Text: TextComponent = ({
|
|
|
48
55
|
const [accessibility, sx] = splitAccessibility(props)
|
|
49
56
|
const sxModifiers = useSxModifiers(sx, FRAME_ALIGNMENT[align ?? 'left'])
|
|
50
57
|
const accessibilityModifiers = useAccessibilityModifiers(accessibility)
|
|
58
|
+
const spans = useMemo(() => spansOf(children, Text), [children])
|
|
59
|
+
const animate = sx.animate
|
|
60
|
+
// With `animate`, SwiftUI interpolates the text when a colour changes: the text's own or a span's.
|
|
61
|
+
const transition = useMemo(
|
|
62
|
+
() => textColorTransition(animate, spansKey(color, spans, typeof children === 'string' ? children : '')),
|
|
63
|
+
[animate, children, color, spans]
|
|
64
|
+
)
|
|
51
65
|
|
|
52
66
|
const modifiers = useMemo(() => {
|
|
53
67
|
const list: ModifierConfig[] = [
|
|
@@ -78,12 +92,30 @@ export const Text: TextComponent = ({
|
|
|
78
92
|
list.push(lineLimit(numberOfLines))
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
return [...list, ...sxModifiers, ...accessibilityModifiers]
|
|
82
|
-
}, [
|
|
95
|
+
return [...list, ...transition, ...sxModifiers, ...accessibilityModifiers]
|
|
96
|
+
}, [
|
|
97
|
+
accessibilityModifiers,
|
|
98
|
+
align,
|
|
99
|
+
color,
|
|
100
|
+
decoration,
|
|
101
|
+
numberOfLines,
|
|
102
|
+
style,
|
|
103
|
+
sxModifiers,
|
|
104
|
+
theme,
|
|
105
|
+
transform,
|
|
106
|
+
transition,
|
|
107
|
+
])
|
|
83
108
|
|
|
84
109
|
return (
|
|
85
110
|
<SwiftUIText modifiers={modifiers} testID={testID}>
|
|
86
|
-
{
|
|
111
|
+
{spans
|
|
112
|
+
? spans.map((span, index) => (
|
|
113
|
+
// Concatenated into one SwiftUI `Text`: a span takes its colour, the font and the rest are the outer text's.
|
|
114
|
+
<SwiftUIText key={index} modifiers={span.color ? [foregroundStyle(theme.color(span.color))] : undefined}>
|
|
115
|
+
{span.text}
|
|
116
|
+
</SwiftUIText>
|
|
117
|
+
))
|
|
118
|
+
: children}
|
|
87
119
|
</SwiftUIText>
|
|
88
120
|
)
|
|
89
121
|
}
|
|
@@ -12,6 +12,10 @@ export type TextTransform = 'lowercase' | 'uppercase'
|
|
|
12
12
|
* SwiftUI `Text` / Compose `Text` in a theme typography variant. A native atom: outside a host, use `RnText`. It takes
|
|
13
13
|
* the native `sx` surface too: padding, a frame (`wide` lines it up by `align`), a background, and the `ios` bucket's
|
|
14
14
|
* `contentTransition`, `animation` and `monospacedDigit` for a number that changes in place.
|
|
15
|
+
*
|
|
16
|
+
* Nested `Text` children are spans: runs of the same text in a colour of their own, one paragraph that wraps as one.
|
|
17
|
+
* With `animate`, a colour that changes (the text's or a span's) eases to its new value instead of switching: a word lit
|
|
18
|
+
* as it is said, a phrase brought forward. SwiftUI interpolates the text, Compose animates each span's colour.
|
|
15
19
|
*/
|
|
16
20
|
export type TextProps = ResponsiveSx &
|
|
17
21
|
AccessibilityProps & {
|
|
@@ -20,7 +24,10 @@ export type TextProps = ResponsiveSx &
|
|
|
20
24
|
* layout; Android keeps them as they are.
|
|
21
25
|
*/
|
|
22
26
|
align?: TextAlign
|
|
23
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* The text: a string, or strings and nested `Text` spans, each in a `color` of its own. A span takes only its
|
|
29
|
+
* colour; the variant, alignment and everything else are the outer text's.
|
|
30
|
+
*/
|
|
24
31
|
children?: ReactNode
|
|
25
32
|
/** The text's colour. `content.primary` by default. */
|
|
26
33
|
color?: ColorToken
|