@colixsystems/widget-sdk 0.70.0 → 0.72.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 +15 -3
- package/dist/contract.cjs +51 -7
- package/dist/contract.js +51 -7
- package/dist/index.d.ts +2 -0
- package/dist/theme-components.cjs +13 -0
- package/dist/theme-components.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,19 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
54
54
|
|
|
55
55
|
## Status
|
|
56
56
|
|
|
57
|
-
`v0.
|
|
57
|
+
`v0.71.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
58
|
+
|
|
59
|
+
### What's new in 0.71.0
|
|
60
|
+
|
|
61
|
+
**A theme can set leading, tracking and case for text app-wide (sc-3857).** The `text` scope of `themeConfig.components` gains three tokens beside `color` and `fontSize`:
|
|
62
|
+
|
|
63
|
+
- **`lineHeight`** — leading as a MULTIPLE of the font size (0.8–3), so one app-wide value stays right at every size. Tight leading (1.0–1.15) is what makes a 32px+ headline read as a headline rather than as oversized body text.
|
|
64
|
+
- **`letterSpacing`** — tracking in pixels (−2 to 20). Wide positive tracking is what makes a small uppercase kicker read as a kicker.
|
|
65
|
+
- **`textTransform`** — `none` | `uppercase` | `capitalize`, published as `CONTRACT.themeComponentTextTransforms`.
|
|
66
|
+
|
|
67
|
+
Two new token value types back them: **`decimal`** (a clamped, 2-decimal number — `size` rounds, so it cannot carry a 1.05 multiplier) and **`textTransform`**. Each token binds to the identically named per-instance style field the target widgets read, so the author rule is unchanged: read `props.style` / `useWidgetStyle()`, and a per-instance value still wins over the app-wide token.
|
|
68
|
+
|
|
69
|
+
`CONTRACT.version` → `1.48.0`. Additive: no existing export changed signature, and a theme with none of the new tokens renders exactly as before.
|
|
58
70
|
|
|
59
71
|
### What's new in 0.70.0
|
|
60
72
|
|
|
@@ -420,7 +432,7 @@ A runtime schema resolver so widgets can render by column type.
|
|
|
420
432
|
The tenant's **Theme Settings** now flow all the way into `useTheme()`.
|
|
421
433
|
|
|
422
434
|
- **`themeTokens.colors` gains `secondary` + `onSecondary`.** `useTheme().colors.secondary` reflects the tenant's *Secondary Color* picker (with `onSecondary` as its readable contrast color), alongside the existing `primary` / `onPrimary`. Built-in widgets like Button use it for their secondary variant; third-party widgets can use it for a branded second accent. The full `colors` shape is now `{ primary, onPrimary, secondary, onSecondary, surface, onSurface, surfaceMuted, onSurfaceMuted, border, danger, success, warning, info }`.
|
|
423
|
-
- **`colors.primary` / `colors.secondary` / `typography.fontFamily` are tenant-resolved.** The host maps the Studio Theme Settings blob (Primary Color, Secondary Color, Global Font) onto the default tokens before handing them to `useTheme()`, on both the live Player and the exported app — so a widget that reads tokens re-themes automatically.
|
|
435
|
+
- **`colors.primary` / `colors.secondary` / `typography.fontFamily` / `typography.headingFontFamily` are tenant-resolved.** The host maps the Studio Theme Settings blob (Primary Color, Secondary Color, Global Font, Heading Font) onto the default tokens before handing them to `useTheme()`, on both the live Player and the exported app — so a widget that reads tokens re-themes automatically. Both families are loaded by the Player and bundled into the exported app, so they render the same on web and native.
|
|
424
436
|
- **`CONTRACT.version` → `1.6.0`** (additive: two new `themeTokens.colors` keys). No existing export changed signature.
|
|
425
437
|
|
|
426
438
|
### What's new in 0.15.0
|
|
@@ -550,7 +562,7 @@ A widget that works but looks unfinished is only half done. `useTheme()` is the
|
|
|
550
562
|
|
|
551
563
|
- **Pull spacing and corners from tokens.** Use `theme.spacing` (`xs / sm / md / lg / xl`) for a consistent padding and gap rhythm, and `theme.radii` (`sm / md / lg / pill`) for corners — `radii.lg` for cards and hero surfaces, `radii.md` for controls nested inside one. Don't hardcode raw pixel values.
|
|
552
564
|
- **Build a hierarchy.** A clear title (large, bold, `colors.onSurface`), body text, and muted captions in `colors.onSurfaceMuted` — three weights, not one flat size. Reserve full-strength `colors.primary` (with `colors.onPrimary` for text on it) for the single most important action or metric.
|
|
553
|
-
- **Set the theme font on every `Text`.** React Native `Text` does not inherit `fontFamily` from a parent, so a text element that omits it falls back to the system font and ignores the workspace's configured font. Put `theme.typography.fontFamily` on every text style (a shared `StyleSheet` built from `theme` keeps it in one place) and size text with `theme.typography.sizes`.
|
|
565
|
+
- **Set the theme font on every `Text`.** React Native `Text` does not inherit `fontFamily` from a parent, so a text element that omits it falls back to the system font and ignores the workspace's configured font. Put `theme.typography.fontFamily` on every text style (a shared `StyleSheet` built from `theme` keeps it in one place) and size text with `theme.typography.sizes`. Use `theme.typography.headingFontFamily` instead on your widget's HEADING-tier text — titles and display numbers — so a workspace that pairs a display face with a body face gets that pairing inside your widget too. It defaults to `fontFamily`, so an unpaired workspace looks identical.
|
|
554
566
|
- **Contain and elevate.** Wrap a logical unit in a surface: `colors.surface` + padding + `radii.lg` + `...theme.elevation.sm`. Give it the elevation **or** a `colors.border` hairline, not both — and prefer the elevation, because a hairline-only card reads as a wireframe. `theme.elevation` is a token table you spread into a style (`...theme.elevation.md`), covering `none / sm / md / lg / xl`; never hand-write `shadowOpacity` / `shadowRadius` / `boxShadow`. Use the status roles (`danger / success / warning / info`) for state.
|
|
555
567
|
- **Tint the supporting cast.** `colors.primarySoft` is a tint of the workspace accent over the surface and `colors.onPrimarySoft` is guaranteed readable on it (WCAG AA, on light and dark themes alike). Use the pair for chips, secondary buttons, progress tracks, icon badges and selected rows. One saturated accent moment surrounded by several pale echoes of the same hue is what reads as designed — a row of grey-outlined buttons reads as a form. Never hand-mix a tint with `rgba(...)` or a translucent overlay.
|
|
556
568
|
- **Spend one gradient.** `<Gradient colors={[theme.colors.primary, theme.colors.primaryStrong]} angle={160} style={…}>` is a `View` that paints a gradient behind its children, so it replaces the `View` you'd otherwise give a flat `backgroundColor`. `angle` is CSS degrees (0 = to top, 90 = to right, default 180); text on it uses `colors.onPrimary`. Exactly **one** per widget — on the focal element — and never behind body text. Both hosts render it identically (web paints CSS, native uses `expo-linear-gradient`), so there is no per-platform branching to write; don't import `expo-linear-gradient` yourself and don't write a `backgroundImage` string.
|
package/dist/contract.cjs
CHANGED
|
@@ -76,6 +76,10 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
|
|
|
76
76
|
typography: Object.freeze({
|
|
77
77
|
fontFamily:
|
|
78
78
|
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
79
|
+
// The display face for heading-tier text. Defaults to `fontFamily` — an app
|
|
80
|
+
// that never pairs renders on one typeface exactly as before.
|
|
81
|
+
headingFontFamily:
|
|
82
|
+
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
79
83
|
sizes: Object.freeze({ xs: 12, sm: 14, md: 16, lg: 20, xl: 24, xxl: 32 }),
|
|
80
84
|
}),
|
|
81
85
|
// REQ-THEME-15: the tenant's per-component style tokens, folded in by the
|
|
@@ -107,6 +111,14 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
|
|
|
107
111
|
// cannot drift between what Mason may emit and what a host actually applies.
|
|
108
112
|
const THEME_COMPONENT_SHADOWS = Object.freeze(["none", "sm", "md", "lg"]);
|
|
109
113
|
|
|
114
|
+
// sc-3857 — the `textTransform` token's closed enum, matching the per-instance
|
|
115
|
+
// Text/Label/Data Value style field and React Native's own vocabulary.
|
|
116
|
+
const THEME_COMPONENT_TEXT_TRANSFORMS = Object.freeze([
|
|
117
|
+
"none",
|
|
118
|
+
"uppercase",
|
|
119
|
+
"capitalize",
|
|
120
|
+
]);
|
|
121
|
+
|
|
110
122
|
// sc-3727 — the `gradient` token's value shape: two hex stops plus a CSS-degree
|
|
111
123
|
// angle, the same grammar as `themeConfig.backgroundGradient` minus the radial
|
|
112
124
|
// variant (a component fill projects through `<Gradient>`, which is linear-only
|
|
@@ -129,6 +141,16 @@ const CARD_SURFACE_FIELDS = Object.freeze({
|
|
|
129
141
|
gradient: "cardGradient",
|
|
130
142
|
});
|
|
131
143
|
|
|
144
|
+
// The text field names every text-bearing built-in reads
|
|
145
|
+
// (frontend/src/components/widgets/_shared/textStyle.js TYPOGRAPHY_STYLE_FIELDS).
|
|
146
|
+
const TEXT_TYPOGRAPHY_FIELDS = Object.freeze({
|
|
147
|
+
color: "color",
|
|
148
|
+
fontSize: "fontSize",
|
|
149
|
+
lineHeight: "lineHeight",
|
|
150
|
+
letterSpacing: "letterSpacing",
|
|
151
|
+
textTransform: "textTransform",
|
|
152
|
+
});
|
|
153
|
+
|
|
132
154
|
// A form widget's submit button — the `button` scope reaches it through the
|
|
133
155
|
// form's own submit* fields, so "make the buttons coral" does not skip forms.
|
|
134
156
|
const FORM_SUBMIT_FIELDS = Object.freeze({
|
|
@@ -190,14 +212,16 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
190
212
|
tokens: Object.freeze({
|
|
191
213
|
color: Object.freeze({ type: "color", uiDefault: "colors.onSurface" }),
|
|
192
214
|
fontSize: Object.freeze({ type: "size", min: 8, max: 96, uiDefault: "typography.sizes.md" }),
|
|
215
|
+
// sc-3857 — leading as a MULTIPLE of the font size, so one app-wide value
|
|
216
|
+
// is right at every size; `size` cannot carry it because it rounds.
|
|
217
|
+
lineHeight: Object.freeze({ type: "decimal", min: 0.8, max: 3, step: 0.05 }),
|
|
218
|
+
letterSpacing: Object.freeze({ type: "decimal", min: -2, max: 20, step: 0.1 }),
|
|
219
|
+
textTransform: Object.freeze({ type: "textTransform" }),
|
|
193
220
|
}),
|
|
194
221
|
targets: Object.freeze({
|
|
195
|
-
"appstudio.text":
|
|
196
|
-
"appstudio.label":
|
|
197
|
-
"appstudio.data-value":
|
|
198
|
-
color: "color",
|
|
199
|
-
fontSize: "fontSize",
|
|
200
|
-
}),
|
|
222
|
+
"appstudio.text": TEXT_TYPOGRAPHY_FIELDS,
|
|
223
|
+
"appstudio.label": TEXT_TYPOGRAPHY_FIELDS,
|
|
224
|
+
"appstudio.data-value": TEXT_TYPOGRAPHY_FIELDS,
|
|
201
225
|
}),
|
|
202
226
|
}),
|
|
203
227
|
});
|
|
@@ -2215,7 +2239,26 @@ const CONTRACT = deepFreeze({
|
|
|
2215
2239
|
// null, which is how one button stays flat while the rest are gradiented.
|
|
2216
2240
|
// Additive: no export changed signature and a theme with no gradient token
|
|
2217
2241
|
// renders exactly as before.
|
|
2218
|
-
|
|
2242
|
+
// 1.48.0: additive (sc-3857) — leading, tracking and case on the `text` scope.
|
|
2243
|
+
// `lineHeight` (a MULTIPLE of the font size), `letterSpacing` (pixels) and
|
|
2244
|
+
// `textTransform` join the existing `color` / `fontSize` tokens and bind to
|
|
2245
|
+
// the identically named per-instance style fields Text, Label and Data Value
|
|
2246
|
+
// now read, so a theme can set app-wide typography and an author still
|
|
2247
|
+
// overrides it per instance. Two new token value types back them: `decimal`
|
|
2248
|
+
// (a clamped 2-decimal number — `size` rounds, so it cannot carry a 1.05
|
|
2249
|
+
// multiplier) and `textTransform`, whose closed enum is published as
|
|
2250
|
+
// `themeComponentTextTransforms`. Additive: no export changed signature and
|
|
2251
|
+
// a theme with none of the new tokens renders exactly as before.
|
|
2252
|
+
//
|
|
2253
|
+
// 1.49.0: additive (REQ-THEME-12) — `themeTokens.typography` gains
|
|
2254
|
+
// `headingFontFamily`, the display face for heading-tier text, and the
|
|
2255
|
+
// per-instance text vocabulary gains `fontRole` (`body` | `heading`) so an
|
|
2256
|
+
// author or Mason marks which text is a heading. `headingFontFamily`
|
|
2257
|
+
// defaults to the resolved `fontFamily`, and `fontRole` defaults to
|
|
2258
|
+
// `body`, so an app that sets neither renders on one typeface exactly as
|
|
2259
|
+
// before. Genuine headings — rich-text h1-h3 and container titles — take
|
|
2260
|
+
// the heading face without the field.
|
|
2261
|
+
version: "1.49.0",
|
|
2219
2262
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2220
2263
|
hooks: HOOKS,
|
|
2221
2264
|
primitives: PRIMITIVES,
|
|
@@ -2228,6 +2271,7 @@ const CONTRACT = deepFreeze({
|
|
|
2228
2271
|
themeTokens: DEFAULT_THEME_TOKENS,
|
|
2229
2272
|
themeComponents: THEME_COMPONENTS,
|
|
2230
2273
|
themeComponentShadows: THEME_COMPONENT_SHADOWS,
|
|
2274
|
+
themeComponentTextTransforms: THEME_COMPONENT_TEXT_TRANSFORMS,
|
|
2231
2275
|
themeComponentGradient: THEME_COMPONENT_GRADIENT,
|
|
2232
2276
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
2233
2277
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|
package/dist/contract.js
CHANGED
|
@@ -76,6 +76,10 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
|
|
|
76
76
|
typography: Object.freeze({
|
|
77
77
|
fontFamily:
|
|
78
78
|
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
79
|
+
// The display face for heading-tier text. Defaults to `fontFamily` — an app
|
|
80
|
+
// that never pairs renders on one typeface exactly as before.
|
|
81
|
+
headingFontFamily:
|
|
82
|
+
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
79
83
|
sizes: Object.freeze({ xs: 12, sm: 14, md: 16, lg: 20, xl: 24, xxl: 32 }),
|
|
80
84
|
}),
|
|
81
85
|
// REQ-THEME-15: the tenant's per-component style tokens, folded in by the
|
|
@@ -107,6 +111,14 @@ const DEFAULT_THEME_TOKENS = Object.freeze({
|
|
|
107
111
|
// cannot drift between what Mason may emit and what a host actually applies.
|
|
108
112
|
const THEME_COMPONENT_SHADOWS = Object.freeze(["none", "sm", "md", "lg"]);
|
|
109
113
|
|
|
114
|
+
// sc-3857 — the `textTransform` token's closed enum, matching the per-instance
|
|
115
|
+
// Text/Label/Data Value style field and React Native's own vocabulary.
|
|
116
|
+
const THEME_COMPONENT_TEXT_TRANSFORMS = Object.freeze([
|
|
117
|
+
"none",
|
|
118
|
+
"uppercase",
|
|
119
|
+
"capitalize",
|
|
120
|
+
]);
|
|
121
|
+
|
|
110
122
|
// sc-3727 — the `gradient` token's value shape: two hex stops plus a CSS-degree
|
|
111
123
|
// angle, the same grammar as `themeConfig.backgroundGradient` minus the radial
|
|
112
124
|
// variant (a component fill projects through `<Gradient>`, which is linear-only
|
|
@@ -129,6 +141,16 @@ const CARD_SURFACE_FIELDS = Object.freeze({
|
|
|
129
141
|
gradient: "cardGradient",
|
|
130
142
|
});
|
|
131
143
|
|
|
144
|
+
// The text field names every text-bearing built-in reads
|
|
145
|
+
// (frontend/src/components/widgets/_shared/textStyle.js TYPOGRAPHY_STYLE_FIELDS).
|
|
146
|
+
const TEXT_TYPOGRAPHY_FIELDS = Object.freeze({
|
|
147
|
+
color: "color",
|
|
148
|
+
fontSize: "fontSize",
|
|
149
|
+
lineHeight: "lineHeight",
|
|
150
|
+
letterSpacing: "letterSpacing",
|
|
151
|
+
textTransform: "textTransform",
|
|
152
|
+
});
|
|
153
|
+
|
|
132
154
|
// A form widget's submit button — the `button` scope reaches it through the
|
|
133
155
|
// form's own submit* fields, so "make the buttons coral" does not skip forms.
|
|
134
156
|
const FORM_SUBMIT_FIELDS = Object.freeze({
|
|
@@ -190,14 +212,16 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
190
212
|
tokens: Object.freeze({
|
|
191
213
|
color: Object.freeze({ type: "color", uiDefault: "colors.onSurface" }),
|
|
192
214
|
fontSize: Object.freeze({ type: "size", min: 8, max: 96, uiDefault: "typography.sizes.md" }),
|
|
215
|
+
// sc-3857 — leading as a MULTIPLE of the font size, so one app-wide value
|
|
216
|
+
// is right at every size; `size` cannot carry it because it rounds.
|
|
217
|
+
lineHeight: Object.freeze({ type: "decimal", min: 0.8, max: 3, step: 0.05 }),
|
|
218
|
+
letterSpacing: Object.freeze({ type: "decimal", min: -2, max: 20, step: 0.1 }),
|
|
219
|
+
textTransform: Object.freeze({ type: "textTransform" }),
|
|
193
220
|
}),
|
|
194
221
|
targets: Object.freeze({
|
|
195
|
-
"appstudio.text":
|
|
196
|
-
"appstudio.label":
|
|
197
|
-
"appstudio.data-value":
|
|
198
|
-
color: "color",
|
|
199
|
-
fontSize: "fontSize",
|
|
200
|
-
}),
|
|
222
|
+
"appstudio.text": TEXT_TYPOGRAPHY_FIELDS,
|
|
223
|
+
"appstudio.label": TEXT_TYPOGRAPHY_FIELDS,
|
|
224
|
+
"appstudio.data-value": TEXT_TYPOGRAPHY_FIELDS,
|
|
201
225
|
}),
|
|
202
226
|
}),
|
|
203
227
|
});
|
|
@@ -2215,7 +2239,26 @@ const CONTRACT = deepFreeze({
|
|
|
2215
2239
|
// null, which is how one button stays flat while the rest are gradiented.
|
|
2216
2240
|
// Additive: no export changed signature and a theme with no gradient token
|
|
2217
2241
|
// renders exactly as before.
|
|
2218
|
-
|
|
2242
|
+
// 1.48.0: additive (sc-3857) — leading, tracking and case on the `text` scope.
|
|
2243
|
+
// `lineHeight` (a MULTIPLE of the font size), `letterSpacing` (pixels) and
|
|
2244
|
+
// `textTransform` join the existing `color` / `fontSize` tokens and bind to
|
|
2245
|
+
// the identically named per-instance style fields Text, Label and Data Value
|
|
2246
|
+
// now read, so a theme can set app-wide typography and an author still
|
|
2247
|
+
// overrides it per instance. Two new token value types back them: `decimal`
|
|
2248
|
+
// (a clamped 2-decimal number — `size` rounds, so it cannot carry a 1.05
|
|
2249
|
+
// multiplier) and `textTransform`, whose closed enum is published as
|
|
2250
|
+
// `themeComponentTextTransforms`. Additive: no export changed signature and
|
|
2251
|
+
// a theme with none of the new tokens renders exactly as before.
|
|
2252
|
+
//
|
|
2253
|
+
// 1.49.0: additive (REQ-THEME-12) — `themeTokens.typography` gains
|
|
2254
|
+
// `headingFontFamily`, the display face for heading-tier text, and the
|
|
2255
|
+
// per-instance text vocabulary gains `fontRole` (`body` | `heading`) so an
|
|
2256
|
+
// author or Mason marks which text is a heading. `headingFontFamily`
|
|
2257
|
+
// defaults to the resolved `fontFamily`, and `fontRole` defaults to
|
|
2258
|
+
// `body`, so an app that sets neither renders on one typeface exactly as
|
|
2259
|
+
// before. Genuine headings — rich-text h1-h3 and container titles — take
|
|
2260
|
+
// the heading face without the field.
|
|
2261
|
+
version: "1.49.0",
|
|
2219
2262
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2220
2263
|
hooks: HOOKS,
|
|
2221
2264
|
primitives: PRIMITIVES,
|
|
@@ -2228,6 +2271,7 @@ const CONTRACT = deepFreeze({
|
|
|
2228
2271
|
themeTokens: DEFAULT_THEME_TOKENS,
|
|
2229
2272
|
themeComponents: THEME_COMPONENTS,
|
|
2230
2273
|
themeComponentShadows: THEME_COMPONENT_SHADOWS,
|
|
2274
|
+
themeComponentTextTransforms: THEME_COMPONENT_TEXT_TRANSFORMS,
|
|
2231
2275
|
themeComponentGradient: THEME_COMPONENT_GRADIENT,
|
|
2232
2276
|
widgetContextShape: WIDGET_CONTEXT_SHAPE,
|
|
2233
2277
|
bundleExportContract: BUNDLE_EXPORT_CONTRACT,
|
package/dist/index.d.ts
CHANGED
|
@@ -306,6 +306,8 @@ export interface ThemeTokens {
|
|
|
306
306
|
radii: { sm: number; md: number; lg: number; pill: number };
|
|
307
307
|
typography: {
|
|
308
308
|
fontFamily: string;
|
|
309
|
+
/** The display face for heading-tier text. Defaults to `fontFamily`. */
|
|
310
|
+
headingFontFamily: string;
|
|
309
311
|
sizes: { xs: number; sm: number; md: number; lg: number; xl: number };
|
|
310
312
|
};
|
|
311
313
|
}
|
|
@@ -44,9 +44,22 @@ function coerceToken(def, value) {
|
|
|
44
44
|
if (!Number.isFinite(n)) return undefined;
|
|
45
45
|
return Math.min(def.max, Math.max(def.min, Math.round(n)));
|
|
46
46
|
}
|
|
47
|
+
// sc-3857: a leading multiplier / tracking value must survive the decimals
|
|
48
|
+
// `size` rounds away — 1.05 leading is the whole point of the token.
|
|
49
|
+
if (def.type === "decimal") {
|
|
50
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
51
|
+
if (!Number.isFinite(n)) return undefined;
|
|
52
|
+
const clamped = Math.min(def.max, Math.max(def.min, n));
|
|
53
|
+
return Math.round(clamped * 100) / 100;
|
|
54
|
+
}
|
|
47
55
|
if (def.type === "shadow") {
|
|
48
56
|
return CONTRACT.themeComponentShadows.includes(value) ? value : undefined;
|
|
49
57
|
}
|
|
58
|
+
if (def.type === "textTransform") {
|
|
59
|
+
return CONTRACT.themeComponentTextTransforms.includes(value)
|
|
60
|
+
? value
|
|
61
|
+
: undefined;
|
|
62
|
+
}
|
|
50
63
|
// sc-3727: the gradient value has its own normaliser on the contract, shared
|
|
51
64
|
// with the widget render path so both routes agree (CLAUDE.md §3).
|
|
52
65
|
if (def.type === "gradient") {
|
package/dist/theme-components.js
CHANGED
|
@@ -36,9 +36,22 @@ function coerceToken(def, value) {
|
|
|
36
36
|
if (!Number.isFinite(n)) return undefined;
|
|
37
37
|
return Math.min(def.max, Math.max(def.min, Math.round(n)));
|
|
38
38
|
}
|
|
39
|
+
// sc-3857: a leading multiplier / tracking value must survive the decimals
|
|
40
|
+
// `size` rounds away — 1.05 leading is the whole point of the token.
|
|
41
|
+
if (def.type === "decimal") {
|
|
42
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
43
|
+
if (!Number.isFinite(n)) return undefined;
|
|
44
|
+
const clamped = Math.min(def.max, Math.max(def.min, n));
|
|
45
|
+
return Math.round(clamped * 100) / 100;
|
|
46
|
+
}
|
|
39
47
|
if (def.type === "shadow") {
|
|
40
48
|
return CONTRACT.themeComponentShadows.includes(value) ? value : undefined;
|
|
41
49
|
}
|
|
50
|
+
if (def.type === "textTransform") {
|
|
51
|
+
return CONTRACT.themeComponentTextTransforms.includes(value)
|
|
52
|
+
? value
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
42
55
|
// sc-3727: the gradient value has its own normaliser on the contract, shared
|
|
43
56
|
// with the widget render path so both routes agree (CLAUDE.md §3).
|
|
44
57
|
if (def.type === "gradient") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.72.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",
|