react-native-laminar 1.2.0 → 1.4.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/README.md +9 -2
- package/lib/commonjs/hooks/use-display-units.js +1 -0
- package/lib/commonjs/hooks/use-display-units.js.map +1 -1
- package/lib/commonjs/hooks/use-inline-auto-width.js +43 -10
- package/lib/commonjs/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/commonjs/hooks/use-morph-motion.js +2 -1
- package/lib/commonjs/hooks/use-morph-motion.js.map +1 -1
- package/lib/commonjs/hooks/use-morph-text-style.js +4 -0
- package/lib/commonjs/hooks/use-morph-text-style.js.map +1 -1
- package/lib/commonjs/hooks/use-numeric-lanes.js +1 -0
- package/lib/commonjs/hooks/use-numeric-lanes.js.map +1 -1
- package/lib/commonjs/hooks/use-text-glyphs.js +28 -7
- package/lib/commonjs/hooks/use-text-glyphs.js.map +1 -1
- package/lib/commonjs/index.js +54 -11
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/model/display-units.js +7 -1
- package/lib/commonjs/model/display-units.js.map +1 -1
- package/lib/commonjs/model/numeric-lanes.js +4 -0
- package/lib/commonjs/model/numeric-lanes.js.map +1 -1
- package/lib/commonjs/model/text-keys.js +4 -0
- package/lib/commonjs/model/text-keys.js.map +1 -1
- package/lib/commonjs/motion/entry-exit-builders.js +78 -1
- package/lib/commonjs/motion/entry-exit-builders.js.map +1 -1
- package/lib/commonjs/motion/preset-map.js +7 -0
- package/lib/commonjs/motion/preset-map.js.map +1 -1
- package/lib/commonjs/view/glyph-run.js +21 -2
- package/lib/commonjs/view/glyph-run.js.map +1 -1
- package/lib/commonjs/view/morph-viewport.js +4 -0
- package/lib/commonjs/view/morph-viewport.js.map +1 -1
- package/lib/commonjs/view/number-lane.js +5 -1
- package/lib/commonjs/view/number-lane.js.map +1 -1
- package/lib/commonjs/view/number-run.js +4 -0
- package/lib/commonjs/view/number-run.js.map +1 -1
- package/lib/commonjs/view/slots-run.js +258 -0
- package/lib/commonjs/view/slots-run.js.map +1 -0
- package/lib/commonjs/view/text-run.js +35 -3
- package/lib/commonjs/view/text-run.js.map +1 -1
- package/lib/module/hooks/use-display-units.js +2 -0
- package/lib/module/hooks/use-display-units.js.map +1 -1
- package/lib/module/hooks/use-inline-auto-width.js +44 -11
- package/lib/module/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/module/hooks/use-morph-motion.js +2 -1
- package/lib/module/hooks/use-morph-motion.js.map +1 -1
- package/lib/module/hooks/use-morph-text-style.js +4 -0
- package/lib/module/hooks/use-morph-text-style.js.map +1 -1
- package/lib/module/hooks/use-numeric-lanes.js +1 -0
- package/lib/module/hooks/use-numeric-lanes.js.map +1 -1
- package/lib/module/hooks/use-text-glyphs.js +28 -7
- package/lib/module/hooks/use-text-glyphs.js.map +1 -1
- package/lib/module/index.js +56 -12
- package/lib/module/index.js.map +1 -1
- package/lib/module/model/display-units.js +7 -1
- package/lib/module/model/display-units.js.map +1 -1
- package/lib/module/model/numeric-lanes.js +5 -0
- package/lib/module/model/numeric-lanes.js.map +1 -1
- package/lib/module/model/text-keys.js +4 -0
- package/lib/module/model/text-keys.js.map +1 -1
- package/lib/module/motion/entry-exit-builders.js +75 -0
- package/lib/module/motion/entry-exit-builders.js.map +1 -1
- package/lib/module/motion/preset-map.js +7 -0
- package/lib/module/motion/preset-map.js.map +1 -1
- package/lib/module/view/glyph-run.js +21 -2
- package/lib/module/view/glyph-run.js.map +1 -1
- package/lib/module/view/morph-viewport.js +4 -0
- package/lib/module/view/morph-viewport.js.map +1 -1
- package/lib/module/view/number-lane.js +5 -1
- package/lib/module/view/number-lane.js.map +1 -1
- package/lib/module/view/number-run.js +4 -0
- package/lib/module/view/number-run.js.map +1 -1
- package/lib/module/view/slots-run.js +253 -0
- package/lib/module/view/slots-run.js.map +1 -0
- package/lib/module/view/text-run.js +36 -4
- package/lib/module/view/text-run.js.map +1 -1
- package/lib/typescript/commonjs/hooks/use-display-units.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts +3 -1
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-morph-text-style.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-text-glyphs.d.ts +2 -1
- package/lib/typescript/commonjs/hooks/use-text-glyphs.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/model/display-units.d.ts.map +1 -1
- package/lib/typescript/commonjs/model/numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/commonjs/model/text-keys.d.ts.map +1 -1
- package/lib/typescript/commonjs/motion/entry-exit-builders.d.ts +13 -0
- package/lib/typescript/commonjs/motion/entry-exit-builders.d.ts.map +1 -1
- package/lib/typescript/commonjs/motion/preset-map.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +12 -2
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/glyph-run.d.ts +5 -2
- package/lib/typescript/commonjs/view/glyph-run.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/number-lane.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/number-run.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/slots-run.d.ts +15 -0
- package/lib/typescript/commonjs/view/slots-run.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/text-run.d.ts +5 -1
- package/lib/typescript/commonjs/view/text-run.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-display-units.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts +3 -1
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-morph-motion.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-morph-text-style.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-text-glyphs.d.ts +2 -1
- package/lib/typescript/module/hooks/use-text-glyphs.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/model/display-units.d.ts.map +1 -1
- package/lib/typescript/module/model/numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/module/model/text-keys.d.ts.map +1 -1
- package/lib/typescript/module/motion/entry-exit-builders.d.ts +13 -0
- package/lib/typescript/module/motion/entry-exit-builders.d.ts.map +1 -1
- package/lib/typescript/module/motion/preset-map.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +12 -2
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/view/glyph-run.d.ts +5 -2
- package/lib/typescript/module/view/glyph-run.d.ts.map +1 -1
- package/lib/typescript/module/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/module/view/number-lane.d.ts.map +1 -1
- package/lib/typescript/module/view/number-run.d.ts.map +1 -1
- package/lib/typescript/module/view/slots-run.d.ts +15 -0
- package/lib/typescript/module/view/slots-run.d.ts.map +1 -0
- package/lib/typescript/module/view/text-run.d.ts +5 -1
- package/lib/typescript/module/view/text-run.d.ts.map +1 -1
- package/package.json +11 -2
- package/src/hooks/use-display-units.ts +1 -0
- package/src/hooks/use-inline-auto-width.ts +55 -12
- package/src/hooks/use-morph-motion.ts +2 -1
- package/src/hooks/use-morph-text-style.ts +3 -0
- package/src/hooks/use-numeric-lanes.ts +1 -0
- package/src/hooks/use-text-glyphs.ts +48 -10
- package/src/index.tsx +98 -17
- package/src/model/display-units.ts +4 -1
- package/src/model/numeric-lanes.ts +3 -0
- package/src/model/text-keys.ts +3 -0
- package/src/motion/entry-exit-builders.ts +73 -0
- package/src/motion/preset-map.ts +4 -0
- package/src/types.ts +14 -2
- package/src/view/glyph-run.tsx +38 -13
- package/src/view/morph-viewport.tsx +3 -0
- package/src/view/number-lane.tsx +7 -2
- package/src/view/number-run.tsx +3 -0
- package/src/view/slots-run.tsx +385 -0
- package/src/view/text-run.tsx +77 -6
|
@@ -2,11 +2,14 @@ import React from "react";
|
|
|
2
2
|
import { type StyleProp, type TextStyle } from "react-native";
|
|
3
3
|
import type { ComplexAnimationBuilder, EntryExitAnimationFunction } from "react-native-reanimated";
|
|
4
4
|
import type { GlyphToken, LaminarAlign } from "../types";
|
|
5
|
-
export declare const GlyphRun: React.MemoExoticComponent<({ glyphs, layoutTransition, enterTransition, exitTransition, align, textStyle, className, }: Readonly<{
|
|
5
|
+
export declare const GlyphRun: React.MemoExoticComponent<({ glyphs, layoutTransition, enterTransition, elementEnterTransition, exitTransition, elementExitTransition, leadingGap, align, textStyle, className, }: Readonly<{
|
|
6
6
|
glyphs: readonly GlyphToken[];
|
|
7
|
-
layoutTransition
|
|
7
|
+
layoutTransition?: ComplexAnimationBuilder;
|
|
8
8
|
enterTransition?: EntryExitAnimationFunction;
|
|
9
|
+
elementEnterTransition?: EntryExitAnimationFunction;
|
|
9
10
|
exitTransition?: EntryExitAnimationFunction;
|
|
11
|
+
elementExitTransition?: EntryExitAnimationFunction;
|
|
12
|
+
leadingGap?: number;
|
|
10
13
|
align: LaminarAlign;
|
|
11
14
|
textStyle?: StyleProp<TextStyle>;
|
|
12
15
|
className?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glyph-run.d.ts","sourceRoot":"","sources":["../../../../src/view/glyph-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EACV,uBAAuB,EACvB,0BAA0B,EAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"glyph-run.d.ts","sourceRoot":"","sources":["../../../../src/view/glyph-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EACV,uBAAuB,EACvB,0BAA0B,EAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAezD,eAAO,MAAM,QAAQ,qLAYhB,QAAQ,CAAC;IACV,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,eAAe,CAAC,EAAE,0BAA0B,CAAC;IAC7C,sBAAsB,CAAC,EAAE,0BAA0B,CAAC;IACpD,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,qBAAqB,CAAC,EAAE,0BAA0B,CAAC;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,uBAmCH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"morph-viewport.d.ts","sourceRoot":"","sources":["../../../../src/view/morph-viewport.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,QAAQ,MAAM,yBAAyB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA4C7C,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;IAClF,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"morph-viewport.d.ts","sourceRoot":"","sources":["../../../../src/view/morph-viewport.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,QAAQ,MAAM,yBAAyB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA4C7C,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;IAClF,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC,CAAC;AAGF,eAAO,MAAM,aAAa,4HASrB,kBAAkB,uBAqCtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-lane.d.ts","sourceRoot":"","sources":["../../../../src/view/number-lane.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpE,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAiBnE,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;
|
|
1
|
+
{"version":3,"file":"number-lane.d.ts","sourceRoot":"","sources":["../../../../src/view/number-lane.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpE,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAiBnE,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACzC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,UAAU,+IAYlB,eAAe,uBAqFnB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-run.d.ts","sourceRoot":"","sources":["../../../../src/view/number-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAe3D,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;
|
|
1
|
+
{"version":3,"file":"number-run.d.ts","sourceRoot":"","sources":["../../../../src/view/number-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAe3D,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,SAAS,yGASjB,QAAQ,CAAC,cAAc,CAAC,uBA8C5B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type StyleProp, type TextStyle } from "react-native";
|
|
3
|
+
import type { LaminarAlign, MotionRecipe } from "../types";
|
|
4
|
+
type SlotsRunProps = {
|
|
5
|
+
readonly value: string;
|
|
6
|
+
readonly motionRecipe: MotionRecipe;
|
|
7
|
+
readonly align: LaminarAlign;
|
|
8
|
+
readonly fontSize?: number;
|
|
9
|
+
readonly textStyle?: StyleProp<TextStyle>;
|
|
10
|
+
readonly staggerMs: number;
|
|
11
|
+
readonly className?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const SlotsRun: React.MemoExoticComponent<({ value, motionRecipe, align, fontSize, textStyle, staggerMs, className, }: Readonly<SlotsRunProps>) => React.JSX.Element>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=slots-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slots-run.d.ts","sourceRoot":"","sources":["../../../../src/view/slots-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAC1D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EAEf,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAwB,MAAM,UAAU,CAAC;AAmQjF,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,QAAQ,yGAShB,QAAQ,CAAC,aAAa,CAAC,uBAwF3B,CAAC"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
2
3
|
import type { StyleProp, TextStyle } from "react-native";
|
|
3
4
|
import type { LaminarAlign, MotionRecipe } from "../types";
|
|
4
5
|
type TextRunProps = {
|
|
5
6
|
readonly value: string;
|
|
6
7
|
readonly motionRecipe: MotionRecipe;
|
|
7
8
|
readonly align: LaminarAlign;
|
|
9
|
+
readonly leading?: ReactNode;
|
|
10
|
+
readonly leadingKey?: string | number;
|
|
11
|
+
readonly leadingGap?: number;
|
|
8
12
|
readonly textStyle?: StyleProp<TextStyle>;
|
|
9
13
|
readonly className?: string;
|
|
10
14
|
};
|
|
11
|
-
export declare const TextRun: React.MemoExoticComponent<({ value, motionRecipe, align, textStyle, className }: TextRunProps) => React.JSX.Element>;
|
|
15
|
+
export declare const TextRun: React.MemoExoticComponent<({ value, motionRecipe, align, leading, leadingKey, leadingGap, textStyle, className, }: TextRunProps) => React.JSX.Element>;
|
|
12
16
|
export {};
|
|
13
17
|
//# sourceMappingURL=text-run.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-run.d.ts","sourceRoot":"","sources":["../../../../src/view/text-run.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"text-run.d.ts","sourceRoot":"","sources":["../../../../src/view/text-run.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAOzD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG3D,KAAK,YAAY,GAAG;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,OAAO,qHAUf,YAAY,uBA8EhB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-laminar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "A React Native component for morphing text and numbers with character-level identity.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"react-native": "./src/index.tsx",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
|
+
"react-native": "./src/index.tsx",
|
|
25
|
+
"source": "./src/index.tsx",
|
|
24
26
|
"import": {
|
|
25
27
|
"types": "./lib/typescript/module/index.d.ts",
|
|
26
28
|
"default": "./lib/module/index.js"
|
|
@@ -37,7 +39,14 @@
|
|
|
37
39
|
],
|
|
38
40
|
"scripts": {
|
|
39
41
|
"build": "bob build",
|
|
40
|
-
"prepare": "bob build"
|
|
42
|
+
"prepare": "bob build",
|
|
43
|
+
"test": "jest --config jest.config.js --runInBand",
|
|
44
|
+
"test:watch": "jest --config jest.config.js --watch",
|
|
45
|
+
"test:coverage": "jest --config jest.config.js --runInBand --coverage && node scripts/check-coverage.js",
|
|
46
|
+
"test:ci": "jest --config jest.config.js --runInBand --ci --coverage && node scripts/check-coverage.js",
|
|
47
|
+
"typecheck": "tsc --project tsconfig.test.json --noEmit",
|
|
48
|
+
"pack:check": "npm pack --dry-run --cache ./coverage/npm-cache",
|
|
49
|
+
"quality": "npm run typecheck && npm run test:ci && npm run build && npm run pack:check"
|
|
41
50
|
},
|
|
42
51
|
"sideEffects": false,
|
|
43
52
|
"peerDependencies": {
|
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import type { LayoutChangeEvent } from "react-native";
|
|
3
3
|
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
4
4
|
|
|
5
5
|
type Params = {
|
|
6
6
|
enabled: boolean;
|
|
7
7
|
driveToWidth: (toValue: number) => number;
|
|
8
|
+
measurementKey: string;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
const WIDTH_CACHE_LIMIT = 64;
|
|
12
|
+
|
|
13
|
+
// measure each content signature once and drive later changes through one width value
|
|
14
|
+
export const useInlineAutoWidth = ({
|
|
15
|
+
enabled,
|
|
16
|
+
driveToWidth,
|
|
17
|
+
measurementKey,
|
|
18
|
+
}: Params) => {
|
|
11
19
|
const widthValue = useSharedValue(0);
|
|
12
20
|
const measuredWidthRef = useRef<number | null>(null);
|
|
13
21
|
const bootstrappedRef = useRef(false);
|
|
22
|
+
const widthCacheRef = useRef(new Map<string, number>());
|
|
14
23
|
const [hasBootstrappedWidth, setHasBootstrappedWidth] = useState(false);
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const nextWidth = Math.max(0, Math.ceil(event.nativeEvent.layout.width));
|
|
23
|
-
|
|
25
|
+
// the first measurement establishes the shell without animating from zero
|
|
26
|
+
const applyWidth = useCallback(
|
|
27
|
+
(nextWidth: number) => {
|
|
24
28
|
if (measuredWidthRef.current === nextWidth) {
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
measuredWidthRef.current = nextWidth;
|
|
29
33
|
|
|
30
|
-
// snap the first width so mount does not animate from zero
|
|
31
34
|
if (!bootstrappedRef.current) {
|
|
32
35
|
bootstrappedRef.current = true;
|
|
33
36
|
widthValue.value = nextWidth;
|
|
@@ -37,9 +40,48 @@ export const useInlineAutoWidth = ({ enabled, driveToWidth }: Params) => {
|
|
|
37
40
|
|
|
38
41
|
widthValue.value = driveToWidth(nextWidth);
|
|
39
42
|
},
|
|
40
|
-
[driveToWidth,
|
|
43
|
+
[driveToWidth, widthValue]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const captureLayout = useCallback(
|
|
47
|
+
(event: LayoutChangeEvent) => {
|
|
48
|
+
if (!enabled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// round up so fractional native measurements cannot clip the final glyph
|
|
53
|
+
const nextWidth = Math.max(0, Math.ceil(event.nativeEvent.layout.width));
|
|
54
|
+
|
|
55
|
+
// delete before setting so a repeated key moves to the most recent position
|
|
56
|
+
widthCacheRef.current.delete(measurementKey);
|
|
57
|
+
|
|
58
|
+
if (widthCacheRef.current.size >= WIDTH_CACHE_LIMIT) {
|
|
59
|
+
// map insertion order gives the lru entry at the front
|
|
60
|
+
const oldestKey = widthCacheRef.current.keys().next().value;
|
|
61
|
+
|
|
62
|
+
if (oldestKey !== undefined) {
|
|
63
|
+
widthCacheRef.current.delete(oldestKey);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
widthCacheRef.current.set(measurementKey, nextWidth);
|
|
68
|
+
applyWidth(nextWidth);
|
|
69
|
+
},
|
|
70
|
+
[applyWidth, enabled, measurementKey]
|
|
41
71
|
);
|
|
42
72
|
|
|
73
|
+
const cachedWidth = widthCacheRef.current.get(measurementKey);
|
|
74
|
+
|
|
75
|
+
// a cached width can restore the shell before the hidden probe renders again
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (enabled && cachedWidth !== undefined) {
|
|
78
|
+
// promote cache hits so active states stay available longer
|
|
79
|
+
widthCacheRef.current.delete(measurementKey);
|
|
80
|
+
widthCacheRef.current.set(measurementKey, cachedWidth);
|
|
81
|
+
applyWidth(cachedWidth);
|
|
82
|
+
}
|
|
83
|
+
}, [applyWidth, cachedWidth, enabled, measurementKey]);
|
|
84
|
+
|
|
43
85
|
const animatedWidthStyle = useAnimatedStyle(
|
|
44
86
|
() =>
|
|
45
87
|
enabled && hasBootstrappedWidth
|
|
@@ -53,5 +95,6 @@ export const useInlineAutoWidth = ({ enabled, driveToWidth }: Params) => {
|
|
|
53
95
|
return {
|
|
54
96
|
captureLayout,
|
|
55
97
|
animatedWidthStyle,
|
|
98
|
+
shouldMeasure: enabled && cachedWidth === undefined,
|
|
56
99
|
};
|
|
57
100
|
};
|
|
@@ -18,13 +18,14 @@ type MorphMotion = {
|
|
|
18
18
|
readonly staggerMs: number;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
// resolve one motion recipe and one shared stagger unit for a render tree
|
|
21
22
|
export const useMorphMotion = ({
|
|
22
23
|
variant,
|
|
23
24
|
animationPreset,
|
|
24
25
|
animationDuration,
|
|
25
26
|
stagger,
|
|
26
27
|
}: Params): MorphMotion => {
|
|
27
|
-
//
|
|
28
|
+
// numeric variants need a quicker default because their lanes update often
|
|
28
29
|
const resolvedPreset =
|
|
29
30
|
animationPreset ?? (variant === "text" ? "default" : "snappy");
|
|
30
31
|
|
|
@@ -12,12 +12,14 @@ type MorphTextStyle = {
|
|
|
12
12
|
readonly textStyle: StyleProp<TextStyle>;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
// compose the public style layers into the style passed to every glyph
|
|
15
16
|
export const useMorphTextStyle = ({
|
|
16
17
|
fontSize,
|
|
17
18
|
color,
|
|
18
19
|
fontStyle,
|
|
19
20
|
style,
|
|
20
21
|
}: Params): MorphTextStyle => {
|
|
22
|
+
// keep the base layer small so caller styles can override it predictably
|
|
21
23
|
const baseTextStyle = useMemo(() => {
|
|
22
24
|
const nextStyle: TextStyle = {
|
|
23
25
|
includeFontPadding: false,
|
|
@@ -34,6 +36,7 @@ export const useMorphTextStyle = ({
|
|
|
34
36
|
return nextStyle;
|
|
35
37
|
}, [color, fontSize]);
|
|
36
38
|
|
|
39
|
+
// preserve the merge order used by the public style contract
|
|
37
40
|
const textStyle = useMemo(
|
|
38
41
|
() => [baseTextStyle, fontStyle, style],
|
|
39
42
|
[baseTextStyle, fontStyle, style]
|
|
@@ -20,6 +20,7 @@ type NumericLaneSnapshot = {
|
|
|
20
20
|
readonly leadLength: number;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
// retain numeric lane identity across value changes without storing it in state
|
|
23
24
|
export const useNumericLanes = (value: string): NumericLaneSnapshot => {
|
|
24
25
|
const units = useMemo(() => splitDisplayUnits(value), [value]);
|
|
25
26
|
const ledgerRef = useRef<NumericLedger>({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, useRef } from "react";
|
|
1
|
+
import { useMemo, useRef, type ReactNode } from "react";
|
|
2
2
|
import {
|
|
3
3
|
normalizeDisplayUnit,
|
|
4
4
|
splitDisplayUnits,
|
|
@@ -8,28 +8,62 @@ import type { GlyphToken } from "../types";
|
|
|
8
8
|
|
|
9
9
|
type TextGlyphLedger = {
|
|
10
10
|
previousValue: string;
|
|
11
|
-
|
|
11
|
+
previousSignatures: readonly string[];
|
|
12
12
|
glyphKeys: readonly string[];
|
|
13
13
|
nextSeed: number;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
type TextGlyphUnit =
|
|
17
|
+
| { readonly kind: "text"; readonly signature: string; readonly value: string }
|
|
18
|
+
| { readonly kind: "element"; readonly signature: string; readonly element: ReactNode };
|
|
19
|
+
|
|
20
|
+
// build the inline token stream that lets text and leading content share one ledger
|
|
16
21
|
export const useTextGlyphs = (
|
|
17
22
|
value: string,
|
|
18
|
-
namespace: string
|
|
23
|
+
namespace: string,
|
|
24
|
+
leading?: ReactNode,
|
|
25
|
+
leadingKey?: string | number
|
|
19
26
|
): readonly GlyphToken[] => {
|
|
20
|
-
const units = useMemo
|
|
27
|
+
const units = useMemo<readonly TextGlyphUnit[]>(
|
|
28
|
+
() => [
|
|
29
|
+
// the map key becomes the identity when leading content is state keyed
|
|
30
|
+
...(leading
|
|
31
|
+
? [
|
|
32
|
+
{
|
|
33
|
+
kind: "element",
|
|
34
|
+
signature: `leading:${String(leadingKey ?? "default")}`,
|
|
35
|
+
element: leading,
|
|
36
|
+
} as const,
|
|
37
|
+
]
|
|
38
|
+
: []),
|
|
39
|
+
...splitDisplayUnits(value).map((unit) => ({
|
|
40
|
+
kind: "text" as const,
|
|
41
|
+
signature: `text:${unit}`,
|
|
42
|
+
value: unit,
|
|
43
|
+
})),
|
|
44
|
+
],
|
|
45
|
+
[leading, leadingKey, value]
|
|
46
|
+
);
|
|
47
|
+
const signatures = units.map((unit) => unit.signature);
|
|
21
48
|
const ledgerRef = useRef<TextGlyphLedger>({
|
|
22
49
|
previousValue: value,
|
|
23
|
-
|
|
50
|
+
previousSignatures: signatures,
|
|
24
51
|
glyphKeys: units.map((_, index) => `${namespace}:c${index}`),
|
|
25
52
|
nextSeed: units.length,
|
|
26
53
|
});
|
|
27
54
|
|
|
28
|
-
|
|
55
|
+
// reconcile only when a token signature changes so stable glyphs keep their views
|
|
56
|
+
if (
|
|
57
|
+
value !== ledgerRef.current.previousValue ||
|
|
58
|
+
signatures.length !== ledgerRef.current.previousSignatures.length ||
|
|
59
|
+
signatures.some(
|
|
60
|
+
(signature, index) => signature !== ledgerRef.current.previousSignatures[index]
|
|
61
|
+
)
|
|
62
|
+
) {
|
|
29
63
|
// keep ids stable so unchanged glyphs stay mounted between updates
|
|
30
64
|
const nextLedger = reconcileTextGlyphKeys(
|
|
31
|
-
ledgerRef.current.
|
|
32
|
-
|
|
65
|
+
ledgerRef.current.previousSignatures,
|
|
66
|
+
signatures,
|
|
33
67
|
ledgerRef.current.glyphKeys,
|
|
34
68
|
ledgerRef.current.nextSeed,
|
|
35
69
|
namespace
|
|
@@ -37,7 +71,7 @@ export const useTextGlyphs = (
|
|
|
37
71
|
|
|
38
72
|
ledgerRef.current = {
|
|
39
73
|
previousValue: value,
|
|
40
|
-
|
|
74
|
+
previousSignatures: signatures,
|
|
41
75
|
glyphKeys: nextLedger.glyphKeys,
|
|
42
76
|
nextSeed: nextLedger.nextSeed,
|
|
43
77
|
};
|
|
@@ -45,11 +79,15 @@ export const useTextGlyphs = (
|
|
|
45
79
|
|
|
46
80
|
const glyphKeys = ledgerRef.current.glyphKeys;
|
|
47
81
|
|
|
82
|
+
// normalize spaces at the edge where tokens become renderable glyphs
|
|
48
83
|
return useMemo(
|
|
49
84
|
() =>
|
|
50
85
|
units.map((unit, index) => ({
|
|
51
86
|
id: glyphKeys[index],
|
|
52
|
-
|
|
87
|
+
kind: unit.kind,
|
|
88
|
+
...(unit.kind === "text"
|
|
89
|
+
? { value: normalizeDisplayUnit(unit.value) }
|
|
90
|
+
: { element: unit.element }),
|
|
53
91
|
})),
|
|
54
92
|
[glyphKeys, units]
|
|
55
93
|
);
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Text } from "react-native";
|
|
2
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
3
3
|
import { useInlineAutoWidth } from "./hooks/use-inline-auto-width";
|
|
4
4
|
import { useMorphMotion } from "./hooks/use-morph-motion";
|
|
5
5
|
import { useMorphTextStyle } from "./hooks/use-morph-text-style";
|
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
normalizeDisplayUnit,
|
|
8
8
|
splitDisplayUnits,
|
|
9
9
|
} from "./model/display-units";
|
|
10
|
-
import type { MorphingTextProps } from "./types";
|
|
10
|
+
import type { LaminarLeadingMap, MorphingTextProps } from "./types";
|
|
11
11
|
import { MorphViewport } from "./view/morph-viewport";
|
|
12
12
|
import { NumberRun } from "./view/number-run";
|
|
13
|
+
import { SlotsRun } from "./view/slots-run";
|
|
13
14
|
import { TextRun } from "./view/text-run";
|
|
14
15
|
|
|
16
|
+
// choose the content variant and connect it to the shared viewport and measurement flow
|
|
15
17
|
export const Laminar = React.memo(function Laminar({
|
|
16
18
|
text,
|
|
17
19
|
variant = "text",
|
|
@@ -19,6 +21,9 @@ export const Laminar = React.memo(function Laminar({
|
|
|
19
21
|
color,
|
|
20
22
|
align = "left",
|
|
21
23
|
className,
|
|
24
|
+
leading,
|
|
25
|
+
leadingKey,
|
|
26
|
+
leadingGap = 0,
|
|
22
27
|
style,
|
|
23
28
|
containerStyle,
|
|
24
29
|
fontStyle,
|
|
@@ -29,6 +34,12 @@ export const Laminar = React.memo(function Laminar({
|
|
|
29
34
|
clipToBounds = false,
|
|
30
35
|
}: Readonly<MorphingTextProps>) {
|
|
31
36
|
const resolvedValue = String(text ?? "");
|
|
37
|
+
// resolve state keyed leading content before measurement and token reconciliation
|
|
38
|
+
const leadingMap = isLeadingMap(leading) ? leading : undefined;
|
|
39
|
+
const resolvedLeading: React.ReactNode = leadingMap
|
|
40
|
+
? leadingMap[resolvedValue]
|
|
41
|
+
: (leading as React.ReactNode);
|
|
42
|
+
const resolvedLeadingKey = leadingMap ? resolvedValue : leadingKey;
|
|
32
43
|
const { motionRecipe, staggerMs } = useMorphMotion({
|
|
33
44
|
variant,
|
|
34
45
|
animationPreset,
|
|
@@ -42,16 +53,47 @@ export const Laminar = React.memo(function Laminar({
|
|
|
42
53
|
style,
|
|
43
54
|
});
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
// invalidate the probe when text style, leading content, or leading spacing changes
|
|
57
|
+
const measurementKey = React.useMemo(() => {
|
|
58
|
+
const flattenedStyle = StyleSheet.flatten(textStyle);
|
|
59
|
+
|
|
60
|
+
return JSON.stringify([
|
|
61
|
+
resolvedValue,
|
|
62
|
+
flattenedStyle?.fontFamily,
|
|
63
|
+
flattenedStyle?.fontSize,
|
|
64
|
+
flattenedStyle?.fontStyle,
|
|
65
|
+
flattenedStyle?.fontWeight,
|
|
66
|
+
flattenedStyle?.fontVariant,
|
|
67
|
+
flattenedStyle?.letterSpacing,
|
|
68
|
+
flattenedStyle?.lineHeight,
|
|
69
|
+
flattenedStyle?.textTransform,
|
|
70
|
+
variant === "text" && Boolean(resolvedLeading),
|
|
71
|
+
variant === "text" ? resolvedLeadingKey : undefined,
|
|
72
|
+
variant === "text" ? leadingGap : 0,
|
|
73
|
+
]);
|
|
74
|
+
}, [
|
|
75
|
+
leadingGap,
|
|
76
|
+
resolvedLeading,
|
|
77
|
+
resolvedLeadingKey,
|
|
78
|
+
resolvedValue,
|
|
79
|
+
textStyle,
|
|
80
|
+
variant,
|
|
81
|
+
]);
|
|
82
|
+
const { captureLayout, animatedWidthStyle, shouldMeasure } =
|
|
83
|
+
useInlineAutoWidth({
|
|
84
|
+
enabled: autoSize,
|
|
85
|
+
driveToWidth: motionRecipe.driveNumber,
|
|
86
|
+
measurementKey,
|
|
87
|
+
});
|
|
88
|
+
// measurement uses the same normalized units as the visible text row
|
|
49
89
|
const measuredValue = React.useMemo(() => {
|
|
50
90
|
if (!autoSize) {
|
|
51
91
|
return "";
|
|
52
92
|
}
|
|
53
93
|
|
|
54
|
-
return splitDisplayUnits(resolvedValue)
|
|
94
|
+
return splitDisplayUnits(resolvedValue)
|
|
95
|
+
.map(normalizeDisplayUnit)
|
|
96
|
+
.join("");
|
|
55
97
|
}, [autoSize, resolvedValue]);
|
|
56
98
|
|
|
57
99
|
return (
|
|
@@ -62,16 +104,42 @@ export const Laminar = React.memo(function Laminar({
|
|
|
62
104
|
containerStyle={containerStyle}
|
|
63
105
|
animatedWidthStyle={animatedWidthStyle}
|
|
64
106
|
measurement={
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
107
|
+
shouldMeasure ? (
|
|
108
|
+
<View
|
|
109
|
+
onLayout={captureLayout}
|
|
110
|
+
style={{ flexDirection: "row", alignSelf: "flex-start" }}
|
|
111
|
+
>
|
|
112
|
+
{variant === "text" && resolvedLeading ? (
|
|
113
|
+
<View
|
|
114
|
+
style={{
|
|
115
|
+
alignItems: "center",
|
|
116
|
+
justifyContent: "center",
|
|
117
|
+
marginRight: leadingGap,
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
{resolvedLeading}
|
|
121
|
+
</View>
|
|
122
|
+
) : null}
|
|
123
|
+
{splitDisplayUnits(measuredValue).map((unit, index) => (
|
|
124
|
+
<Text key={`${unit}-${index}`} style={textStyle}>
|
|
125
|
+
{normalizeDisplayUnit(unit)}
|
|
126
|
+
</Text>
|
|
127
|
+
))}
|
|
128
|
+
</View>
|
|
129
|
+
) : undefined
|
|
72
130
|
}
|
|
73
131
|
>
|
|
74
|
-
{variant === "
|
|
132
|
+
{variant === "slots" ? (
|
|
133
|
+
<SlotsRun
|
|
134
|
+
value={resolvedValue}
|
|
135
|
+
motionRecipe={motionRecipe}
|
|
136
|
+
align={align}
|
|
137
|
+
fontSize={fontSize}
|
|
138
|
+
textStyle={textStyle}
|
|
139
|
+
staggerMs={staggerMs}
|
|
140
|
+
className={className}
|
|
141
|
+
/>
|
|
142
|
+
) : variant === "number" ? (
|
|
75
143
|
<NumberRun
|
|
76
144
|
value={resolvedValue}
|
|
77
145
|
motionRecipe={motionRecipe}
|
|
@@ -86,6 +154,9 @@ export const Laminar = React.memo(function Laminar({
|
|
|
86
154
|
value={resolvedValue}
|
|
87
155
|
motionRecipe={motionRecipe}
|
|
88
156
|
align={align}
|
|
157
|
+
leading={resolvedLeading}
|
|
158
|
+
leadingKey={resolvedLeadingKey}
|
|
159
|
+
leadingGap={leadingGap}
|
|
89
160
|
textStyle={textStyle}
|
|
90
161
|
className={className}
|
|
91
162
|
/>
|
|
@@ -94,11 +165,21 @@ export const Laminar = React.memo(function Laminar({
|
|
|
94
165
|
);
|
|
95
166
|
});
|
|
96
167
|
|
|
97
|
-
|
|
168
|
+
function isLeadingMap(
|
|
169
|
+
value: MorphingTextProps["leading"]
|
|
170
|
+
): value is LaminarLeadingMap {
|
|
171
|
+
// react elements are objects too, so exclude them before treating a value as a map
|
|
172
|
+
return (
|
|
173
|
+
typeof value === "object" &&
|
|
174
|
+
value !== null &&
|
|
175
|
+
!React.isValidElement(value) &&
|
|
176
|
+
!Array.isArray(value)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
98
179
|
|
|
99
|
-
export default Laminar;
|
|
100
180
|
export type {
|
|
101
181
|
LaminarAlign,
|
|
182
|
+
LaminarLeadingMap,
|
|
102
183
|
LaminarProps,
|
|
103
184
|
MorphAnimationPresetName,
|
|
104
185
|
MorphContentVariant,
|
|
@@ -4,6 +4,7 @@ const graphemeSegmenter =
|
|
|
4
4
|
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
5
5
|
: null;
|
|
6
6
|
|
|
7
|
+
// segment graphemes when the runtime supports them so emoji and combining marks stay whole
|
|
7
8
|
export const splitDisplayUnits = (input: string): string[] => {
|
|
8
9
|
if (graphemeSegmenter) {
|
|
9
10
|
return Array.from(
|
|
@@ -15,12 +16,14 @@ export const splitDisplayUnits = (input: string): string[] => {
|
|
|
15
16
|
return Array.from(input);
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
//
|
|
19
|
+
// replace plain spaces because native text measurement can collapse them
|
|
19
20
|
export const normalizeDisplayUnit = (unit: string) =>
|
|
20
21
|
unit === " " ? NBSP : unit;
|
|
21
22
|
|
|
23
|
+
// numeric lanes only treat ascii digits as place-value columns
|
|
22
24
|
export const isAsciiDigit = (unit: string) => unit >= "0" && unit <= "9";
|
|
23
25
|
|
|
26
|
+
// leading symbols and punctuation stay attached to the left side of a number
|
|
24
27
|
export const findNumericLeadLength = (units: readonly string[]) => {
|
|
25
28
|
const firstDigitIndex = units.findIndex(isAsciiDigit);
|
|
26
29
|
|
|
@@ -4,9 +4,11 @@ import {
|
|
|
4
4
|
splitDisplayUnits,
|
|
5
5
|
} from "./display-units";
|
|
6
6
|
|
|
7
|
+
// strip formatting before comparing values so direction follows the displayed number
|
|
7
8
|
const readNumericMagnitude = (input: string) =>
|
|
8
9
|
parseFloat(input.replace(/[^0-9.-]/g, "")) || 0;
|
|
9
10
|
|
|
11
|
+
// the sign controls which side of each digit lane enters and exits
|
|
10
12
|
const deriveFlowDirection = (
|
|
11
13
|
previousValue: string,
|
|
12
14
|
nextValue: string
|
|
@@ -19,6 +21,7 @@ type ReconciledLaneState = {
|
|
|
19
21
|
readonly direction: NumericFlowDirection;
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
// preserve right-aligned place values while allowing prefixes to grow or shrink
|
|
22
25
|
export const reconcileNumericLanes = (
|
|
23
26
|
previousValue: string,
|
|
24
27
|
nextValue: string,
|
package/src/model/text-keys.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// find the longest ordered set of shared units so edits keep nearby glyph identity
|
|
1
2
|
const computeLcsPairs = (
|
|
2
3
|
previousUnits: readonly string[],
|
|
3
4
|
nextUnits: readonly string[]
|
|
@@ -9,6 +10,7 @@ const computeLcsPairs = (
|
|
|
9
10
|
return [];
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
// dynamic programming makes the middle edit deterministic when prefix and suffix match
|
|
12
14
|
const dp = Array.from({ length: previousLength + 1 }, () =>
|
|
13
15
|
new Array<number>(nextLength + 1).fill(0)
|
|
14
16
|
);
|
|
@@ -54,6 +56,7 @@ type ReconciledTextGlyphState = {
|
|
|
54
56
|
readonly nextSeed: number;
|
|
55
57
|
};
|
|
56
58
|
|
|
59
|
+
// reuse prefix, suffix, and lcs matches before allocating keys for new glyphs
|
|
57
60
|
export const reconcileTextGlyphKeys = (
|
|
58
61
|
previousUnits: readonly string[],
|
|
59
62
|
nextUnits: readonly string[],
|