melta-app 0.5.3 → 0.6.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/README.md +285 -186
- package/docs/architecture.md +92 -0
- package/lib/module/primitives/Text.js +18 -5
- package/lib/module/primitives/Text.js.map +1 -1
- package/lib/module/primitives/metric.styles.js +4 -1
- package/lib/module/primitives/metric.styles.js.map +1 -1
- package/lib/module/primitives/text.styles.js +7 -1
- package/lib/module/primitives/text.styles.js.map +1 -1
- package/lib/module/theme/define-theme.js +13 -0
- package/lib/module/theme/define-theme.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/line-height.js +43 -0
- package/lib/module/theme/line-height.js.map +1 -0
- package/lib/module/theme/native-theme.js +7 -6
- package/lib/module/theme/native-theme.js.map +1 -1
- package/lib/typescript/src/primitives/Text.d.ts.map +1 -1
- package/lib/typescript/src/primitives/metric.styles.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.styles.d.ts.map +1 -1
- package/lib/typescript/src/theme/define-theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/index.d.ts +1 -0
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/line-height.d.ts +34 -0
- package/lib/typescript/src/theme/line-height.d.ts.map +1 -0
- package/lib/typescript/src/theme/native-theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +9 -0
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/src/primitives/Text.tsx +19 -3
- package/src/primitives/metric.styles.ts +8 -1
- package/src/primitives/text.styles.ts +10 -1
- package/src/theme/define-theme.ts +18 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/line-height.ts +44 -0
- package/src/theme/native-theme.ts +7 -6
- package/src/theme/types.ts +9 -0
package/src/primitives/Text.tsx
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { useMemo, type ReactNode } from "react";
|
|
16
|
-
import { Text as RNText, type StyleProp, type TextStyle } from "react-native";
|
|
17
|
-
import { useTheme } from "../theme";
|
|
16
|
+
import { StyleSheet, Text as RNText, type StyleProp, type TextStyle } from "react-native";
|
|
17
|
+
import { useTheme, DEFAULT_MIN_LINE_HEIGHT_RATIO, minLineHeightFor } from "../theme";
|
|
18
18
|
import type { FontWeightKey, SemanticColors } from "../theme";
|
|
19
19
|
import { CONTRACTS, type VariantOf } from "../contracts/contract-types";
|
|
20
20
|
import { resolveTextShape, type TextRole } from "./text.styles";
|
|
@@ -55,9 +55,25 @@ export function Text({
|
|
|
55
55
|
[theme, variant, role, weight],
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
+
// 消費者 style は合成順で最後=上書きが勝つため、resolver 内のクランプだけでは
|
|
59
|
+
// `style={{lineHeight: 16}}` を止められない。flatten した**最終値**に行間の安全下限を掛ける
|
|
60
|
+
// (fontSize の上書きにも追随させるため、下限は最終 fontSize から算出。機序は theme/line-height.ts)。
|
|
61
|
+
const flat: TextStyle = StyleSheet.flatten([shape, { color: colors[color] }, style]);
|
|
62
|
+
// `fontSize: undefined` の明示上書き(flatten は undefined でも上書きする)は、RN 既定サイズで
|
|
63
|
+
// 描画されるのにクランプ基準だけ variant 値になる齟齬を生む。variant の fontSize を復元して
|
|
64
|
+
// 「Text の文字サイズは variant が決める」契約とクランプ基準を一致させる。
|
|
65
|
+
if (typeof flat.fontSize !== "number") flat.fontSize = shape.fontSize;
|
|
66
|
+
if (flat.lineHeight !== undefined) {
|
|
67
|
+
const floor = minLineHeightFor(
|
|
68
|
+
flat.fontSize ?? 0,
|
|
69
|
+
theme.typography.minLineHeightRatio ?? DEFAULT_MIN_LINE_HEIGHT_RATIO,
|
|
70
|
+
);
|
|
71
|
+
if (flat.lineHeight < floor) flat.lineHeight = floor;
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
return (
|
|
59
75
|
<RNText
|
|
60
|
-
style={
|
|
76
|
+
style={flat}
|
|
61
77
|
numberOfLines={numberOfLines}
|
|
62
78
|
accessibilityRole={role === "heading" ? "header" : undefined}
|
|
63
79
|
testID={testID}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import type { FontVariant } from "react-native";
|
|
10
10
|
import type { NativeTheme, ThemeMode, FontSizeKey, FontWeightValue } from "../theme";
|
|
11
|
+
// theme index からの runtime import は ThemeProvider → react-native を引くため pure module を直接参照
|
|
12
|
+
import { DEFAULT_MIN_LINE_HEIGHT_RATIO, clampLineHeight } from "../theme/line-height";
|
|
11
13
|
|
|
12
14
|
export type MetricSize = "sm" | "md" | "lg";
|
|
13
15
|
|
|
@@ -56,7 +58,12 @@ export function resolveMetricStyles(
|
|
|
56
58
|
return {
|
|
57
59
|
valueStyle: {
|
|
58
60
|
fontSize: fontSize[font.value].fontSize,
|
|
59
|
-
|
|
61
|
+
// カスタム theme の未クランプ値への防波堤(text.styles.ts と同じ。根拠は theme/line-height.ts)
|
|
62
|
+
lineHeight: clampLineHeight(
|
|
63
|
+
fontSize[font.value].fontSize,
|
|
64
|
+
fontSize[font.value].lineHeight,
|
|
65
|
+
theme.typography.minLineHeightRatio ?? DEFAULT_MIN_LINE_HEIGHT_RATIO,
|
|
66
|
+
),
|
|
60
67
|
fontWeight: theme.typography.fontWeight.bold,
|
|
61
68
|
color: sem["text-heading"],
|
|
62
69
|
fontVariant: ["tabular-nums"],
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { NativeTheme, FontSizeKey, FontWeightKey, FontWeightValue } from "../theme";
|
|
10
|
+
// theme index からの runtime import は ThemeProvider → react-native を引くため、
|
|
11
|
+
// pure module(line-height.ts)を直接参照する(letterSpacing の式直書きと同じ理由)。
|
|
12
|
+
import { DEFAULT_MIN_LINE_HEIGHT_RATIO, clampLineHeight } from "../theme/line-height";
|
|
10
13
|
|
|
11
14
|
/** letterSpacing の切替軸(text.recipe description の role prop に対応)。 */
|
|
12
15
|
export type TextRole = "heading" | "body";
|
|
@@ -35,7 +38,13 @@ export function resolveTextShape(
|
|
|
35
38
|
const fs = theme.typography.fontSize[variant];
|
|
36
39
|
return {
|
|
37
40
|
fontSize: fs.fontSize,
|
|
38
|
-
|
|
41
|
+
// 既定 theme は codegen 時点でクランプ済みだが、defineTheme で注入されたカスタム theme の
|
|
42
|
+
// 値はここが最初の防波堤(機序と下限の根拠は theme/line-height.ts)。
|
|
43
|
+
lineHeight: clampLineHeight(
|
|
44
|
+
fs.fontSize,
|
|
45
|
+
fs.lineHeight,
|
|
46
|
+
theme.typography.minLineHeightRatio ?? DEFAULT_MIN_LINE_HEIGHT_RATIO,
|
|
47
|
+
),
|
|
39
48
|
letterSpacing: fs.fontSize * theme.typography.letterSpacingRatio[role],
|
|
40
49
|
...(weight ? { fontWeight: theme.typography.fontWeight[weight] } : null),
|
|
41
50
|
};
|
|
@@ -373,6 +373,24 @@ export function validateTheme(options: ThemeOptions): string[] {
|
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
// minLineHeightRatio は任意欄だが、書くなら実在しうる比率であること
|
|
377
|
+
// (0.9 のような「詰め」を宣言されると clamp が下限の意味を失う。上限は設けない——
|
|
378
|
+
// 行間をいくら広げても字形は欠けないので、広い分には嘘にならない)。
|
|
379
|
+
// missingKeys と同じ方針で **accessor は呼ばない**(値の直読みは消費者の開発用プローブを
|
|
380
|
+
// 発火させ、「defineTheme は accessor を1度も呼ばない」契約を破る)。data property のみ検査する。
|
|
381
|
+
const minRatioDesc =
|
|
382
|
+
options.typography !== null && typeof options.typography === "object"
|
|
383
|
+
? Object.getOwnPropertyDescriptor(options.typography, "minLineHeightRatio")
|
|
384
|
+
: undefined;
|
|
385
|
+
if (minRatioDesc !== undefined && "value" in minRatioDesc && minRatioDesc.value !== undefined) {
|
|
386
|
+
const minRatio: unknown = minRatioDesc.value;
|
|
387
|
+
if (typeof minRatio !== "number" || !Number.isFinite(minRatio) || minRatio < 1) {
|
|
388
|
+
problems.push(
|
|
389
|
+
`${where}: typography.minLineHeightRatio が不正 — ${String(minRatio)}(フォント実測の 1 以上の有限数を宣言する。根拠は theme/line-height.ts)`,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
376
394
|
const status = options.color?.status;
|
|
377
395
|
if (status !== null && typeof status === "object") {
|
|
378
396
|
for (const kind of STATUS_KINDS) {
|
package/src/theme/index.ts
CHANGED
|
@@ -9,6 +9,11 @@ export { nativeTheme } from "./native-theme";
|
|
|
9
9
|
export { ThemeProvider, useTheme } from "./ThemeProvider";
|
|
10
10
|
export type { ThemeContextValue, ThemeMode } from "./ThemeProvider";
|
|
11
11
|
export { defineTheme } from "./define-theme";
|
|
12
|
+
export {
|
|
13
|
+
DEFAULT_MIN_LINE_HEIGHT_RATIO,
|
|
14
|
+
minLineHeightFor,
|
|
15
|
+
clampLineHeight,
|
|
16
|
+
} from "./line-height";
|
|
12
17
|
/**
|
|
13
18
|
* 以下は theme の解決規則を外から検証・再利用するためのヘルパ。
|
|
14
19
|
* **@experimental — 安定 API ではない。** 挙動・シグネチャ・メッセージは予告なく変わりうる。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* line-height — 行間の安全下限(フォントの字形を欠かせない最小 lineHeight)。
|
|
3
|
+
*
|
|
4
|
+
* なぜ要るか(2026-08-05 modelog dogfood で実害):
|
|
5
|
+
* RN Android の CustomLineHeightSpan は `leading = lineHeight - (ascent + descent)` を計算し、
|
|
6
|
+
* leading が負になると不足分を行の上下から半分ずつ削って帳尻を合わせる。削られた領域の字形
|
|
7
|
+
* (濁点は行の上端にある)は Text 既定の overflow: hidden で消える——「ギ」が「チ」に見える。
|
|
8
|
+
* web はこの機序を持たないため、web の実描画検査では検出できない(Android 実機のみで発現)。
|
|
9
|
+
*
|
|
10
|
+
* 下限は使用フォントのメトリクス(ascent + descent)に依存するが、melta はフォントを同梱しない
|
|
11
|
+
* (system フォント運用)ので、既定は **Android の日本語 system フォント Noto Sans CJK JP の
|
|
12
|
+
* 実測 1.448**(hhea: (1160 + 288) / 1000。AOSP 版・Google Fonts 版とも同値、2026-08-06 実測)
|
|
13
|
+
* を切り上げた 1.45 とする。iOS(Hiragino 1.50 相当)にはこの clip 機序が無いので基準にしない。
|
|
14
|
+
*
|
|
15
|
+
* フォントを同梱する消費者は、そのフォントの実測比率を theme の
|
|
16
|
+
* `typography.minLineHeightRatio` で宣言する(例: LINE Seed JP = 1.61)。
|
|
17
|
+
* 宣言が既定より小さいラテン専用フォント(例: Inter 1.21)も同じ口で下げられる。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 既定の最小行間比。Android system 日本語フォント(Noto Sans CJK JP)の実測 1.448 の切り上げ。
|
|
22
|
+
* theme が `typography.minLineHeightRatio` を宣言しない場合にこの値へ倒れる(日本語安全側)。
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_MIN_LINE_HEIGHT_RATIO = 1.45;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* fontSize に対する安全な最小 lineHeight(px)。
|
|
28
|
+
* **切り上げ**であることが要点: 四捨五入だと 32 × 1.45 = 46.4 → 46 で下限を割る。
|
|
29
|
+
*/
|
|
30
|
+
export function minLineHeightFor(fontSize: number, minRatio: number): number {
|
|
31
|
+
return Math.ceil(fontSize * minRatio);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 宣言された lineHeight を安全下限でクランプする。
|
|
36
|
+
* 宣言値が下限以上ならそのまま(意匠の行間はどこまでも広げられる。詰める方向だけを止める)。
|
|
37
|
+
*/
|
|
38
|
+
export function clampLineHeight(
|
|
39
|
+
fontSize: number,
|
|
40
|
+
declaredLineHeight: number,
|
|
41
|
+
minRatio: number,
|
|
42
|
+
): number {
|
|
43
|
+
return Math.max(declaredLineHeight, minLineHeightFor(fontSize, minRatio));
|
|
44
|
+
}
|
|
@@ -81,11 +81,11 @@ export const nativeTheme: NativeTheme = {
|
|
|
81
81
|
"fontSize": {
|
|
82
82
|
"xxs": {
|
|
83
83
|
"fontSize": 10,
|
|
84
|
-
"lineHeight":
|
|
84
|
+
"lineHeight": 15
|
|
85
85
|
},
|
|
86
86
|
"xs": {
|
|
87
87
|
"fontSize": 13,
|
|
88
|
-
"lineHeight":
|
|
88
|
+
"lineHeight": 19
|
|
89
89
|
},
|
|
90
90
|
"sm": {
|
|
91
91
|
"fontSize": 15,
|
|
@@ -101,15 +101,15 @@ export const nativeTheme: NativeTheme = {
|
|
|
101
101
|
},
|
|
102
102
|
"xl": {
|
|
103
103
|
"fontSize": 22,
|
|
104
|
-
"lineHeight":
|
|
104
|
+
"lineHeight": 32
|
|
105
105
|
},
|
|
106
106
|
"2xl": {
|
|
107
107
|
"fontSize": 26,
|
|
108
|
-
"lineHeight":
|
|
108
|
+
"lineHeight": 38
|
|
109
109
|
},
|
|
110
110
|
"3xl": {
|
|
111
111
|
"fontSize": 32,
|
|
112
|
-
"lineHeight":
|
|
112
|
+
"lineHeight": 47
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
115
|
"fontWeight": {
|
|
@@ -121,7 +121,8 @@ export const nativeTheme: NativeTheme = {
|
|
|
121
121
|
"letterSpacingRatio": {
|
|
122
122
|
"heading": 0.01,
|
|
123
123
|
"body": 0.02
|
|
124
|
-
}
|
|
124
|
+
},
|
|
125
|
+
"minLineHeightRatio": 1.45
|
|
125
126
|
},
|
|
126
127
|
"spacing": {
|
|
127
128
|
"1": 4,
|
package/src/theme/types.ts
CHANGED
|
@@ -91,6 +91,15 @@ export interface ThemeTypography {
|
|
|
91
91
|
* (requirements §4 の「px に換算」からの意図的な乖離。理由はこのコメントの通り)
|
|
92
92
|
*/
|
|
93
93
|
letterSpacingRatio: { heading: number; body: number };
|
|
94
|
+
/**
|
|
95
|
+
* 使用フォントが要求する最小行間比((ascent + descent) / unitsPerEm)。
|
|
96
|
+
* これを下回る lineHeight は RN Android で字形の描画領域を削り、濁点などが欠ける
|
|
97
|
+
* (機序と既定値 1.45 の根拠は theme/line-height.ts のコメント)。
|
|
98
|
+
* style resolver は `clampLineHeight()` でこの比率を下限として適用する。
|
|
99
|
+
* 未宣言は DEFAULT_MIN_LINE_HEIGHT_RATIO(1.45 = Android system 日本語フォント基準)へ倒れる。
|
|
100
|
+
* フォントを同梱する消費者は実測値を宣言する(例: LINE Seed JP = 1.61)。
|
|
101
|
+
*/
|
|
102
|
+
minLineHeightRatio?: number;
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
/**
|