@colixsystems/widget-sdk 0.144.0 → 0.146.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 +23 -1
- package/dist/contract.cjs +24 -1
- package/dist/contract.js +24 -1
- package/dist/host.d.ts +17 -4
- package/dist/navigation.cjs +63 -13
- package/dist/navigation.js +63 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,29 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
71
71
|
|
|
72
72
|
## Status
|
|
73
73
|
|
|
74
|
-
`v0.
|
|
74
|
+
`v0.146.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**.
|
|
75
|
+
|
|
76
|
+
### What's new in 0.146.0 (contract 1.114.0)
|
|
77
|
+
|
|
78
|
+
**The chrome divider's width is bounded in the resolver, so both hosts agree (sc-7965).** `resolveFooterTokens` and `resolveSidebarTokens` returned `borderWidth` as any finite number. Only the export bounded it — on its way into the StyleSheet — so a stored `1e9` ruled a **billion-pixel border across the published web app** and a normal one in the exported app. The clamp moved into the shared resolver, where both hosts inherit it.
|
|
79
|
+
|
|
80
|
+
- **Clamped, not dropped.** A value past the end lands **on** the end (1–8), the idiom `tabRadiusOr` / `tabIndicatorOr` already use. This moves the EXPORT too for such a value: it used to *drop* out-of-range and fall back to the 1px hairline, and now renders 8px like the Player.
|
|
81
|
+
- **Zero clamps up**, matching what the export already rendered there — the divider's COLOUR is its switch, so a coloured 0px edge is a contradiction rather than "off".
|
|
82
|
+
- **Every legal 1–8 value is untouched**, and the write path has always refused anything else, so only a hand-edited or pre-coercer `theme_config` can hold a value this moves.
|
|
83
|
+
|
|
84
|
+
The site header was already bounded by its own resolver and is unchanged. The compiler keeps its own `coerceBorderWidth` as defence in depth over a blob `PUT /tenant/config` still stores verbatim; a test pins the two bounds to each other behaviourally, since the SDK cannot import the backend coercer.
|
|
85
|
+
|
|
86
|
+
### What's new in 0.145.0 (contract 1.113.0)
|
|
87
|
+
|
|
88
|
+
**The app's bottom navigation bar gets its own active-tab look — `resolveFooterTokens` gains three (sc-7951).** `theme_config.footer` already carried an `activeStyle`, but no Studio control ever wrote it, so the SIDEBAR's active-item style silently decided the bottom bar too: an author who accented the rail accented the tab bar, with no way to separate them. The bar was also under-authorable — `"filled"` *bundled* the active tab's surface with its label, pinning the surface to `activeColor` and the label to a hard-coded white, so no author could paint a surface of their own.
|
|
89
|
+
|
|
90
|
+
- **`footer.activeStyle` is now the bar's**, reachable from the Design page's Bottom navigation bar panel. Unset it still falls back to `sidebar.activeStyle`, so every existing workspace renders exactly as before.
|
|
91
|
+
- **`footer.tabActiveBackgroundColor`** — the active tab's surface. New, and it falls back to **nothing**: a vertical rail item has no tab surface, so unlike the colours this inherits from no one.
|
|
92
|
+
- **`footer.tabLayout`** — `"inline"` (the row the bar has always drawn) or `"stacked"`, the glyph above its label. Also footer-only.
|
|
93
|
+
|
|
94
|
+
`resolveFooterTokens` now returns the active tab **already decided** — `activeSurface` (hex or `null`) and `activeTint` — so neither host re-derives the back-compat pairing. `"filled"` with nothing named is still the active colour behind a white label; **naming a surface unbundles the two**, and the label goes back to `activeColor` so the author owns both halves. `activeColor` is resolved in the helper now rather than by each caller, so it is a `string` rather than raw-or-null — `FooterTokens.activeColor` narrows accordingly. The inherited colours are also hex-guarded on the way **out**, not only in: the export already refused a malformed stored value while the Player handed it to a style object.
|
|
95
|
+
|
|
96
|
+
It also settles a divergence the bar carried: under `"accent"` the web Player drew a **left** border (the rail's rule on a horizontal tab) and the export drew no edge at all. Both now mark the edge **facing the page** — a bottom tab's top — reserved transparent at every tab's width so marking one cannot shift the row. A tab is also the same 6px-rounded chip on both hosts, where native used to be square.
|
|
75
97
|
|
|
76
98
|
### What's new in 0.144.0 (contract 1.112.0)
|
|
77
99
|
|
package/dist/contract.cjs
CHANGED
|
@@ -4041,7 +4041,30 @@ const CONTRACT = deepFreeze({
|
|
|
4041
4041
|
// belongs to a PAGE's own bottom navigation bar (`local_nav = BOTTOM_BAR`,
|
|
4042
4042
|
// REQ-NAV-LOCAL), chosen per page. `resolveFooterTokens` is unchanged —
|
|
4043
4043
|
// it is the bottom edge's token set and the surviving bar reads it.
|
|
4044
|
-
|
|
4044
|
+
// 1.113.0: additive (sc-7951) — `resolveFooterTokens` gains the bottom
|
|
4045
|
+
// bar's OWN three. `activeStyle` was on the wire but had no Studio
|
|
4046
|
+
// control, so the SIDEBAR's decided the strip too; `tabActiveBackgroundColor`
|
|
4047
|
+
// (the active tab's surface) and `tabLayout` (`inline` | `stacked`) are new
|
|
4048
|
+
// and fall back to nothing, because a vertical rail item has neither. The
|
|
4049
|
+
// resolver now also returns the active tab already DECIDED — `activeSurface`
|
|
4050
|
+
// and `activeTint` — so neither host re-derives the back-compat pairing
|
|
4051
|
+
// ("filled" with nothing named is the active colour behind a white label);
|
|
4052
|
+
// naming a surface unbundles the two. `activeColor` is resolved here now
|
|
4053
|
+
// rather than by each caller, so it is a string rather than raw-or-null,
|
|
4054
|
+
// and the inherited colours are hex-guarded on the way out as well as in.
|
|
4055
|
+
// Every colour still falls back to the sidebar's: an untouched workspace is
|
|
4056
|
+
// unchanged.
|
|
4057
|
+
// 1.114.0: behaviour (sc-7965) — `resolveFooterTokens` / `resolveSidebarTokens`
|
|
4058
|
+
// now return a `borderWidth` CLAMPED to 1-8 instead of any finite number.
|
|
4059
|
+
// The bound lived only in the export (`coerceBorderWidth(...) ?? 1`), so a
|
|
4060
|
+
// stored `1e9` ruled a billion-pixel divider across the web Player and a
|
|
4061
|
+
// 1px one in the exported app. Clamped rather than dropped, like the tab
|
|
4062
|
+
// measurements beside it: a value past the end lands ON the end, which also
|
|
4063
|
+
// moves the EXPORT for such a value (it used to drop to the 1px hairline).
|
|
4064
|
+
// Zero clamps up for the same reason the export already rendered 1 there —
|
|
4065
|
+
// the divider's COLOUR is its switch. Every legal 1-8 value is unchanged,
|
|
4066
|
+
// and only a hand-edited or pre-coercer blob can hold anything else.
|
|
4067
|
+
version: "1.114.0",
|
|
4045
4068
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4046
4069
|
hooks: HOOKS,
|
|
4047
4070
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -4037,7 +4037,30 @@ const CONTRACT = deepFreeze({
|
|
|
4037
4037
|
// belongs to a PAGE's own bottom navigation bar (`local_nav = BOTTOM_BAR`,
|
|
4038
4038
|
// REQ-NAV-LOCAL), chosen per page. `resolveFooterTokens` is unchanged —
|
|
4039
4039
|
// it is the bottom edge's token set and the surviving bar reads it.
|
|
4040
|
-
|
|
4040
|
+
// 1.113.0: additive (sc-7951) — `resolveFooterTokens` gains the bottom
|
|
4041
|
+
// bar's OWN three. `activeStyle` was on the wire but had no Studio
|
|
4042
|
+
// control, so the SIDEBAR's decided the strip too; `tabActiveBackgroundColor`
|
|
4043
|
+
// (the active tab's surface) and `tabLayout` (`inline` | `stacked`) are new
|
|
4044
|
+
// and fall back to nothing, because a vertical rail item has neither. The
|
|
4045
|
+
// resolver now also returns the active tab already DECIDED — `activeSurface`
|
|
4046
|
+
// and `activeTint` — so neither host re-derives the back-compat pairing
|
|
4047
|
+
// ("filled" with nothing named is the active colour behind a white label);
|
|
4048
|
+
// naming a surface unbundles the two. `activeColor` is resolved here now
|
|
4049
|
+
// rather than by each caller, so it is a string rather than raw-or-null,
|
|
4050
|
+
// and the inherited colours are hex-guarded on the way out as well as in.
|
|
4051
|
+
// Every colour still falls back to the sidebar's: an untouched workspace is
|
|
4052
|
+
// unchanged.
|
|
4053
|
+
// 1.114.0: behaviour (sc-7965) — `resolveFooterTokens` / `resolveSidebarTokens`
|
|
4054
|
+
// now return a `borderWidth` CLAMPED to 1-8 instead of any finite number.
|
|
4055
|
+
// The bound lived only in the export (`coerceBorderWidth(...) ?? 1`), so a
|
|
4056
|
+
// stored `1e9` ruled a billion-pixel divider across the web Player and a
|
|
4057
|
+
// 1px one in the exported app. Clamped rather than dropped, like the tab
|
|
4058
|
+
// measurements beside it: a value past the end lands ON the end, which also
|
|
4059
|
+
// moves the EXPORT for such a value (it used to drop to the 1px hairline).
|
|
4060
|
+
// Zero clamps up for the same reason the export already rendered 1 there —
|
|
4061
|
+
// the divider's COLOUR is its switch. Every legal 1-8 value is unchanged,
|
|
4062
|
+
// and only a hand-edited or pre-coercer blob can hold anything else.
|
|
4063
|
+
version: "1.114.0",
|
|
4041
4064
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4042
4065
|
hooks: HOOKS,
|
|
4043
4066
|
primitives: PRIMITIVES,
|
package/dist/host.d.ts
CHANGED
|
@@ -179,16 +179,29 @@ export function menuItemCap(menuType: string): number | null;
|
|
|
179
179
|
export interface FooterTokens {
|
|
180
180
|
backgroundColor: string | null;
|
|
181
181
|
textColor: string | null;
|
|
182
|
-
|
|
182
|
+
/** Never `null`: an unset value resolves to the brand primary here rather
|
|
183
|
+
* than in each host, which is what lets the two active-tab fields below be
|
|
184
|
+
* resolved here at all. */
|
|
185
|
+
activeColor: string;
|
|
183
186
|
borderColor: string | null;
|
|
184
187
|
borderWidth: number | null;
|
|
185
188
|
activeStyle: "filled" | "accent";
|
|
189
|
+
/** Whether a tab's glyph sits beside its label or above it (sc-7951). */
|
|
190
|
+
tabLayout: "inline" | "stacked";
|
|
191
|
+
/** The active tab's surface, or `null` for none. Under "filled" with nothing
|
|
192
|
+
* named this is the active colour — what both hosts shipped. */
|
|
193
|
+
activeSurface: string | null;
|
|
194
|
+
/** The active tab's label and glyph. White only under an unnamed "filled"
|
|
195
|
+
* surface, which is the pairing both hosts hard-coded. */
|
|
196
|
+
activeTint: string;
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
/**
|
|
189
|
-
* Resolves the footer strip's tokens from a whole `theme_config`. Every
|
|
190
|
-
* falls back to the sidebar's, so a workspace that never opens the
|
|
191
|
-
* renders exactly as it did before the block existed.
|
|
200
|
+
* Resolves the footer strip's tokens from a whole `theme_config`. Every colour
|
|
201
|
+
* falls back to the sidebar's, so a workspace that never opens the Bottom
|
|
202
|
+
* navigation bar panel renders exactly as it did before the block existed.
|
|
203
|
+
* `tabActiveBackgroundColor` and `tabLayout` are the strip's own and inherit
|
|
204
|
+
* from nothing — a vertical rail item has neither.
|
|
192
205
|
*/
|
|
193
206
|
export function resolveFooterTokens(theme: unknown): FooterTokens;
|
|
194
207
|
|
package/dist/navigation.cjs
CHANGED
|
@@ -76,39 +76,69 @@ function menuItemCap(menuType) {
|
|
|
76
76
|
//
|
|
77
77
|
// sc-7753: the global quick bar these tokens were introduced for is retired;
|
|
78
78
|
// the bar that reads them now is the page's own (`local_nav = BOTTOM_BAR`).
|
|
79
|
+
//
|
|
80
|
+
// sc-7951: three of the keys ARE that bar's own — the active-item style it
|
|
81
|
+
// used to borrow from the rail for want of a control, the active tab's
|
|
82
|
+
// surface, and whether the glyph sits beside the label or above it.
|
|
79
83
|
function resolveFooterTokens(theme) {
|
|
80
84
|
const config = isPlainObject(theme) ? theme : {};
|
|
81
85
|
const footer = isPlainObject(config.footer) ? config.footer : {};
|
|
82
86
|
const sidebar = isPlainObject(config.sidebar) ? config.sidebar : {};
|
|
83
|
-
|
|
87
|
+
// Hex-guarded on the way out, not merely on the way in: `theme_config` is an
|
|
88
|
+
// opaque blob an operator can hand-edit, and the EXPORT already refused a
|
|
89
|
+
// malformed colour here while the Player passed it to a style object — so the
|
|
90
|
+
// guard is what stops the two hosts painting a stored junk value differently.
|
|
91
|
+
const pick = (key) => hexOrNull(footer[key]) || hexOrNull(sidebar[key]) || null;
|
|
84
92
|
const borderColor = pick("borderColor");
|
|
93
|
+
// sc-7951: the rail's style is the FALLBACK now, not the only source. Unset
|
|
94
|
+
// still follows the rail, which is what keeps every shipped app where it is.
|
|
85
95
|
const activeStyle = footer.activeStyle || sidebar.activeStyle;
|
|
96
|
+
const accent = activeStyle === "accent";
|
|
97
|
+
const activeColor = hexOr(pick("activeColor"), brandPrimary(config));
|
|
98
|
+
// Footer-only: a rail item is a full-width row with no tab surface to paint,
|
|
99
|
+
// so this inherits from nothing and unset means "no surface of its own".
|
|
100
|
+
const namedSurface = hexOrNull(footer.tabActiveBackgroundColor);
|
|
86
101
|
return {
|
|
87
102
|
backgroundColor: pick("backgroundColor"),
|
|
88
103
|
textColor: pick("textColor"),
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
activeColor
|
|
104
|
+
// Resolved here rather than by each caller, which is what lets the two
|
|
105
|
+
// active-tab fields below be resolved here too.
|
|
106
|
+
activeColor,
|
|
92
107
|
// REQ-THEME-LOOK: the divider's COLOUR is its switch. Unset draws no line at
|
|
93
108
|
// all — which the native strip did not honour before this, ruling a
|
|
94
109
|
// hairline across a tinted bar that had asked for none.
|
|
95
110
|
borderColor,
|
|
96
111
|
borderWidth: borderColor ? borderWidthOr(footer.borderWidth, sidebar.borderWidth) : null,
|
|
97
|
-
activeStyle:
|
|
112
|
+
activeStyle: accent ? "accent" : "filled",
|
|
113
|
+
// sc-7951: bottom tabs are the only strip tall enough to stack the two, so
|
|
114
|
+
// the choice is the footer's alone.
|
|
115
|
+
tabLayout: footer.tabLayout === "stacked" ? "stacked" : "inline",
|
|
116
|
+
// sc-7951: the active tab's two halves, resolved HERE so neither host
|
|
117
|
+
// re-derives the back-compat rule. "filled" with nothing named is what both
|
|
118
|
+
// hosts shipped — the active colour as the fill, the label forced white —
|
|
119
|
+
// and that pairing is the only reason a marked tab's label is not simply
|
|
120
|
+
// the active colour. Naming a surface unbundles them: the surface is the
|
|
121
|
+
// author's and the label goes back to the active colour.
|
|
122
|
+
activeSurface: namedSurface || (accent ? null : activeColor),
|
|
123
|
+
activeTint: accent || namedSurface ? activeColor : FILLED_TAB_LABEL,
|
|
98
124
|
};
|
|
99
125
|
}
|
|
100
126
|
|
|
101
127
|
// REQ-THEME-14 — the rail's and the app bar's tokens, resolved once for both
|
|
102
128
|
// hosts.
|
|
103
129
|
//
|
|
104
|
-
// These return FULLY resolved values where `resolveFooterTokens` above
|
|
105
|
-
// raw-or-null
|
|
106
|
-
//
|
|
107
|
-
//
|
|
130
|
+
// These return FULLY resolved values where `resolveFooterTokens` above still
|
|
131
|
+
// returns raw-or-null for its SURFACE, LABEL and DIVIDER colours, and the
|
|
132
|
+
// difference is deliberate. The footer's two hosts happen to agree on their
|
|
133
|
+
// separately-written defaults for those three (`#ffffff` / `#475569` / no
|
|
134
|
+
// line); the rail's and the bar's did not. sc-7951 resolved the footer's active
|
|
135
|
+
// tab — `activeColor`, `activeSurface`, `activeTint` — because a rule that
|
|
136
|
+
// pairs a surface with a label cannot live in two hosts; the three above are
|
|
137
|
+
// what remains of the old shape. An unset `sidebar.backgroundColor` painted #ffffff on web and the app
|
|
108
138
|
// background in the export, and an unset `topBar.textColor` painted the brand
|
|
109
139
|
// primary on web and slate-700 in the export. A default that lives in the
|
|
110
|
-
// resolver cannot drift; one that lives in each host already had. The footer
|
|
111
|
-
//
|
|
140
|
+
// resolver cannot drift; one that lives in each host already had. The footer's
|
|
141
|
+
// three remaining raw colours want this same shape next.
|
|
112
142
|
//
|
|
113
143
|
// Every default is what the WEB Player renders today, so the Player does not
|
|
114
144
|
// move and the export converges onto it. That is the direction the footer was
|
|
@@ -126,6 +156,9 @@ const DEFAULT_TOP_BAR_TINT = "#334155";
|
|
|
126
156
|
// The page beneath the chrome — the web's `DEFAULT_APP_BACKGROUND_COLOR`
|
|
127
157
|
// (frontend/src/utils/theme.js) and the compiler's, which are the same slate-50.
|
|
128
158
|
const DEFAULT_APP_SURFACE = "#f8fafc";
|
|
159
|
+
// The literal both hosts hard-coded behind an unnamed "filled" surface, before
|
|
160
|
+
// resolveFooterTokens decided the pairing once (sc-7951).
|
|
161
|
+
const FILLED_TAB_LABEL = "#ffffff";
|
|
129
162
|
|
|
130
163
|
// A gradient shows only when BOTH stops are a flat hex — the rule both hosts'
|
|
131
164
|
// page readers apply (`normaliseThemeGradient`, mirrored frontend/backend). A
|
|
@@ -332,11 +365,28 @@ function resolveTopBarTokens(theme) {
|
|
|
332
365
|
};
|
|
333
366
|
}
|
|
334
367
|
|
|
368
|
+
// REQ-THEME-LOOK: the divider's width in px, bounded HERE so both hosts inherit
|
|
369
|
+
// one range. The export clamped it on its way into the StyleSheet
|
|
370
|
+
// (`coerceBorderWidth(...) ?? 1`) where the Player passed any finite number
|
|
371
|
+
// straight to a style object, so a stored `1e9` ruled a billion-pixel border
|
|
372
|
+
// across the published web app and a 1px one in the exported one (sc-7965).
|
|
373
|
+
//
|
|
374
|
+
// Clamped rather than dropped, like the tab measurements above: a value past
|
|
375
|
+
// the end lands on the end instead of reverting to the hairline. Zero clamps up
|
|
376
|
+
// for the same reason the export already turned it into one — the divider's
|
|
377
|
+
// COLOUR is its switch, so a coloured 0px edge is a contradiction, not "off".
|
|
378
|
+
const BORDER_WIDTH_MIN = 1;
|
|
379
|
+
const BORDER_WIDTH_MAX = 8;
|
|
335
380
|
function borderWidthOr(...values) {
|
|
336
381
|
for (const value of values) {
|
|
337
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
382
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
383
|
+
return Math.min(
|
|
384
|
+
Math.max(Math.round(value), BORDER_WIDTH_MIN),
|
|
385
|
+
BORDER_WIDTH_MAX,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
338
388
|
}
|
|
339
|
-
return
|
|
389
|
+
return BORDER_WIDTH_MIN;
|
|
340
390
|
}
|
|
341
391
|
|
|
342
392
|
module.exports = { normaliseNavigation, menuItemCap, resolveFooterTokens, resolveSidebarTokens, resolveTopBarTokens };
|
package/dist/navigation.js
CHANGED
|
@@ -67,39 +67,69 @@ export function menuItemCap(menuType) {
|
|
|
67
67
|
//
|
|
68
68
|
// sc-7753: the global quick bar these tokens were introduced for is retired;
|
|
69
69
|
// the bar that reads them now is the page's own (`local_nav = BOTTOM_BAR`).
|
|
70
|
+
//
|
|
71
|
+
// sc-7951: three of the keys ARE that bar's own — the active-item style it
|
|
72
|
+
// used to borrow from the rail for want of a control, the active tab's
|
|
73
|
+
// surface, and whether the glyph sits beside the label or above it.
|
|
70
74
|
export function resolveFooterTokens(theme) {
|
|
71
75
|
const config = isPlainObject(theme) ? theme : {};
|
|
72
76
|
const footer = isPlainObject(config.footer) ? config.footer : {};
|
|
73
77
|
const sidebar = isPlainObject(config.sidebar) ? config.sidebar : {};
|
|
74
|
-
|
|
78
|
+
// Hex-guarded on the way out, not merely on the way in: `theme_config` is an
|
|
79
|
+
// opaque blob an operator can hand-edit, and the EXPORT already refused a
|
|
80
|
+
// malformed colour here while the Player passed it to a style object — so the
|
|
81
|
+
// guard is what stops the two hosts painting a stored junk value differently.
|
|
82
|
+
const pick = (key) => hexOrNull(footer[key]) || hexOrNull(sidebar[key]) || null;
|
|
75
83
|
const borderColor = pick("borderColor");
|
|
84
|
+
// sc-7951: the rail's style is the FALLBACK now, not the only source. Unset
|
|
85
|
+
// still follows the rail, which is what keeps every shipped app where it is.
|
|
76
86
|
const activeStyle = footer.activeStyle || sidebar.activeStyle;
|
|
87
|
+
const accent = activeStyle === "accent";
|
|
88
|
+
const activeColor = hexOr(pick("activeColor"), brandPrimary(config));
|
|
89
|
+
// Footer-only: a rail item is a full-width row with no tab surface to paint,
|
|
90
|
+
// so this inherits from nothing and unset means "no surface of its own".
|
|
91
|
+
const namedSurface = hexOrNull(footer.tabActiveBackgroundColor);
|
|
77
92
|
return {
|
|
78
93
|
backgroundColor: pick("backgroundColor"),
|
|
79
94
|
textColor: pick("textColor"),
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
activeColor
|
|
95
|
+
// Resolved here rather than by each caller, which is what lets the two
|
|
96
|
+
// active-tab fields below be resolved here too.
|
|
97
|
+
activeColor,
|
|
83
98
|
// REQ-THEME-LOOK: the divider's COLOUR is its switch. Unset draws no line at
|
|
84
99
|
// all — which the native strip did not honour before this, ruling a
|
|
85
100
|
// hairline across a tinted bar that had asked for none.
|
|
86
101
|
borderColor,
|
|
87
102
|
borderWidth: borderColor ? borderWidthOr(footer.borderWidth, sidebar.borderWidth) : null,
|
|
88
|
-
activeStyle:
|
|
103
|
+
activeStyle: accent ? "accent" : "filled",
|
|
104
|
+
// sc-7951: bottom tabs are the only strip tall enough to stack the two, so
|
|
105
|
+
// the choice is the footer's alone.
|
|
106
|
+
tabLayout: footer.tabLayout === "stacked" ? "stacked" : "inline",
|
|
107
|
+
// sc-7951: the active tab's two halves, resolved HERE so neither host
|
|
108
|
+
// re-derives the back-compat rule. "filled" with nothing named is what both
|
|
109
|
+
// hosts shipped — the active colour as the fill, the label forced white —
|
|
110
|
+
// and that pairing is the only reason a marked tab's label is not simply
|
|
111
|
+
// the active colour. Naming a surface unbundles them: the surface is the
|
|
112
|
+
// author's and the label goes back to the active colour.
|
|
113
|
+
activeSurface: namedSurface || (accent ? null : activeColor),
|
|
114
|
+
activeTint: accent || namedSurface ? activeColor : FILLED_TAB_LABEL,
|
|
89
115
|
};
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
// REQ-THEME-14 — the rail's and the app bar's tokens, resolved once for both
|
|
93
119
|
// hosts.
|
|
94
120
|
//
|
|
95
|
-
// These return FULLY resolved values where `resolveFooterTokens` above
|
|
96
|
-
// raw-or-null
|
|
97
|
-
//
|
|
98
|
-
//
|
|
121
|
+
// These return FULLY resolved values where `resolveFooterTokens` above still
|
|
122
|
+
// returns raw-or-null for its SURFACE, LABEL and DIVIDER colours, and the
|
|
123
|
+
// difference is deliberate. The footer's two hosts happen to agree on their
|
|
124
|
+
// separately-written defaults for those three (`#ffffff` / `#475569` / no
|
|
125
|
+
// line); the rail's and the bar's did not. sc-7951 resolved the footer's active
|
|
126
|
+
// tab — `activeColor`, `activeSurface`, `activeTint` — because a rule that
|
|
127
|
+
// pairs a surface with a label cannot live in two hosts; the three above are
|
|
128
|
+
// what remains of the old shape. An unset `sidebar.backgroundColor` painted #ffffff on web and the app
|
|
99
129
|
// background in the export, and an unset `topBar.textColor` painted the brand
|
|
100
130
|
// primary on web and slate-700 in the export. A default that lives in the
|
|
101
|
-
// resolver cannot drift; one that lives in each host already had. The footer
|
|
102
|
-
//
|
|
131
|
+
// resolver cannot drift; one that lives in each host already had. The footer's
|
|
132
|
+
// three remaining raw colours want this same shape next.
|
|
103
133
|
//
|
|
104
134
|
// Every default is what the WEB Player renders today, so the Player does not
|
|
105
135
|
// move and the export converges onto it. That is the direction the footer was
|
|
@@ -117,6 +147,9 @@ const DEFAULT_TOP_BAR_TINT = "#334155";
|
|
|
117
147
|
// The page beneath the chrome — the web's `DEFAULT_APP_BACKGROUND_COLOR`
|
|
118
148
|
// (frontend/src/utils/theme.js) and the compiler's, which are the same slate-50.
|
|
119
149
|
const DEFAULT_APP_SURFACE = "#f8fafc";
|
|
150
|
+
// The literal both hosts hard-coded behind an unnamed "filled" surface, before
|
|
151
|
+
// resolveFooterTokens decided the pairing once (sc-7951).
|
|
152
|
+
const FILLED_TAB_LABEL = "#ffffff";
|
|
120
153
|
|
|
121
154
|
// A gradient shows only when BOTH stops are a flat hex — the rule both hosts'
|
|
122
155
|
// page readers apply (`normaliseThemeGradient`, mirrored frontend/backend). A
|
|
@@ -323,9 +356,26 @@ export function resolveTopBarTokens(theme) {
|
|
|
323
356
|
};
|
|
324
357
|
}
|
|
325
358
|
|
|
359
|
+
// REQ-THEME-LOOK: the divider's width in px, bounded HERE so both hosts inherit
|
|
360
|
+
// one range. The export clamped it on its way into the StyleSheet
|
|
361
|
+
// (`coerceBorderWidth(...) ?? 1`) where the Player passed any finite number
|
|
362
|
+
// straight to a style object, so a stored `1e9` ruled a billion-pixel border
|
|
363
|
+
// across the published web app and a 1px one in the exported one (sc-7965).
|
|
364
|
+
//
|
|
365
|
+
// Clamped rather than dropped, like the tab measurements above: a value past
|
|
366
|
+
// the end lands on the end instead of reverting to the hairline. Zero clamps up
|
|
367
|
+
// for the same reason the export already turned it into one — the divider's
|
|
368
|
+
// COLOUR is its switch, so a coloured 0px edge is a contradiction, not "off".
|
|
369
|
+
const BORDER_WIDTH_MIN = 1;
|
|
370
|
+
const BORDER_WIDTH_MAX = 8;
|
|
326
371
|
function borderWidthOr(...values) {
|
|
327
372
|
for (const value of values) {
|
|
328
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
373
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
374
|
+
return Math.min(
|
|
375
|
+
Math.max(Math.round(value), BORDER_WIDTH_MIN),
|
|
376
|
+
BORDER_WIDTH_MAX,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
329
379
|
}
|
|
330
|
-
return
|
|
380
|
+
return BORDER_WIDTH_MIN;
|
|
331
381
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.146.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|