melta-app 0.3.0 → 0.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- The MIT License (MIT)
1
+ MIT License
2
2
 
3
- Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
3
+ Copyright (c) 2026 tsubotax
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -36,6 +36,44 @@ export default function App() {
36
36
 
37
37
  テーマは `ThemeProvider` が OS の light / dark に自動追従(`forcedMode` で固定も可)。トークンは `useTheme()` / `nativeTheme` から取れる。Icon だけは subpath([後述](#icon-だけ-subpath-エントリmelta-appicons))。
38
38
 
39
+ ### ブランドテーマを注入する
40
+
41
+ `theme` を渡すと自分のブランドトークンで塗り替わる。未指定なら melta 既定(`nativeTheme`)。
42
+
43
+ ```tsx
44
+ import { defineTheme, ThemeProvider } from "melta-app";
45
+
46
+ // module スコープで1回だけ組み立てる(render 中に作ると毎レンダー参照が変わり全体が再レンダーする)
47
+ const theme = defineTheme({
48
+ id: "acme",
49
+ color: { /* primary / body / semantic / status */ },
50
+ typography: { /* … */ },
51
+ spacing: { /* … */ }, radius: { /* … */ },
52
+ elevation: { /* … */ }, motion: { /* … */ }, zIndex: { /* … */ },
53
+ });
54
+
55
+ export default function App() {
56
+ return <ThemeProvider theme={theme}>{/* … */}</ThemeProvider>;
57
+ }
58
+ ```
59
+
60
+ **単一の配色しか持たないブランド**(例: dark しか作らない)は、持っていない mode を **書かない**。
61
+
62
+ ```tsx
63
+ const theme = defineTheme({
64
+ id: "acme",
65
+ color: { /* … */ semantic: { dark: darkColors } }, // light は書かない
66
+ /* … */
67
+ });
68
+ ```
69
+
70
+ - `useTheme().capabilities.colorScheme` が `"single-dark"` として導出される(宣言する欄は無い。`color.semantic` のキー集合がそのまま能力になる)
71
+ - OS が light を返しても **dark で描画する**(警告は出さない。light を作らないのは設計判断で、OS 設定は事故ではない)
72
+ - `forcedMode="light"` のように**対応していない mode を明示指定**した場合も描画は止めず clamp するが、開発時は `console.error` で1回報告する。light/dark トグル UI を出すなら `capabilities.colorScheme` を見て出し分けること
73
+ - `theme.color.semantic.light` を直接読むと、原因を名指しするエラーで落ちる(値を捏造して静かに間違った色を返すことはしない)。現在 mode の色は `useTheme().colors` から取る
74
+
75
+ > 現状 `color.primary` / `text-accent` / `elevation` / status の light 値は、単一 dark のテーマでも**必須のまま**。省略できるのは `color.semantic` の mode だけで、他の軸を「持たない」と宣言する仕組みは後続で入れる。
76
+
39
77
  ## 設計の核
40
78
 
41
79
  - **契約は共有、実装は各最適**: tokens / 禁止ルール / component 契約は `melta-contracts`(JSON)が SSOT。melta-app に token は持たない(二重化を物理防止)。
@@ -8,24 +8,63 @@
8
8
  * useColorScheme() を Provider 内で1回だけ読む(RN コア、追加依存なし)。
9
9
  *
10
10
  * context value は useMemo 必須(§3): 毎レンダで新オブジェクトを配ると全 consumer が再レンダする。
11
+ *
12
+ * theme 注入(Step 2-①): `theme` を渡すと消費者ブランドの theme で塗り替わる。未指定なら
13
+ * 従来どおり codegen 済みの `nativeTheme`(=既存消費者に破壊的変更なし)。渡せるのは
14
+ * `defineTheme()` の戻り値だけ(define-theme.ts 参照)。
11
15
  */
12
16
 
13
17
  import { createContext, useContext, useMemo } from "react";
14
18
  import { useColorScheme } from "react-native";
15
19
  import { nativeTheme } from "./native-theme.js";
20
+ import { defineTheme, isDev, resolveMode } from "./define-theme.js";
16
21
  import { jsx as _jsx } from "react/jsx-runtime";
17
22
  const ThemeContext = /*#__PURE__*/createContext(null);
23
+
24
+ /** 既定 theme。melta-contracts 由来の codegen 結果を注入経路と同じ形に通す(自分でも dogfood する)。 */
25
+ const DEFAULT_THEME = defineTheme({
26
+ ...nativeTheme,
27
+ id: "melta"
28
+ });
29
+
30
+ /** 同じ矛盾を毎レンダー報告しないためのラッチ(StrictMode の二重 render も1回に畳む)。 */
31
+ const reportedViolations = new Set();
32
+ function reportViolation(themeId, violation) {
33
+ const key = `${themeId ?? "(id 未設定)"}:${violation.requested}`;
34
+ if (reportedViolations.has(key)) return;
35
+ reportedViolations.add(key);
36
+ console.error(`melta: forcedMode="${violation.requested}" が指定されたが、この theme は ` + `colorScheme=${violation.colorScheme} なので "${violation.resolved}" で描画する。` + `useTheme().capabilities.colorScheme を見て、対応していない mode は UI 側で出さないこと。`);
37
+ }
18
38
  export function ThemeProvider({
19
39
  children,
20
- forcedMode
40
+ forcedMode,
41
+ theme
21
42
  }) {
22
43
  const system = useColorScheme();
23
- const mode = forcedMode ?? (system === "dark" ? "dark" : "light");
24
- const value = useMemo(() => ({
25
- theme: nativeTheme,
44
+ const activeTheme = theme ?? DEFAULT_THEME;
45
+ const {
26
46
  mode,
27
- colors: nativeTheme.color.semantic[mode]
28
- }), [mode]);
47
+ violation
48
+ } = resolveMode(activeTheme.capabilities.colorScheme, forcedMode, system === "dark" ? "dark" : "light");
49
+ if (isDev && violation) reportViolation(activeTheme.id, violation);
50
+ const value = useMemo(() => {
51
+ const colors = activeTheme.color.semantic[mode];
52
+ if (!colors) {
53
+ // resolveMode が capability に従って選んだ mode なので、ここに来るのは theme が壊れている場合だけ。
54
+ // 色が undefined のまま下流に流れると全 component が別々の場所で死ぬので、ここで落とす。
55
+ throw new Error(`melta: theme "${activeTheme.id ?? "(id 未設定)"}" に mode="${mode}" の semantic 色が無い。`);
56
+ }
57
+ return {
58
+ // 配布時の型は NativeTheme のまま(既存の全 style resolver の署名を維持するため)。
59
+ // 単一 colorScheme の theme では持たない mode のキーが実体に無く、型が実体より広い。
60
+ // 到達しうる読みは resolveMode が保証した現在 mode だけで、他 mode を直接読んだ場合は
61
+ // define-theme.ts の guard getter が原因を名指しして落とす。
62
+ theme: activeTheme,
63
+ mode,
64
+ colors,
65
+ capabilities: activeTheme.capabilities
66
+ };
67
+ }, [activeTheme, mode]);
29
68
  return /*#__PURE__*/_jsx(ThemeContext.Provider, {
30
69
  value: value,
31
70
  children: children
@@ -1 +1 @@
1
- {"version":3,"names":["createContext","useContext","useMemo","useColorScheme","nativeTheme","jsx","_jsx","ThemeContext","ThemeProvider","children","forcedMode","system","mode","value","theme","colors","color","semantic","Provider","useTheme","ctx","Error"],"sourceRoot":"../../../src","sources":["theme/ThemeProvider.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,aAAa,EAAEC,UAAU,EAAEC,OAAO,QAAwB,OAAO;AAC1E,SAASC,cAAc,QAAQ,cAAc;AAC7C,SAASC,WAAW,QAAQ,mBAAgB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAc7C,MAAMC,YAAY,gBAAGP,aAAa,CAA2B,IAAI,CAAC;AAWlE,OAAO,SAASQ,aAAaA,CAAC;EAAEC,QAAQ;EAAEC;AAA+B,CAAC,EAAE;EAC1E,MAAMC,MAAM,GAAGR,cAAc,CAAC,CAAC;EAC/B,MAAMS,IAAe,GAAGF,UAAU,KAAKC,MAAM,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;EAE5E,MAAME,KAAK,GAAGX,OAAO,CACnB,OAAO;IACLY,KAAK,EAAEV,WAAW;IAClBQ,IAAI;IACJG,MAAM,EAAEX,WAAW,CAACY,KAAK,CAACC,QAAQ,CAACL,IAAI;EACzC,CAAC,CAAC,EACF,CAACA,IAAI,CACP,CAAC;EAED,oBAAON,IAAA,CAACC,YAAY,CAACW,QAAQ;IAACL,KAAK,EAAEA,KAAM;IAAAJ,QAAA,EAAEA;EAAQ,CAAwB,CAAC;AAChF;AAEA,OAAO,SAASU,QAAQA,CAAA,EAAsB;EAC5C,MAAMC,GAAG,GAAGnB,UAAU,CAACM,YAAY,CAAC;EACpC,IAAI,CAACa,GAAG,EAAE;IACR,MAAM,IAAIC,KAAK,CAAC,yCAAyC,CAAC;EAC5D;EACA,OAAOD,GAAG;AACZ","ignoreList":[]}
1
+ {"version":3,"names":["createContext","useContext","useMemo","useColorScheme","nativeTheme","defineTheme","isDev","resolveMode","jsx","_jsx","ThemeContext","DEFAULT_THEME","id","reportedViolations","Set","reportViolation","themeId","violation","key","requested","has","add","console","error","colorScheme","resolved","ThemeProvider","children","forcedMode","theme","system","activeTheme","mode","capabilities","value","colors","color","semantic","Error","Provider","useTheme","ctx"],"sourceRoot":"../../../src","sources":["theme/ThemeProvider.tsx"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,aAAa,EAAEC,UAAU,EAAEC,OAAO,QAAwB,OAAO;AAC1E,SAASC,cAAc,QAAQ,cAAc;AAC7C,SAASC,WAAW,QAAQ,mBAAgB;AAC5C,SAASC,WAAW,EAAEC,KAAK,EAAEC,WAAW,QAAsF,mBAAgB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAsB/I,MAAMC,YAAY,gBAAGV,aAAa,CAA2B,IAAI,CAAC;;AAElE;AACA,MAAMW,aAAkC,GAAGN,WAAW,CAAC;EAAE,GAAGD,WAAW;EAAEQ,EAAE,EAAE;AAAQ,CAAC,CAAC;;AAEvF;AACA,MAAMC,kBAAkB,GAAG,IAAIC,GAAG,CAAS,CAAC;AAE5C,SAASC,eAAeA,CAACC,OAA2B,EAAEC,SAA6B,EAAQ;EACzF,MAAMC,GAAG,GAAG,GAAGF,OAAO,IAAI,UAAU,IAAIC,SAAS,CAACE,SAAS,EAAE;EAC7D,IAAIN,kBAAkB,CAACO,GAAG,CAACF,GAAG,CAAC,EAAE;EACjCL,kBAAkB,CAACQ,GAAG,CAACH,GAAG,CAAC;EAC3BI,OAAO,CAACC,KAAK,CACX,sBAAsBN,SAAS,CAACE,SAAS,uBAAuB,GAC9D,eAAeF,SAAS,CAACO,WAAW,SAASP,SAAS,CAACQ,QAAQ,UAAU,GACzE,qEACJ,CAAC;AACH;AAoBA,OAAO,SAASC,aAAaA,CAAC;EAAEC,QAAQ;EAAEC,UAAU;EAAEC;AAA0B,CAAC,EAAE;EACjF,MAAMC,MAAM,GAAG3B,cAAc,CAAC,CAAC;EAC/B,MAAM4B,WAAW,GAAGF,KAAK,IAAIlB,aAAa;EAE1C,MAAM;IAAEqB,IAAI;IAAEf;EAAU,CAAC,GAAGV,WAAW,CACrCwB,WAAW,CAACE,YAAY,CAACT,WAAW,EACpCI,UAAU,EACVE,MAAM,KAAK,MAAM,GAAG,MAAM,GAAG,OAC/B,CAAC;EACD,IAAIxB,KAAK,IAAIW,SAAS,EAAEF,eAAe,CAACgB,WAAW,CAACnB,EAAE,EAAEK,SAAS,CAAC;EAElE,MAAMiB,KAAK,GAAGhC,OAAO,CAAoB,MAAM;IAC7C,MAAMiC,MAAM,GAAGJ,WAAW,CAACK,KAAK,CAACC,QAAQ,CAACL,IAAI,CAAC;IAC/C,IAAI,CAACG,MAAM,EAAE;MACX;MACA;MACA,MAAM,IAAIG,KAAK,CACb,iBAAiBP,WAAW,CAACnB,EAAE,IAAI,UAAU,aAAaoB,IAAI,oBAChE,CAAC;IACH;IACA,OAAO;MACL;MACA;MACA;MACA;MACAH,KAAK,EAAEE,WAAqC;MAC5CC,IAAI;MACJG,MAAM;MACNF,YAAY,EAAEF,WAAW,CAACE;IAC5B,CAAC;EACH,CAAC,EAAE,CAACF,WAAW,EAAEC,IAAI,CAAC,CAAC;EAEvB,oBAAOvB,IAAA,CAACC,YAAY,CAAC6B,QAAQ;IAACL,KAAK,EAAEA,KAAM;IAAAP,QAAA,EAAEA;EAAQ,CAAwB,CAAC;AAChF;AAEA,OAAO,SAASa,QAAQA,CAAA,EAAsB;EAC5C,MAAMC,GAAG,GAAGxC,UAAU,CAACS,YAAY,CAAC;EACpC,IAAI,CAAC+B,GAAG,EAAE;IACR,MAAM,IAAIH,KAAK,CAAC,yCAAyC,CAAC;EAC5D;EACA,OAAOG,GAAG;AACZ","ignoreList":[]}
@@ -0,0 +1,360 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * define-theme — 消費者ブランドの theme を注入するための入口(設計書 Step 2-①)。
5
+ *
6
+ * melta 既定の theme は melta-contracts から codegen した `nativeTheme` だが、消費者が自分の
7
+ * ブランドトークンで塗り替えられる正規の口をここで提供する。`defineTheme()` を通した成果物だけを
8
+ * `<ThemeProvider theme={...}>` が受け取る(Chakra の `$$chakra` と同じ brand 方式。
9
+ * 生オブジェクトの直渡しを型で塞ぎ、validation を必ず1回通すため)。
10
+ *
11
+ * 設計の要点:
12
+ * - **capability は宣言でなく導出する**。「この theme は dark しか持たない」は
13
+ * `color.semantic` のキー集合そのものが語る(Primer の colorSchemes / DTCG Resolver の contexts と同じ流儀)。
14
+ * 別欄で宣言すると「宣言と実体の不一致」という新種の嘘が生まれ、それを警備する検査が要るだけになる。
15
+ * - **嘘の値で埋めない**。単一 colorScheme の theme は存在しない mode を **書かない**。
16
+ * ゼロ埋め・他 mode のコピー・センチネル値はいずれも「その mode が存在する」という嘘をトークンに残す。
17
+ * - **Proxy は使わない**。Hermes は Proxy が使われていなくても全プロパティアクセスに分岐が入る
18
+ * (hermes#33)。存在しない mode の検出は non-enumerable な getter で行う(下記)。
19
+ *
20
+ * 注: accent(primary パレット / text-accent)・elevation・status の light 値は、単一 dark の
21
+ * theme でも現状 **必須のまま**。①の射程は colorScheme 軸だけで、それらの capability 化は
22
+ * 後続(accent 依存の除去)で扱う。
23
+ */
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // dev 判定
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * 開発ビルドか。**裸の `__DEV__` を直接書かないこと。**
31
+ *
32
+ * `__DEV__` は Metro のグローバルで、Metro なら prod ビルド時に `false` へ畳み込まれて
33
+ * dev ブロックごと消える。一方 melta は package.json の exports の `import` / `default` 条件で
34
+ * 同じ ESM を非 Metro の web バンドラにも配っており、react-native-web は `__DEV__` を
35
+ * **定義しない**(vendored 側で `process.env.NODE_ENV` にローカル shim している)。
36
+ * そこで `typeof` で存在を確かめ、無い環境では `NODE_ENV === "development"` のときだけ有効にする
37
+ * (判定不能なら dev 機能を**切る**側に倒す = 本番に dev コストを漏らさない)。
38
+ * `process` を識別子として直接参照しないのは、それを書くと tsconfig.build.json(bob build)だけが
39
+ * TS2591 で落ちるため(typecheck は通ってしまうので気づけない)。
40
+ */
41
+ export const isDev = typeof __DEV__ !== "undefined" ? __DEV__ : globalThis.process?.env?.NODE_ENV === "development";
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // 型
45
+ // ---------------------------------------------------------------------------
46
+
47
+ /** theme が持つ配色の能力。`color.semantic` のキー集合から導出される(宣言ではない)。 */
48
+
49
+ /**
50
+ * 解決済みの capability 集合。context 経由で全 component が読める。
51
+ * 後続で accent / elevation の軸が増えるのもこの器(それらはキー集合から導出できないので宣言になる)。
52
+ */
53
+
54
+ /**
55
+ * `defineTheme()` の入力。`NativeTheme` との差は `color.semantic` が mode ごとに任意な点だけ。
56
+ * 持っていない mode は **書かない**(capability はキー集合から導出される)。
57
+ */
58
+
59
+ /**
60
+ * `defineTheme()` の成果物。`ThemeProvider` の `theme` prop が受け取れる唯一の形。
61
+ * `$$melta` brand により生オブジェクトの直渡しを型で塞ぐ(= validation を必ず1回通す)。
62
+ */
63
+
64
+ /** 解決した mode が theme の能力と食い違ったこと。ThemeProvider は dev で報告し、値は clamp する。 */
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // キー集合(型と実行時の二重管理を防ぐ: Record<K, true> で網羅性を tsc に検査させる)
68
+ // ---------------------------------------------------------------------------
69
+
70
+ function keysOf(map) {
71
+ return Object.keys(map);
72
+ }
73
+ const MODES = keysOf({
74
+ light: true,
75
+ dark: true
76
+ });
77
+ const SEMANTIC_KEYS = keysOf({
78
+ "bg-page": true,
79
+ "bg-page-alt": true,
80
+ "bg-surface": true,
81
+ "bg-surface-alt": true,
82
+ "text-heading": true,
83
+ "text-default": true,
84
+ "text-muted": true,
85
+ "border-default": true,
86
+ "border-strong": true,
87
+ "input-bg": true,
88
+ "input-border": true,
89
+ "text-accent": true,
90
+ "text-on-accent": true
91
+ });
92
+ const PRIMARY_KEYS = keysOf({
93
+ "50": true,
94
+ "100": true,
95
+ "200": true,
96
+ "300": true,
97
+ "400": true,
98
+ "500": true,
99
+ "600": true,
100
+ "700": true,
101
+ "800": true,
102
+ "900": true,
103
+ "950": true
104
+ });
105
+ const STATUS_KINDS = keysOf({
106
+ success: true,
107
+ warning: true,
108
+ danger: true
109
+ });
110
+ const STATUS_COLOR_KEYS = keysOf({
111
+ base: true,
112
+ subtleLight: true,
113
+ textLight: true,
114
+ subtleDark: true,
115
+ textDark: true
116
+ });
117
+ const SPACING_KEYS = keysOf({
118
+ "1": true,
119
+ "2": true,
120
+ "3": true,
121
+ "4": true,
122
+ "5": true,
123
+ "6": true,
124
+ "8": true,
125
+ "10": true,
126
+ "12": true,
127
+ "14": true,
128
+ "16": true
129
+ });
130
+ const RADIUS_KEYS = keysOf({
131
+ sm: true,
132
+ md: true,
133
+ lg: true,
134
+ full: true
135
+ });
136
+ const ELEVATION_KEYS = keysOf({
137
+ none: true,
138
+ sm: true,
139
+ md: true,
140
+ overlay: true
141
+ });
142
+ const FONT_SIZE_KEYS = keysOf({
143
+ xxs: true,
144
+ xs: true,
145
+ sm: true,
146
+ base: true,
147
+ lg: true,
148
+ xl: true,
149
+ "2xl": true,
150
+ "3xl": true
151
+ });
152
+ const FONT_WEIGHT_KEYS = keysOf({
153
+ normal: true,
154
+ medium: true,
155
+ semibold: true,
156
+ bold: true
157
+ });
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // capability の導出
161
+ // ---------------------------------------------------------------------------
162
+
163
+ /** theme が実際に値を持っている mode(enumerable なキーのみ = dev getter を踏まない)。 */
164
+ export function declaredModes(semantic) {
165
+ return MODES.filter(mode => Object.prototype.hasOwnProperty.call(semantic, mode));
166
+ }
167
+
168
+ /** colorScheme capability を `color.semantic` のキー集合から導出する。 */
169
+ export function deriveColorScheme(semantic) {
170
+ const modes = declaredModes(semantic);
171
+ if (modes.length === 2) return "light-dark";
172
+ if (modes[0] === "dark") return "single-dark";
173
+ if (modes[0] === "light") return "single-light";
174
+ throw new Error("melta: theme.color.semantic に light / dark のどちらも無い。最低1つの mode が要る。");
175
+ }
176
+
177
+ /** capability が実際に描ける mode。 */
178
+ export function supportedModes(colorScheme) {
179
+ if (colorScheme === "single-dark") return ["dark"];
180
+ if (colorScheme === "single-light") return ["light"];
181
+ return ["light", "dark"];
182
+ }
183
+
184
+ // ---------------------------------------------------------------------------
185
+ // mode 解決
186
+ // ---------------------------------------------------------------------------
187
+
188
+ /**
189
+ * 表示 mode を解決する(純関数。ThemeProvider はこの結果に従うだけで判定はしない)。
190
+ *
191
+ * - **OS 由来の不一致は無反応で clamp する**。「light テーマを作らない」は消費者の意図的な設計判断で、
192
+ * OS が light なのは環境の事実にすぎない。ここで警告を出すと light 設定のユーザ全員に出てしまう
193
+ * (CSS の `color-scheme` / Expo の `userInterfaceStyle` も黙って clamp する)。
194
+ * - **`forcedMode` との不一致は報告する**。宣言同士の矛盾=呼び出し側の間違いなので violation を返す。
195
+ * ただし throw はしない(clamp して描き続ける)。厳格に落としたいテストや compiler が
196
+ * violation を error に昇格させる側の責務にする。
197
+ */
198
+ export function resolveMode(colorScheme, forcedMode, systemMode) {
199
+ const supported = supportedModes(colorScheme);
200
+ const requested = forcedMode ?? systemMode;
201
+ if (supported.includes(requested)) return {
202
+ mode: requested
203
+ };
204
+ const resolved = supported[0];
205
+ // OS 由来の clamp は「事故」ではないので violation を立てない。
206
+ if (forcedMode === undefined) return {
207
+ mode: resolved
208
+ };
209
+ return {
210
+ mode: resolved,
211
+ violation: {
212
+ kind: "forced-mode-unsupported",
213
+ requested,
214
+ resolved,
215
+ colorScheme
216
+ }
217
+ };
218
+ }
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // validation(開発時のみ。純関数なのでテストからも直接呼べる)
222
+ // ---------------------------------------------------------------------------
223
+
224
+ function missingKeys(target, keys) {
225
+ if (target === null || typeof target !== "object") return [...keys].map(String);
226
+ return keys.filter(key => !Object.prototype.hasOwnProperty.call(target, key)).map(String);
227
+ }
228
+
229
+ /**
230
+ * theme の形を検査して問題の一覧を返す(空配列 = 問題なし)。
231
+ *
232
+ * 「宣言した mode の色が欠けている」「as キャストでキーが抜けた」といった、型を通り抜けた欠落を拾う。
233
+ * `defineTheme()` が dev で1回だけ呼ぶ。**render 中に呼ばないこと**(theme-ui は Provider の
234
+ * render body で毎レンダー全色を再帰走査して性能を落とした前例がある)。
235
+ */
236
+ export function validateTheme(options) {
237
+ const problems = [];
238
+ const where = options.id ? `theme "${options.id}"` : "theme";
239
+ const semantic = options.color?.semantic;
240
+ if (semantic === null || typeof semantic !== "object") {
241
+ problems.push(`${where}: color.semantic が無い`);
242
+ return problems;
243
+ }
244
+ const unknownModes = Object.keys(semantic).filter(key => !MODES.includes(key));
245
+ for (const key of unknownModes) {
246
+ problems.push(`${where}: color.semantic の未知のキー "${key}"(light / dark のみ)`);
247
+ }
248
+ const modes = declaredModes(semantic);
249
+ if (modes.length === 0) {
250
+ problems.push(`${where}: color.semantic に light / dark のどちらも無い`);
251
+ }
252
+ for (const mode of modes) {
253
+ const missing = missingKeys(semantic[mode], SEMANTIC_KEYS);
254
+ if (missing.length > 0) {
255
+ problems.push(`${where}: color.semantic.${mode} に欠けている色 — ${missing.join(", ")}`);
256
+ }
257
+ }
258
+ const groups = [["color.primary", options.color?.primary, PRIMARY_KEYS], ["spacing", options.spacing, SPACING_KEYS], ["radius", options.radius, RADIUS_KEYS], ["elevation", options.elevation, ELEVATION_KEYS], ["typography.fontSize", options.typography?.fontSize, FONT_SIZE_KEYS], ["typography.fontWeight", options.typography?.fontWeight, FONT_WEIGHT_KEYS], ["color.status", options.color?.status, STATUS_KINDS]];
259
+ for (const [label, target, keys] of groups) {
260
+ const missing = missingKeys(target, keys);
261
+ if (missing.length > 0) {
262
+ problems.push(`${where}: ${label} に欠けているキー — ${missing.join(", ")}`);
263
+ }
264
+ }
265
+ const status = options.color?.status;
266
+ if (status !== null && typeof status === "object") {
267
+ for (const kind of STATUS_KINDS) {
268
+ if (!Object.prototype.hasOwnProperty.call(status, kind)) continue;
269
+ const missing = missingKeys(status[kind], STATUS_COLOR_KEYS);
270
+ if (missing.length > 0) {
271
+ problems.push(`${where}: color.status.${kind} に欠けているキー — ${missing.join(", ")}`);
272
+ }
273
+ }
274
+ }
275
+ return problems;
276
+ }
277
+
278
+ // ---------------------------------------------------------------------------
279
+ // defineTheme
280
+ // ---------------------------------------------------------------------------
281
+
282
+ /**
283
+ * 持っていない mode に「読んだら落ちる」getter を置く(**dev / production 共通**)。
284
+ *
285
+ * 値は捏造しない(他 mode のコピーもゼロ埋めもしない)。放置して `undefined` にすると
286
+ * 実際に踏んだとき `Cannot read properties of undefined (reading 'bg-page')` としか出ず、
287
+ * どの theme のどの mode の話か分からない。**落ちること自体は getter の有無に関わらず同じ**なので、
288
+ * 原因を名指しするメッセージを本番のクラッシュレポートにも残す方を選ぶ。
289
+ *
290
+ * **non-enumerable にするのが要点**: spread / `Object.keys` / `JSON.stringify` /
291
+ * React DevTools の context 列挙では踏まれない。`theme.color.semantic.light` と
292
+ * 明示的に書いたときだけ落ちる。
293
+ *
294
+ * ホットパス(全 style resolver が引く `semantic[mode]`)にアクセサを混ぜることになるが、
295
+ * getter が付くのは **単一 colorScheme の theme を注入した場合だけ**。既定の light-dark theme
296
+ * には1つも付かないので、既存消費者の実行経路は変わらない。
297
+ */
298
+ function installMissingModeGuards(semantic, where, colorScheme) {
299
+ for (const mode of MODES) {
300
+ if (Object.prototype.hasOwnProperty.call(semantic, mode)) continue;
301
+ Object.defineProperty(semantic, mode, {
302
+ enumerable: false,
303
+ configurable: true,
304
+ get() {
305
+ throw new Error(`melta: ${where} は colorScheme=${colorScheme} なので color.semantic.${mode} を持たない。` + `現在の mode の色は useTheme().colors(または color.semantic[mode])から取ること。`);
306
+ }
307
+ });
308
+ }
309
+ }
310
+
311
+ /** dev 限定の再帰 freeze。enumerable なキーだけ辿るので上の guard getter は踏まない。 */
312
+ function deepFreeze(value) {
313
+ if (value === null || typeof value !== "object") return;
314
+ if (Object.isFrozen(value)) return;
315
+ Object.freeze(value);
316
+ for (const key of Object.keys(value)) {
317
+ deepFreeze(value[key]);
318
+ }
319
+ }
320
+
321
+ /**
322
+ * ブランド theme を組み立てる。`ThemeProvider` の `theme` prop に渡せるのはこの戻り値だけ。
323
+ *
324
+ * **module スコープで1回だけ呼ぶこと**(render の中で呼ぶと毎レンダー新しい参照になり、
325
+ * context の consumer が全部再レンダーする)。styled-components / Emotion が docs で
326
+ * 同じことを求めているのと同じ理由。
327
+ *
328
+ * dev では validateTheme を1回通し、問題があれば throw する(構造が壊れた theme は
329
+ * 後段で必ず事故になるので早く落とす)。production では検査ごと省く。
330
+ */
331
+ export function defineTheme(options) {
332
+ if (isDev) {
333
+ const problems = validateTheme(options);
334
+ if (problems.length > 0) {
335
+ throw new Error(`melta: theme の形が不正。\n- ${problems.join("\n- ")}`);
336
+ }
337
+ }
338
+ const semantic = {
339
+ ...options.color.semantic
340
+ };
341
+ const colorScheme = deriveColorScheme(semantic);
342
+ const theme = {
343
+ ...options,
344
+ color: {
345
+ ...options.color,
346
+ semantic
347
+ },
348
+ capabilities: {
349
+ colorScheme
350
+ },
351
+ $$melta: true
352
+ };
353
+
354
+ // guard は本番でも置く(値は捏造せず、踏んだときのメッセージだけ良くする)。
355
+ // freeze より先に置くこと(freeze 後は defineProperty できない)。
356
+ installMissingModeGuards(semantic, options.id ? `theme "${options.id}"` : "theme", colorScheme);
357
+ if (isDev) deepFreeze(theme);
358
+ return theme;
359
+ }
360
+ //# sourceMappingURL=define-theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["isDev","__DEV__","globalThis","process","env","NODE_ENV","keysOf","map","Object","keys","MODES","light","dark","SEMANTIC_KEYS","PRIMARY_KEYS","STATUS_KINDS","success","warning","danger","STATUS_COLOR_KEYS","base","subtleLight","textLight","subtleDark","textDark","SPACING_KEYS","RADIUS_KEYS","sm","md","lg","full","ELEVATION_KEYS","none","overlay","FONT_SIZE_KEYS","xxs","xs","xl","FONT_WEIGHT_KEYS","normal","medium","semibold","bold","declaredModes","semantic","filter","mode","prototype","hasOwnProperty","call","deriveColorScheme","modes","length","Error","supportedModes","colorScheme","resolveMode","forcedMode","systemMode","supported","requested","includes","resolved","undefined","violation","kind","missingKeys","target","String","key","validateTheme","options","problems","where","id","color","push","unknownModes","missing","join","groups","primary","spacing","radius","elevation","typography","fontSize","fontWeight","status","label","installMissingModeGuards","defineProperty","enumerable","configurable","get","deepFreeze","value","isFrozen","freeze","defineTheme","theme","capabilities","$$melta"],"sourceRoot":"../../../src","sources":["theme/define-theme.ts"],"mappings":"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","ignoreList":[]}
@@ -3,11 +3,13 @@
3
3
  /**
4
4
  * melta-app theme エントリ。
5
5
  * 型は types.ts、値は native-theme.ts(melta-contracts から自動生成)。
6
+ * 消費者ブランドの theme を注入する口は define-theme.ts(`defineTheme`)。
6
7
  */
7
8
 
8
9
  export * from "./types.js";
9
10
  export { nativeTheme } from "./native-theme.js";
10
11
  export { ThemeProvider, useTheme } from "./ThemeProvider.js";
12
+ export { defineTheme, validateTheme, resolveMode, supportedModes, deriveColorScheme, declaredModes } from "./define-theme.js";
11
13
  /**
12
14
  * letterSpacingRatio(em 相当の比率)を、適用先の fontSize から RN の letterSpacing(pt) に解決する。
13
15
  * 例: resolveLetterSpacing(18, theme.typography.letterSpacingRatio.body)
@@ -1 +1 @@
1
- {"version":3,"names":["nativeTheme","ThemeProvider","useTheme","resolveLetterSpacing","fontSize","ratio"],"sourceRoot":"../../../src","sources":["theme/index.ts"],"mappings":";;AAAA;AACA;AACA;AACA;;AAEA,cAAc,YAAS;AACvB,SAASA,WAAW,QAAQ,mBAAgB;AAC5C,SAASC,aAAa,EAAEC,QAAQ,QAAQ,oBAAiB;AAGzD;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,oBAAoBA,CAACC,QAAgB,EAAEC,KAAa,EAAU;EAC5E,OAAOD,QAAQ,GAAGC,KAAK;AACzB","ignoreList":[]}
1
+ {"version":3,"names":["nativeTheme","ThemeProvider","useTheme","defineTheme","validateTheme","resolveMode","supportedModes","deriveColorScheme","declaredModes","resolveLetterSpacing","fontSize","ratio"],"sourceRoot":"../../../src","sources":["theme/index.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;;AAEA,cAAc,YAAS;AACvB,SAASA,WAAW,QAAQ,mBAAgB;AAC5C,SAASC,aAAa,EAAEC,QAAQ,QAAQ,oBAAiB;AAEzD,SACEC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,cAAc,EACdC,iBAAiB,EACjBC,aAAa,QACR,mBAAgB;AASvB;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,oBAAoBA,CAACC,QAAgB,EAAEC,KAAa,EAAU;EAC5E,OAAOD,QAAQ,GAAGC,KAAK;AACzB","ignoreList":[]}
@@ -6,27 +6,48 @@
6
6
  * useColorScheme() を Provider 内で1回だけ読む(RN コア、追加依存なし)。
7
7
  *
8
8
  * context value は useMemo 必須(§3): 毎レンダで新オブジェクトを配ると全 consumer が再レンダする。
9
+ *
10
+ * theme 注入(Step 2-①): `theme` を渡すと消費者ブランドの theme で塗り替わる。未指定なら
11
+ * 従来どおり codegen 済みの `nativeTheme`(=既存消費者に破壊的変更なし)。渡せるのは
12
+ * `defineTheme()` の戻り値だけ(define-theme.ts 参照)。
9
13
  */
10
14
  import { type ReactNode } from "react";
11
- import type { NativeTheme, SemanticColors } from "./types";
12
- export type ThemeMode = "light" | "dark";
15
+ import { type ResolvedCapabilities, type ResolvedNativeTheme } from "./define-theme";
16
+ import type { NativeTheme, SemanticColors, ThemeMode } from "./types";
17
+ export type { ThemeMode };
13
18
  export interface ThemeContextValue {
14
- /** 正規化済み theme 全体(色以外の形状トークンもここから取る)。 */
19
+ /**
20
+ * 正規化済み theme 全体(色以外の形状トークンもここから取る)。
21
+ *
22
+ * 注: 単一 colorScheme の theme を注入した場合、`color.semantic` は**宣言された mode しか持たない**
23
+ * (型は両方あるように見える)。持たない側を直接読むと原因を名指しするエラーで落ちる。
24
+ * 現在 mode の色は `colors` を使うこと。
25
+ */
15
26
  theme: NativeTheme;
16
27
  /** 現在の表示モード。 */
17
28
  mode: ThemeMode;
18
29
  /** 現在 mode の semantic 色(= theme.color.semantic[mode])。最頻アクセスなので展開済みで配る。 */
19
30
  colors: SemanticColors;
31
+ /** theme が持つ能力(`color.semantic` のキー集合などから導出済み)。 */
32
+ capabilities: ResolvedCapabilities;
20
33
  }
21
34
  interface ThemeProviderProps {
22
35
  children: ReactNode;
23
36
  /**
24
37
  * 明示モード。指定時は OS の colorScheme を無視して固定する。
25
38
  * カタログの light/dark トグル(§6)や、特定画面の固定表示に使う。
39
+ *
40
+ * theme が対応していない mode を渡した場合は theme 側の mode に clamp する(描画は止めない)。
41
+ * dev ではその矛盾を console.error で1回だけ報告する。
26
42
  */
27
43
  forcedMode?: ThemeMode;
44
+ /**
45
+ * 消費者ブランドの theme。未指定なら melta 既定(`nativeTheme`)。
46
+ * `defineTheme()` の戻り値を **module スコープで保持したもの**を渡すこと
47
+ * (render 中に組み立てると毎レンダー参照が変わり、全 consumer が再レンダする)。
48
+ */
49
+ theme?: ResolvedNativeTheme;
28
50
  }
29
- export declare function ThemeProvider({ children, forcedMode }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
51
+ export declare function ThemeProvider({ children, forcedMode, theme }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
30
52
  export declare function useTheme(): ThemeContextValue;
31
- export {};
32
53
  //# sourceMappingURL=ThemeProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/theme/ThemeProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzC,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,KAAK,EAAE,WAAW,CAAC;IACnB,gBAAgB;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,2EAA2E;IAC3E,MAAM,EAAE,cAAc,CAAC;CACxB;AAID,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,kBAAkB,2CAczE;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../../../src/theme/ThemeProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3E,OAAO,EAAmC,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAA2B,MAAM,gBAAgB,CAAC;AAC/I,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEtE,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,KAAK,EAAE,WAAW,CAAC;IACnB,gBAAgB;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,2EAA2E;IAC3E,MAAM,EAAE,cAAc,CAAC;IACvB,mDAAmD;IACnD,YAAY,EAAE,oBAAoB,CAAC;CACpC;AAqBD,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC7B;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,kBAAkB,2CAiChF;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}