@colixsystems/widget-sdk 0.68.0 → 0.69.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 +3 -2
- package/dist/contract.cjs +61 -1
- package/dist/contract.js +61 -1
- package/dist/host.d.ts +11 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1 -0
- package/dist/index.native.js +1 -0
- package/dist/theme-components.cjs +6 -1
- package/dist/theme-components.js +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,9 +61,10 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
61
61
|
|
|
62
62
|
- **`useTheme()` gains a `components` slice.** It is HOST-OWNED plumbing, not an author API: by the time your component renders, the host has already folded the matching tokens into `props.style`. Do not read `theme.components` and do not re-apply it — you would double-apply the theme and defeat the author's own styling.
|
|
63
63
|
- **New host-only exports on `@colixsystems/widget-sdk/host`:** `normaliseThemeComponents(raw)` and `applyThemeComponentStyle(manifestId, theme, props)`. These are the platform-host surface (the web Player / Studio canvas and the exported Expo app), never the author API — one implementation, so the two hosts cannot diverge.
|
|
64
|
-
- **`CONTRACT.themeComponents` / `CONTRACT.themeComponentShadows`** publish the vocabulary: each scope's tokens, their value types and ranges, and the widget → style-field bindings. `themeTokens.components` defaults to `{}`.
|
|
64
|
+
- **`CONTRACT.themeComponents` / `CONTRACT.themeComponentShadows` / `CONTRACT.themeComponentGradient`** publish the vocabulary: each scope's tokens, their value types and ranges, and the widget → style-field bindings. `themeTokens.components` defaults to `{}`.
|
|
65
|
+
- **The `button` and `card` scopes carry a `gradient` token (sc-3727)** — `{ from: "#hex", to: "#hex", angle: 0-359 }`, painted through the `<Gradient>` primitive. It reaches a widget as an ordinary style field (`gradient`, `cardGradient`, `submitGradient`), so the author rule is unchanged: read `props.style`, and treat an explicit `null` as "this instance opted out of the app-wide gradient" rather than as unset.
|
|
65
66
|
|
|
66
|
-
`CONTRACT.version` → `1.
|
|
67
|
+
`CONTRACT.version` → `1.46.0`. Additive; no existing export changed signature, and an unthemed app renders identically.
|
|
67
68
|
|
|
68
69
|
### What's new in 0.66.0
|
|
69
70
|
|
package/dist/contract.cjs
CHANGED
|
@@ -107,6 +107,17 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
|
|
|
107
107
|
// cannot drift between what Mason may emit and what a host actually applies.
|
|
108
108
|
const THEME_COMPONENT_SHADOWS = Object.freeze(["none", "sm", "md", "lg"]);
|
|
109
109
|
|
|
110
|
+
// sc-3727 — the `gradient` token's value shape: two hex stops plus a CSS-degree
|
|
111
|
+
// angle, the same grammar as `themeConfig.backgroundGradient` minus the radial
|
|
112
|
+
// variant (a component fill projects through `<Gradient>`, which is linear-only
|
|
113
|
+
// on both hosts). Declared once so the coercion, the Studio control and the
|
|
114
|
+
// planner prompt agree on the bounds.
|
|
115
|
+
const THEME_COMPONENT_GRADIENT = Object.freeze({
|
|
116
|
+
angleMin: 0,
|
|
117
|
+
angleMax: 359,
|
|
118
|
+
defaultAngle: 180,
|
|
119
|
+
});
|
|
120
|
+
|
|
110
121
|
// The card-surface field names shared by every widget that paints its own card
|
|
111
122
|
// (frontend/src/components/widgets/_shared/cardStyle.js CARD_STYLE_SCHEMA).
|
|
112
123
|
const CARD_SURFACE_FIELDS = Object.freeze({
|
|
@@ -115,6 +126,7 @@ const CARD_SURFACE_FIELDS = Object.freeze({
|
|
|
115
126
|
radius: "cardRadius",
|
|
116
127
|
padding: "cardPadding",
|
|
117
128
|
shadow: "shadow",
|
|
129
|
+
gradient: "cardGradient",
|
|
118
130
|
});
|
|
119
131
|
|
|
120
132
|
// A form widget's submit button — the `button` scope reaches it through the
|
|
@@ -122,6 +134,7 @@ const CARD_SURFACE_FIELDS = Object.freeze({
|
|
|
122
134
|
const FORM_SUBMIT_FIELDS = Object.freeze({
|
|
123
135
|
background: "submitBackground",
|
|
124
136
|
textColor: "submitTextColor",
|
|
137
|
+
gradient: "submitGradient",
|
|
125
138
|
});
|
|
126
139
|
|
|
127
140
|
const THEME_COMPONENTS = Object.freeze({
|
|
@@ -134,6 +147,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
134
147
|
radius: Object.freeze({ type: "size", min: 0, max: 48, uiDefault: "radii.sm" }),
|
|
135
148
|
fontSize: Object.freeze({ type: "size", min: 8, max: 96, uiDefault: "typography.sizes.sm" }),
|
|
136
149
|
shadow: Object.freeze({ type: "shadow" }),
|
|
150
|
+
gradient: Object.freeze({ type: "gradient" }),
|
|
137
151
|
}),
|
|
138
152
|
targets: Object.freeze({
|
|
139
153
|
"appstudio.button": Object.freeze({
|
|
@@ -143,6 +157,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
143
157
|
radius: "radius",
|
|
144
158
|
fontSize: "fontSize",
|
|
145
159
|
shadow: "shadow",
|
|
160
|
+
gradient: "gradient",
|
|
146
161
|
}),
|
|
147
162
|
"appstudio.form-input": FORM_SUBMIT_FIELDS,
|
|
148
163
|
"appstudio.form-builder": FORM_SUBMIT_FIELDS,
|
|
@@ -156,6 +171,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
156
171
|
radius: Object.freeze({ type: "size", min: 0, max: 48, uiDefault: "radii.md" }),
|
|
157
172
|
padding: Object.freeze({ type: "size", min: 0, max: 64, uiDefault: "spacing.md" }),
|
|
158
173
|
shadow: Object.freeze({ type: "shadow" }),
|
|
174
|
+
gradient: Object.freeze({ type: "gradient" }),
|
|
159
175
|
}),
|
|
160
176
|
targets: Object.freeze({
|
|
161
177
|
"appstudio.user": CARD_SURFACE_FIELDS,
|
|
@@ -166,6 +182,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
166
182
|
"appstudio.notifications": CARD_SURFACE_FIELDS,
|
|
167
183
|
"appstudio.form-input": CARD_SURFACE_FIELDS,
|
|
168
184
|
"appstudio.form-builder": CARD_SURFACE_FIELDS,
|
|
185
|
+
"appstudio.user-management": CARD_SURFACE_FIELDS,
|
|
169
186
|
}),
|
|
170
187
|
}),
|
|
171
188
|
text: Object.freeze({
|
|
@@ -2150,7 +2167,21 @@ const CONTRACT = deepFreeze({
|
|
|
2150
2167
|
// (`@colixsystems/widget-sdk/host`), never the author surface. Additive: no
|
|
2151
2168
|
// export changed signature and a theme with no `components` key resolves to
|
|
2152
2169
|
// an empty override, rendering identically to before.
|
|
2153
|
-
|
|
2170
|
+
// 1.46.0: additive (sc-3727) — a `gradient` token type on the per-component
|
|
2171
|
+
// vocabulary. The `button` and `card` scopes each gain a `gradient` token
|
|
2172
|
+
// whose value is `{ from: "#hex", to: "#hex", angle: 0-359 }` — the
|
|
2173
|
+
// `backgroundGradient` grammar minus the radial variant, because a component
|
|
2174
|
+
// fill paints through the `<Gradient>` primitive, which is linear on both
|
|
2175
|
+
// hosts. `themeComponentGradient` publishes the angle bounds + default so the
|
|
2176
|
+
// coercion, the Studio control and the planner prompt cannot disagree. The
|
|
2177
|
+
// token binds to per-instance style fields the target widgets read
|
|
2178
|
+
// (`gradient` on Button, `cardGradient` on every card surface,
|
|
2179
|
+
// `submitGradient` on a form's submit button), so the theme value is a
|
|
2180
|
+
// DEFAULT an author overrides per instance — or suppresses with an explicit
|
|
2181
|
+
// null, which is how one button stays flat while the rest are gradiented.
|
|
2182
|
+
// Additive: no export changed signature and a theme with no gradient token
|
|
2183
|
+
// renders exactly as before.
|
|
2184
|
+
version: "1.46.0",
|
|
2154
2185
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2155
2186
|
hooks: HOOKS,
|
|
2156
2187
|
primitives: PRIMITIVES,
|
|
@@ -2163,6 +2194,7 @@ const CONTRACT = deepFreeze({
|
|
|
2163
2194
|
themeTokens: DEFAULT_THEME_TOKENS,
|
|
2164
2195
|
themeComponents: THEME_COMPONENTS,
|
|
2165
2196
|
themeComponentShadows: THEME_COMPONENT_SHADOWS,
|
|
2197
|
+
themeComponentGradient: THEME_COMPONENT_GRADIENT,
|
|
2166
2198
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
2167
2199
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|
|
2168
2200
|
bannedApis: BANNED_APIS,
|
|
@@ -2305,6 +2337,33 @@ function gradientAngleToVector(angle) {
|
|
|
2305
2337
|
};
|
|
2306
2338
|
}
|
|
2307
2339
|
|
|
2340
|
+
// Alpha is allowed here (unlike HEX_RE) because the Mason build runner already
|
|
2341
|
+
// persists 8-digit component colours; the host must not drop what it accepted.
|
|
2342
|
+
const GRADIENT_HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
2343
|
+
|
|
2344
|
+
/**
|
|
2345
|
+
* sc-3727 — normalise a component `gradient` to `{ from, to, angle }`, or `null`.
|
|
2346
|
+
*
|
|
2347
|
+
* ONE validator for a value arriving by two routes — the theme token and the
|
|
2348
|
+
* author's never-coerced per-instance `props.style` — so `set_theme` and the
|
|
2349
|
+
* renderer cannot disagree about the same value (CLAUDE.md §3). Both stops are
|
|
2350
|
+
* required: a one-stop gradient would paint a fill the author never chose. The
|
|
2351
|
+
* hex grammar is also the injection guard — these stops are interpolated into
|
|
2352
|
+
* CSS and into generated export source.
|
|
2353
|
+
*/
|
|
2354
|
+
function normaliseComponentGradient(raw) {
|
|
2355
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
2356
|
+
const from = typeof raw.from === "string" ? raw.from.trim() : "";
|
|
2357
|
+
const to = typeof raw.to === "string" ? raw.to.trim() : "";
|
|
2358
|
+
if (!GRADIENT_HEX_RE.test(from) || !GRADIENT_HEX_RE.test(to)) return null;
|
|
2359
|
+
const { defaultAngle } = THEME_COMPONENT_GRADIENT;
|
|
2360
|
+
// Read once — a second read of an accessor could return a different value and
|
|
2361
|
+
// land NaN in a style.
|
|
2362
|
+
const angle = raw.angle;
|
|
2363
|
+
const deg = Number.isFinite(angle) ? Math.round(angle) : defaultAngle;
|
|
2364
|
+
return { from, to, angle: ((deg % 360) + 360) % 360 };
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2308
2367
|
module.exports = {
|
|
2309
2368
|
CONTRACT,
|
|
2310
2369
|
isHookAllowed,
|
|
@@ -2315,6 +2374,7 @@ module.exports = {
|
|
|
2315
2374
|
readableTextColor,
|
|
2316
2375
|
deriveAccentTints,
|
|
2317
2376
|
gradientAngleToVector,
|
|
2377
|
+
normaliseComponentGradient,
|
|
2318
2378
|
widgetTranslationPrefix,
|
|
2319
2379
|
widgetTranslationKey,
|
|
2320
2380
|
sharedTranslationPrefix,
|
package/dist/contract.js
CHANGED
|
@@ -107,6 +107,17 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
|
|
|
107
107
|
// cannot drift between what Mason may emit and what a host actually applies.
|
|
108
108
|
const THEME_COMPONENT_SHADOWS = Object.freeze(["none", "sm", "md", "lg"]);
|
|
109
109
|
|
|
110
|
+
// sc-3727 — the `gradient` token's value shape: two hex stops plus a CSS-degree
|
|
111
|
+
// angle, the same grammar as `themeConfig.backgroundGradient` minus the radial
|
|
112
|
+
// variant (a component fill projects through `<Gradient>`, which is linear-only
|
|
113
|
+
// on both hosts). Declared once so the coercion, the Studio control and the
|
|
114
|
+
// planner prompt agree on the bounds.
|
|
115
|
+
const THEME_COMPONENT_GRADIENT = Object.freeze({
|
|
116
|
+
angleMin: 0,
|
|
117
|
+
angleMax: 359,
|
|
118
|
+
defaultAngle: 180,
|
|
119
|
+
});
|
|
120
|
+
|
|
110
121
|
// The card-surface field names shared by every widget that paints its own card
|
|
111
122
|
// (frontend/src/components/widgets/_shared/cardStyle.js CARD_STYLE_SCHEMA).
|
|
112
123
|
const CARD_SURFACE_FIELDS = Object.freeze({
|
|
@@ -115,6 +126,7 @@ const CARD_SURFACE_FIELDS = Object.freeze({
|
|
|
115
126
|
radius: "cardRadius",
|
|
116
127
|
padding: "cardPadding",
|
|
117
128
|
shadow: "shadow",
|
|
129
|
+
gradient: "cardGradient",
|
|
118
130
|
});
|
|
119
131
|
|
|
120
132
|
// A form widget's submit button — the `button` scope reaches it through the
|
|
@@ -122,6 +134,7 @@ const CARD_SURFACE_FIELDS = Object.freeze({
|
|
|
122
134
|
const FORM_SUBMIT_FIELDS = Object.freeze({
|
|
123
135
|
background: "submitBackground",
|
|
124
136
|
textColor: "submitTextColor",
|
|
137
|
+
gradient: "submitGradient",
|
|
125
138
|
});
|
|
126
139
|
|
|
127
140
|
const THEME_COMPONENTS = Object.freeze({
|
|
@@ -134,6 +147,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
134
147
|
radius: Object.freeze({ type: "size", min: 0, max: 48, uiDefault: "radii.sm" }),
|
|
135
148
|
fontSize: Object.freeze({ type: "size", min: 8, max: 96, uiDefault: "typography.sizes.sm" }),
|
|
136
149
|
shadow: Object.freeze({ type: "shadow" }),
|
|
150
|
+
gradient: Object.freeze({ type: "gradient" }),
|
|
137
151
|
}),
|
|
138
152
|
targets: Object.freeze({
|
|
139
153
|
"appstudio.button": Object.freeze({
|
|
@@ -143,6 +157,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
143
157
|
radius: "radius",
|
|
144
158
|
fontSize: "fontSize",
|
|
145
159
|
shadow: "shadow",
|
|
160
|
+
gradient: "gradient",
|
|
146
161
|
}),
|
|
147
162
|
"appstudio.form-input": FORM_SUBMIT_FIELDS,
|
|
148
163
|
"appstudio.form-builder": FORM_SUBMIT_FIELDS,
|
|
@@ -156,6 +171,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
156
171
|
radius: Object.freeze({ type: "size", min: 0, max: 48, uiDefault: "radii.md" }),
|
|
157
172
|
padding: Object.freeze({ type: "size", min: 0, max: 64, uiDefault: "spacing.md" }),
|
|
158
173
|
shadow: Object.freeze({ type: "shadow" }),
|
|
174
|
+
gradient: Object.freeze({ type: "gradient" }),
|
|
159
175
|
}),
|
|
160
176
|
targets: Object.freeze({
|
|
161
177
|
"appstudio.user": CARD_SURFACE_FIELDS,
|
|
@@ -166,6 +182,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
166
182
|
"appstudio.notifications": CARD_SURFACE_FIELDS,
|
|
167
183
|
"appstudio.form-input": CARD_SURFACE_FIELDS,
|
|
168
184
|
"appstudio.form-builder": CARD_SURFACE_FIELDS,
|
|
185
|
+
"appstudio.user-management": CARD_SURFACE_FIELDS,
|
|
169
186
|
}),
|
|
170
187
|
}),
|
|
171
188
|
text: Object.freeze({
|
|
@@ -2150,7 +2167,21 @@ const CONTRACT = deepFreeze({
|
|
|
2150
2167
|
// (`@colixsystems/widget-sdk/host`), never the author surface. Additive: no
|
|
2151
2168
|
// export changed signature and a theme with no `components` key resolves to
|
|
2152
2169
|
// an empty override, rendering identically to before.
|
|
2153
|
-
|
|
2170
|
+
// 1.46.0: additive (sc-3727) — a `gradient` token type on the per-component
|
|
2171
|
+
// vocabulary. The `button` and `card` scopes each gain a `gradient` token
|
|
2172
|
+
// whose value is `{ from: "#hex", to: "#hex", angle: 0-359 }` — the
|
|
2173
|
+
// `backgroundGradient` grammar minus the radial variant, because a component
|
|
2174
|
+
// fill paints through the `<Gradient>` primitive, which is linear on both
|
|
2175
|
+
// hosts. `themeComponentGradient` publishes the angle bounds + default so the
|
|
2176
|
+
// coercion, the Studio control and the planner prompt cannot disagree. The
|
|
2177
|
+
// token binds to per-instance style fields the target widgets read
|
|
2178
|
+
// (`gradient` on Button, `cardGradient` on every card surface,
|
|
2179
|
+
// `submitGradient` on a form's submit button), so the theme value is a
|
|
2180
|
+
// DEFAULT an author overrides per instance — or suppresses with an explicit
|
|
2181
|
+
// null, which is how one button stays flat while the rest are gradiented.
|
|
2182
|
+
// Additive: no export changed signature and a theme with no gradient token
|
|
2183
|
+
// renders exactly as before.
|
|
2184
|
+
version: "1.46.0",
|
|
2154
2185
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2155
2186
|
hooks: HOOKS,
|
|
2156
2187
|
primitives: PRIMITIVES,
|
|
@@ -2163,6 +2194,7 @@ const CONTRACT = deepFreeze({
|
|
|
2163
2194
|
themeTokens: DEFAULT_THEME_TOKENS,
|
|
2164
2195
|
themeComponents: THEME_COMPONENTS,
|
|
2165
2196
|
themeComponentShadows: THEME_COMPONENT_SHADOWS,
|
|
2197
|
+
themeComponentGradient: THEME_COMPONENT_GRADIENT,
|
|
2166
2198
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
2167
2199
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|
|
2168
2200
|
bannedApis: BANNED_APIS,
|
|
@@ -2305,6 +2337,33 @@ function gradientAngleToVector(angle) {
|
|
|
2305
2337
|
};
|
|
2306
2338
|
}
|
|
2307
2339
|
|
|
2340
|
+
// Alpha is allowed here (unlike HEX_RE) because the Mason build runner already
|
|
2341
|
+
// persists 8-digit component colours; the host must not drop what it accepted.
|
|
2342
|
+
const GRADIENT_HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
2343
|
+
|
|
2344
|
+
/**
|
|
2345
|
+
* sc-3727 — normalise a component `gradient` to `{ from, to, angle }`, or `null`.
|
|
2346
|
+
*
|
|
2347
|
+
* ONE validator for a value arriving by two routes — the theme token and the
|
|
2348
|
+
* author's never-coerced per-instance `props.style` — so `set_theme` and the
|
|
2349
|
+
* renderer cannot disagree about the same value (CLAUDE.md §3). Both stops are
|
|
2350
|
+
* required: a one-stop gradient would paint a fill the author never chose. The
|
|
2351
|
+
* hex grammar is also the injection guard — these stops are interpolated into
|
|
2352
|
+
* CSS and into generated export source.
|
|
2353
|
+
*/
|
|
2354
|
+
function normaliseComponentGradient(raw) {
|
|
2355
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
2356
|
+
const from = typeof raw.from === "string" ? raw.from.trim() : "";
|
|
2357
|
+
const to = typeof raw.to === "string" ? raw.to.trim() : "";
|
|
2358
|
+
if (!GRADIENT_HEX_RE.test(from) || !GRADIENT_HEX_RE.test(to)) return null;
|
|
2359
|
+
const { defaultAngle } = THEME_COMPONENT_GRADIENT;
|
|
2360
|
+
// Read once — a second read of an accessor could return a different value and
|
|
2361
|
+
// land NaN in a style.
|
|
2362
|
+
const angle = raw.angle;
|
|
2363
|
+
const deg = Number.isFinite(angle) ? Math.round(angle) : defaultAngle;
|
|
2364
|
+
return { from, to, angle: ((deg % 360) + 360) % 360 };
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2308
2367
|
export {
|
|
2309
2368
|
CONTRACT,
|
|
2310
2369
|
isHookAllowed,
|
|
@@ -2315,6 +2374,7 @@ export {
|
|
|
2315
2374
|
readableTextColor,
|
|
2316
2375
|
deriveAccentTints,
|
|
2317
2376
|
gradientAngleToVector,
|
|
2377
|
+
normaliseComponentGradient,
|
|
2318
2378
|
widgetTranslationPrefix,
|
|
2319
2379
|
widgetTranslationKey,
|
|
2320
2380
|
sharedTranslationPrefix,
|
package/dist/host.d.ts
CHANGED
|
@@ -12,7 +12,17 @@ export function resolveProps<T = Record<string, unknown>>(
|
|
|
12
12
|
props: unknown,
|
|
13
13
|
): T;
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// sc-3727: a `gradient` token's value, the one non-scalar token type.
|
|
16
|
+
export interface ThemeComponentGradient {
|
|
17
|
+
from: string;
|
|
18
|
+
to: string;
|
|
19
|
+
angle: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ThemeComponentStyle = Record<
|
|
23
|
+
string,
|
|
24
|
+
string | number | ThemeComponentGradient
|
|
25
|
+
>;
|
|
16
26
|
export type ThemeComponents = Record<string, ThemeComponentStyle>;
|
|
17
27
|
|
|
18
28
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1432,6 +1432,22 @@ export function gradientAngleToVector(angle: number): {
|
|
|
1432
1432
|
end: { x: number; y: number };
|
|
1433
1433
|
};
|
|
1434
1434
|
|
|
1435
|
+
export interface ComponentGradient {
|
|
1436
|
+
from: string;
|
|
1437
|
+
to: string;
|
|
1438
|
+
angle: number;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* Normalise a component `gradient` style value to `{ from, to, angle }`, or null
|
|
1443
|
+
* when it is unusable. Both stops are required; the angle wraps into 0-359.
|
|
1444
|
+
* Shared by the theme-token coercion and the widget render path, so the same
|
|
1445
|
+
* value cannot resolve two ways.
|
|
1446
|
+
*/
|
|
1447
|
+
export function normaliseComponentGradient(
|
|
1448
|
+
raw: unknown,
|
|
1449
|
+
): ComponentGradient | null;
|
|
1450
|
+
|
|
1435
1451
|
// Linter
|
|
1436
1452
|
export interface LintFinding {
|
|
1437
1453
|
rule: string;
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// widget reads. The Mason build runner validates `set_theme` against the SAME
|
|
24
24
|
// literal, so what a planner may emit and what a host applies cannot diverge.
|
|
25
25
|
|
|
26
|
-
const { CONTRACT } = require("./contract.cjs");
|
|
26
|
+
const { CONTRACT, normaliseComponentGradient } = require("./contract.cjs");
|
|
27
27
|
|
|
28
28
|
// 3-, 6- and 8-digit hex, matching what the build runner persists — the host
|
|
29
29
|
// must never drop a colour the runner already accepted.
|
|
@@ -47,6 +47,11 @@ function coerceToken(def, value) {
|
|
|
47
47
|
if (def.type === "shadow") {
|
|
48
48
|
return CONTRACT.themeComponentShadows.includes(value) ? value : undefined;
|
|
49
49
|
}
|
|
50
|
+
// sc-3727: the gradient value has its own normaliser on the contract, shared
|
|
51
|
+
// with the widget render path so both routes agree (CLAUDE.md §3).
|
|
52
|
+
if (def.type === "gradient") {
|
|
53
|
+
return normaliseComponentGradient(value) || undefined;
|
|
54
|
+
}
|
|
50
55
|
return undefined;
|
|
51
56
|
}
|
|
52
57
|
|
package/dist/theme-components.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// widget reads. The Mason build runner validates `set_theme` against the SAME
|
|
16
16
|
// literal, so what a planner may emit and what a host applies cannot diverge.
|
|
17
17
|
|
|
18
|
-
import { CONTRACT } from "./contract.js";
|
|
18
|
+
import { CONTRACT, normaliseComponentGradient } from "./contract.js";
|
|
19
19
|
|
|
20
20
|
// 3-, 6- and 8-digit hex, matching what the build runner persists — the host
|
|
21
21
|
// must never drop a colour the runner already accepted.
|
|
@@ -39,6 +39,11 @@ function coerceToken(def, value) {
|
|
|
39
39
|
if (def.type === "shadow") {
|
|
40
40
|
return CONTRACT.themeComponentShadows.includes(value) ? value : undefined;
|
|
41
41
|
}
|
|
42
|
+
// sc-3727: the gradient value has its own normaliser on the contract, shared
|
|
43
|
+
// with the widget render path so both routes agree (CLAUDE.md §3).
|
|
44
|
+
if (def.type === "gradient") {
|
|
45
|
+
return normaliseComponentGradient(value) || undefined;
|
|
46
|
+
}
|
|
42
47
|
return undefined;
|
|
43
48
|
}
|
|
44
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|