@colixsystems/widget-sdk 0.145.0 → 0.147.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 +22 -1
- package/dist/contract.cjs +27 -1
- package/dist/contract.js +27 -1
- package/dist/host.d.ts +15 -18
- package/dist/navigation.cjs +88 -58
- package/dist/navigation.js +88 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,28 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
71
71
|
|
|
72
72
|
## Status
|
|
73
73
|
|
|
74
|
-
`v0.
|
|
74
|
+
`v0.147.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.147.0 (contract 1.115.0)
|
|
77
|
+
|
|
78
|
+
**BREAKING: the bottom navigation bar is themed by `footer` alone — `resolveFooterTokens` reads no `sidebar` (sc-8033).** Every colour fell back to the rail's, so restyling the sidebar restyled a page's bottom bar and the two could not be dressed apart. That is the defect sc-7951 set out to fix and only half did: it gave the strip a Design panel, but left the panel's "Active tab style" offering a **Follow the sidebar** option and every colour still inheriting. The resolver now reads `theme.footer` and nothing else, and the Studio panel offers the strip's **eight** settings with no inherit anywhere.
|
|
79
|
+
|
|
80
|
+
- **The overloaded `activeColor` is retired** for **`footer.tabActiveBackgroundColor`** and **`footer.tabActiveTextColor`**. One key meant the FILL under `"filled"` and the LABEL under `"accent"`, which is exactly why no author could set both. They are independent now, and each style's *defaults* are what it already rendered — `"filled"`: the brand primary behind a white label; `"accent"`: no surface, a brand-primary label and top edge. So all four colours stay meaningful under both styles.
|
|
81
|
+
- **`activeTint` is gone from `FooterTokens`.** The returned **`activeColor`** is the active tab's label, glyph and accent edge — one colour for all three, because a mark and its label disagreeing about which colour means "you are here" would be two marks.
|
|
82
|
+
- **`backgroundColor` and `textColor` are resolved strings**, not raw-or-null. Both hosts kept their own `#ffffff` / `#475569` defaults for them; a default that lives in each host is the drift §8 exists to prevent, and these had already diverged once. `borderColor` stays nullable — no line, no colour.
|
|
83
|
+
- **`borderWidth` admits `0` here alone**, where `resolveSidebarTokens` keeps sc-7965's 1–8 clamp. The strip's thickness is a Studio control offered down to zero, so a stored zero is an author saying "no line" rather than the junk that clamp refuses; it resolves into the existing no-divider state, so neither host needed new drawing code.
|
|
84
|
+
|
|
85
|
+
**No shipped app moved.** A backfill (`20270123000000_sc8033_footer_tokens_uncouple`) materialised into each workspace's `footer` block exactly what it had been inheriting, and split every stored `activeColor` onto the key that says which half it meant. Nothing is written where the old chain ended in a default the new one also ends in.
|
|
86
|
+
|
|
87
|
+
### What's new in 0.146.0 (contract 1.114.0)
|
|
88
|
+
|
|
89
|
+
**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.
|
|
90
|
+
|
|
91
|
+
- **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.
|
|
92
|
+
- **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".
|
|
93
|
+
- **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.
|
|
94
|
+
|
|
95
|
+
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.
|
|
75
96
|
|
|
76
97
|
### What's new in 0.145.0 (contract 1.113.0)
|
|
77
98
|
|
package/dist/contract.cjs
CHANGED
|
@@ -4054,7 +4054,33 @@ const CONTRACT = deepFreeze({
|
|
|
4054
4054
|
// and the inherited colours are hex-guarded on the way out as well as in.
|
|
4055
4055
|
// Every colour still falls back to the sidebar's: an untouched workspace is
|
|
4056
4056
|
// unchanged.
|
|
4057
|
-
|
|
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
|
+
// 1.115.0: BREAKING on the pre-1.0 channel (sc-8033) — `resolveFooterTokens`
|
|
4068
|
+
// reads `theme.footer` and NOTHING else. Every colour used to fall back to
|
|
4069
|
+
// `theme.sidebar`, so restyling the rail restyled a page's bottom
|
|
4070
|
+
// navigation bar and the two could not be dressed apart — the defect
|
|
4071
|
+
// sc-7951 set out to fix and only half did. The wire block's overloaded
|
|
4072
|
+
// `activeColor` (the FILL under "filled", the LABEL under "accent") is
|
|
4073
|
+
// retired for `tabActiveBackgroundColor` + `tabActiveTextColor`, two
|
|
4074
|
+
// independent halves; `activeTint` folds into the returned `activeColor`,
|
|
4075
|
+
// which is now the active tab's label, glyph and accent edge. The returned
|
|
4076
|
+
// `backgroundColor` and `textColor` are resolved strings rather than
|
|
4077
|
+
// raw-or-null, so neither host keeps a default of its own. `borderWidth`
|
|
4078
|
+
// admits 0 HERE alone — the strip's thickness is a Studio control offered
|
|
4079
|
+
// down to zero, and zero resolves into the existing no-divider state —
|
|
4080
|
+
// where the rail's and the header's keep sc-7965's 1-8 clamp. A backfill
|
|
4081
|
+
// (`20270123000000_sc8033_footer_tokens_uncouple`) materialised what each
|
|
4082
|
+
// workspace was inheriting, so no shipped app moved.
|
|
4083
|
+
version: "1.115.0",
|
|
4058
4084
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4059
4085
|
hooks: HOOKS,
|
|
4060
4086
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -4050,7 +4050,33 @@ const CONTRACT = deepFreeze({
|
|
|
4050
4050
|
// and the inherited colours are hex-guarded on the way out as well as in.
|
|
4051
4051
|
// Every colour still falls back to the sidebar's: an untouched workspace is
|
|
4052
4052
|
// unchanged.
|
|
4053
|
-
|
|
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
|
+
// 1.115.0: BREAKING on the pre-1.0 channel (sc-8033) — `resolveFooterTokens`
|
|
4064
|
+
// reads `theme.footer` and NOTHING else. Every colour used to fall back to
|
|
4065
|
+
// `theme.sidebar`, so restyling the rail restyled a page's bottom
|
|
4066
|
+
// navigation bar and the two could not be dressed apart — the defect
|
|
4067
|
+
// sc-7951 set out to fix and only half did. The wire block's overloaded
|
|
4068
|
+
// `activeColor` (the FILL under "filled", the LABEL under "accent") is
|
|
4069
|
+
// retired for `tabActiveBackgroundColor` + `tabActiveTextColor`, two
|
|
4070
|
+
// independent halves; `activeTint` folds into the returned `activeColor`,
|
|
4071
|
+
// which is now the active tab's label, glyph and accent edge. The returned
|
|
4072
|
+
// `backgroundColor` and `textColor` are resolved strings rather than
|
|
4073
|
+
// raw-or-null, so neither host keeps a default of its own. `borderWidth`
|
|
4074
|
+
// admits 0 HERE alone — the strip's thickness is a Studio control offered
|
|
4075
|
+
// down to zero, and zero resolves into the existing no-divider state —
|
|
4076
|
+
// where the rail's and the header's keep sc-7965's 1-8 clamp. A backfill
|
|
4077
|
+
// (`20270123000000_sc8033_footer_tokens_uncouple`) materialised what each
|
|
4078
|
+
// workspace was inheriting, so no shipped app moved.
|
|
4079
|
+
version: "1.115.0",
|
|
4054
4080
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4055
4081
|
hooks: HOOKS,
|
|
4056
4082
|
primitives: PRIMITIVES,
|
package/dist/host.d.ts
CHANGED
|
@@ -173,35 +173,32 @@ export function normaliseNavigation(navigation: unknown): ResolvedNavigation;
|
|
|
173
173
|
*/
|
|
174
174
|
export function menuItemCap(menuType: string): number | null;
|
|
175
175
|
|
|
176
|
-
/** The app's bottom edge — a page's own bottom navigation bar.
|
|
177
|
-
*
|
|
178
|
-
* divider is drawn at all. */
|
|
176
|
+
/** The app's bottom edge — a page's own bottom navigation bar. Every colour is
|
|
177
|
+
* resolved, so no host keeps a default of its own; a `null` `borderColor`
|
|
178
|
+
* means no divider is drawn at all. */
|
|
179
179
|
export interface FooterTokens {
|
|
180
|
-
backgroundColor: string
|
|
181
|
-
textColor: string
|
|
182
|
-
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
180
|
+
backgroundColor: string;
|
|
181
|
+
textColor: string;
|
|
182
|
+
/** The active tab's label, its glyph, and — under "accent" — the edge that
|
|
183
|
+
* marks it. One colour for all three: a mark and its label disagreeing about
|
|
184
|
+
* which colour means "you are here" would be two marks, not one. */
|
|
185
185
|
activeColor: string;
|
|
186
|
+
/** `null` when no divider is drawn, which a zero `borderWidth` also means. */
|
|
186
187
|
borderColor: string | null;
|
|
187
188
|
borderWidth: number | null;
|
|
188
189
|
activeStyle: "filled" | "accent";
|
|
189
190
|
/** Whether a tab's glyph sits beside its label or above it (sc-7951). */
|
|
190
191
|
tabLayout: "inline" | "stacked";
|
|
191
|
-
/** The active tab's surface, or `null` for none
|
|
192
|
-
*
|
|
192
|
+
/** The active tab's surface, or `null` for none — which is what "accent"
|
|
193
|
+
* resolves to unless the author names one. */
|
|
193
194
|
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;
|
|
197
195
|
}
|
|
198
196
|
|
|
199
197
|
/**
|
|
200
|
-
* Resolves the footer strip's tokens from a whole `theme_config`.
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* from nothing — a vertical rail item has neither.
|
|
198
|
+
* Resolves the footer strip's tokens from a whole `theme_config`. It reads
|
|
199
|
+
* `theme.footer` and NOTHING else (sc-8033): the colours used to fall back to
|
|
200
|
+
* the sidebar's, so restyling the rail restyled the strip and the two could not
|
|
201
|
+
* be dressed apart.
|
|
205
202
|
*/
|
|
206
203
|
export function resolveFooterTokens(theme: unknown): FooterTokens;
|
|
207
204
|
|
package/dist/navigation.cjs
CHANGED
|
@@ -71,78 +71,76 @@ function menuItemCap(menuType) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// REQ-NAV-LOCAL: the app's BOTTOM EDGE — the tokens a page's own bottom
|
|
74
|
-
// navigation bar is drawn from.
|
|
75
|
-
// workspace that never opens the panel keeps the appearance it has.
|
|
74
|
+
// navigation bar is drawn from.
|
|
76
75
|
//
|
|
77
76
|
// sc-7753: the global quick bar these tokens were introduced for is retired;
|
|
78
77
|
// the bar that reads them now is the page's own (`local_nav = BOTTOM_BAR`).
|
|
79
78
|
//
|
|
80
|
-
// sc-
|
|
81
|
-
// used to
|
|
82
|
-
//
|
|
79
|
+
// sc-8033: the block is SELF-CONTAINED — it reads `theme.footer` and nothing
|
|
80
|
+
// else. Every colour used to fall back to the rail's, so restyling the sidebar
|
|
81
|
+
// restyled the strip and the two could not be dressed apart; that is the defect
|
|
82
|
+
// sc-7951 set out to fix and only half did. A one-off backfill materialised
|
|
83
|
+
// what each workspace was inheriting, so no shipped app lost its look when the
|
|
84
|
+
// chain went (migration `20260918000000_footer_tokens_uncouple`).
|
|
85
|
+
//
|
|
86
|
+
// The eight authorable keys are the eight controls the Design panel offers, one
|
|
87
|
+
// for one: `backgroundColor`, `textColor`, `tabActiveBackgroundColor`,
|
|
88
|
+
// `tabActiveTextColor`, `borderColor`, `borderWidth`, `activeStyle`,
|
|
89
|
+
// `tabLayout`.
|
|
83
90
|
function resolveFooterTokens(theme) {
|
|
84
91
|
const config = isPlainObject(theme) ? theme : {};
|
|
85
92
|
const footer = isPlainObject(config.footer) ? config.footer : {};
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
// so this inherits from nothing and unset means "no surface of its own".
|
|
100
|
-
const namedSurface = hexOrNull(footer.tabActiveBackgroundColor);
|
|
93
|
+
// Hex-guarded on read: `theme_config` is an opaque blob an operator can
|
|
94
|
+
// hand-edit, and the EXPORT already refused a malformed colour here while the
|
|
95
|
+
// Player passed it to a style object — so the guard is what stops the two
|
|
96
|
+
// hosts painting a stored junk value differently.
|
|
97
|
+
const accent = footer.activeStyle === "accent";
|
|
98
|
+
const borderColor = hexOrNull(footer.borderColor);
|
|
99
|
+
// sc-8033: the strip's thickness is authorable down to 0, where the rail's
|
|
100
|
+
// and the header's clamp up to 1 (sc-7965). The panel OFFERS zero, so zero is
|
|
101
|
+
// a choice rather than the junk value that clamp refuses — and it resolves
|
|
102
|
+
// into the existing no-divider state, so neither host needs a second way to
|
|
103
|
+
// draw nothing.
|
|
104
|
+
const borderWidth = borderColor ? footerBorderWidthOr(footer.borderWidth) : 0;
|
|
105
|
+
const divider = borderWidth > 0;
|
|
101
106
|
return {
|
|
102
|
-
backgroundColor:
|
|
103
|
-
textColor:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
activeColor,
|
|
107
|
-
// REQ-THEME-LOOK: the divider's COLOUR is its switch. Unset draws no line at
|
|
108
|
-
// all — which the native strip did not honour before this, ruling a
|
|
109
|
-
// hairline across a tinted bar that had asked for none.
|
|
110
|
-
borderColor,
|
|
111
|
-
borderWidth: borderColor ? borderWidthOr(footer.borderWidth, sidebar.borderWidth) : null,
|
|
107
|
+
backgroundColor: hexOr(footer.backgroundColor, DEFAULT_FOOTER_SURFACE),
|
|
108
|
+
textColor: hexOr(footer.textColor, DEFAULT_CHROME_TEXT),
|
|
109
|
+
borderColor: divider ? borderColor : null,
|
|
110
|
+
borderWidth: divider ? borderWidth : null,
|
|
112
111
|
activeStyle: accent ? "accent" : "filled",
|
|
113
112
|
// sc-7951: bottom tabs are the only strip tall enough to stack the two, so
|
|
114
113
|
// the choice is the footer's alone.
|
|
115
114
|
tabLayout: footer.tabLayout === "stacked" ? "stacked" : "inline",
|
|
116
|
-
// sc-
|
|
117
|
-
// re-derives
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
activeSurface:
|
|
123
|
-
|
|
115
|
+
// sc-8033: the active tab's two halves are two KEYS, resolved here so
|
|
116
|
+
// neither host re-derives them. They used to be one overloaded
|
|
117
|
+
// `activeColor` — the fill under "filled", the label under "accent" — which
|
|
118
|
+
// is why no author could set both. Only the DEFAULTS still consult the
|
|
119
|
+
// style, and they are exactly what each style shipped: a brand-primary fill
|
|
120
|
+
// under a white label, or no fill under a brand-primary label and edge.
|
|
121
|
+
activeSurface: accent
|
|
122
|
+
? hexOrNull(footer.tabActiveBackgroundColor)
|
|
123
|
+
: hexOr(footer.tabActiveBackgroundColor, brandPrimary(config)),
|
|
124
|
+
// The active tab's label, its glyph, and — under "accent" — the edge that
|
|
125
|
+
// marks it. One colour for all three: a mark and its label disagreeing
|
|
126
|
+
// about which colour means "you are here" would be two marks, not one.
|
|
127
|
+
activeColor: hexOr(
|
|
128
|
+
footer.tabActiveTextColor,
|
|
129
|
+
accent ? brandPrimary(config) : FILLED_TAB_LABEL,
|
|
130
|
+
),
|
|
124
131
|
};
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
// REQ-THEME-14 — the rail's and the app bar's tokens, resolved once for both
|
|
128
135
|
// hosts.
|
|
129
136
|
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
// what remains of the old shape. An unset `sidebar.backgroundColor` painted #ffffff on web and the app
|
|
138
|
-
// background in the export, and an unset `topBar.textColor` painted the brand
|
|
139
|
-
// primary on web and slate-700 in the export. A default that lives in the
|
|
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.
|
|
142
|
-
//
|
|
143
|
-
// Every default is what the WEB Player renders today, so the Player does not
|
|
144
|
-
// move and the export converges onto it. That is the direction the footer was
|
|
145
|
-
// converged in too (compiler.service's footerBg defaults to the web's #ffffff).
|
|
137
|
+
// Every default here is what the WEB Player renders, so the Player does not
|
|
138
|
+
// move and the export converges onto it. An unset `sidebar.backgroundColor`
|
|
139
|
+
// painted #ffffff on web and the app background in the export, and an unset
|
|
140
|
+
// `topBar.textColor` painted the brand primary on web and slate-700 in the
|
|
141
|
+
// export. A default that lives in the resolver cannot drift; one that lives in
|
|
142
|
+
// each host already had — which is why `resolveFooterTokens` above stopped
|
|
143
|
+
// returning raw-or-null too (sc-8033).
|
|
146
144
|
//
|
|
147
145
|
// sc-6596 — the ONE exception, and it moves BOTH hosts: an unset rail or bar
|
|
148
146
|
// takes the page's own colour. Converging on the Player's white left a themed
|
|
@@ -151,13 +149,16 @@ function resolveFooterTokens(theme) {
|
|
|
151
149
|
const DEFAULT_PRIMARY_COLOR = "#3b82f6";
|
|
152
150
|
// The web nav item's `text-slate-600`.
|
|
153
151
|
const DEFAULT_CHROME_TEXT = "#475569";
|
|
152
|
+
// The strip's own surface. Both hosts already defaulted to this separately (the
|
|
153
|
+
// web `footerBg`, the compiler's `#ffffff`); sc-8033 resolved it here instead.
|
|
154
|
+
const DEFAULT_FOOTER_SURFACE = "#ffffff";
|
|
154
155
|
// The web mobile header's icon colour, `text-slate-700`.
|
|
155
156
|
const DEFAULT_TOP_BAR_TINT = "#334155";
|
|
156
157
|
// The page beneath the chrome — the web's `DEFAULT_APP_BACKGROUND_COLOR`
|
|
157
158
|
// (frontend/src/utils/theme.js) and the compiler's, which are the same slate-50.
|
|
158
159
|
const DEFAULT_APP_SURFACE = "#f8fafc";
|
|
159
|
-
//
|
|
160
|
-
//
|
|
160
|
+
// A filled tab's label where the author names none — the literal both hosts
|
|
161
|
+
// hard-coded behind an unnamed "filled" surface before the resolver decided it.
|
|
161
162
|
const FILLED_TAB_LABEL = "#ffffff";
|
|
162
163
|
|
|
163
164
|
// A gradient shows only when BOTH stops are a flat hex — the rule both hosts'
|
|
@@ -365,11 +366,40 @@ function resolveTopBarTokens(theme) {
|
|
|
365
366
|
};
|
|
366
367
|
}
|
|
367
368
|
|
|
369
|
+
// REQ-THEME-LOOK: the divider's width in px, bounded HERE so both hosts inherit
|
|
370
|
+
// one range. The export clamped it on its way into the StyleSheet
|
|
371
|
+
// (`coerceBorderWidth(...) ?? 1`) where the Player passed any finite number
|
|
372
|
+
// straight to a style object, so a stored `1e9` ruled a billion-pixel border
|
|
373
|
+
// across the published web app and a 1px one in the exported one (sc-7965).
|
|
374
|
+
//
|
|
375
|
+
// Clamped rather than dropped, like the tab measurements above: a value past
|
|
376
|
+
// the end lands on the end instead of reverting to the hairline. Zero clamps up
|
|
377
|
+
// for the same reason the export already turned it into one — the divider's
|
|
378
|
+
// COLOUR is its switch, so a coloured 0px edge is a contradiction, not "off".
|
|
379
|
+
const BORDER_WIDTH_MIN = 1;
|
|
380
|
+
const BORDER_WIDTH_MAX = 8;
|
|
381
|
+
|
|
382
|
+
// The BOTTOM strip's own range, which starts at zero. Its thickness is a
|
|
383
|
+
// control the Design panel offers down to 0, so a stored zero is an author
|
|
384
|
+
// saying "no line" rather than the junk value the clamp above refuses — and
|
|
385
|
+
// `resolveFooterTokens` turns it back into the no-divider state both hosts
|
|
386
|
+
// already draw, so the extra bound costs neither host a branch (sc-8033).
|
|
387
|
+
function footerBorderWidthOr(value) {
|
|
388
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
389
|
+
return BORDER_WIDTH_MIN;
|
|
390
|
+
}
|
|
391
|
+
return Math.min(Math.max(Math.round(value), 0), BORDER_WIDTH_MAX);
|
|
392
|
+
}
|
|
368
393
|
function borderWidthOr(...values) {
|
|
369
394
|
for (const value of values) {
|
|
370
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
395
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
396
|
+
return Math.min(
|
|
397
|
+
Math.max(Math.round(value), BORDER_WIDTH_MIN),
|
|
398
|
+
BORDER_WIDTH_MAX,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
371
401
|
}
|
|
372
|
-
return
|
|
402
|
+
return BORDER_WIDTH_MIN;
|
|
373
403
|
}
|
|
374
404
|
|
|
375
405
|
module.exports = { normaliseNavigation, menuItemCap, resolveFooterTokens, resolveSidebarTokens, resolveTopBarTokens };
|
package/dist/navigation.js
CHANGED
|
@@ -62,78 +62,76 @@ export function menuItemCap(menuType) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// REQ-NAV-LOCAL: the app's BOTTOM EDGE — the tokens a page's own bottom
|
|
65
|
-
// navigation bar is drawn from.
|
|
66
|
-
// workspace that never opens the panel keeps the appearance it has.
|
|
65
|
+
// navigation bar is drawn from.
|
|
67
66
|
//
|
|
68
67
|
// sc-7753: the global quick bar these tokens were introduced for is retired;
|
|
69
68
|
// the bar that reads them now is the page's own (`local_nav = BOTTOM_BAR`).
|
|
70
69
|
//
|
|
71
|
-
// sc-
|
|
72
|
-
// used to
|
|
73
|
-
//
|
|
70
|
+
// sc-8033: the block is SELF-CONTAINED — it reads `theme.footer` and nothing
|
|
71
|
+
// else. Every colour used to fall back to the rail's, so restyling the sidebar
|
|
72
|
+
// restyled the strip and the two could not be dressed apart; that is the defect
|
|
73
|
+
// sc-7951 set out to fix and only half did. A one-off backfill materialised
|
|
74
|
+
// what each workspace was inheriting, so no shipped app lost its look when the
|
|
75
|
+
// chain went (migration `20260918000000_footer_tokens_uncouple`).
|
|
76
|
+
//
|
|
77
|
+
// The eight authorable keys are the eight controls the Design panel offers, one
|
|
78
|
+
// for one: `backgroundColor`, `textColor`, `tabActiveBackgroundColor`,
|
|
79
|
+
// `tabActiveTextColor`, `borderColor`, `borderWidth`, `activeStyle`,
|
|
80
|
+
// `tabLayout`.
|
|
74
81
|
export function resolveFooterTokens(theme) {
|
|
75
82
|
const config = isPlainObject(theme) ? theme : {};
|
|
76
83
|
const footer = isPlainObject(config.footer) ? config.footer : {};
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
// so this inherits from nothing and unset means "no surface of its own".
|
|
91
|
-
const namedSurface = hexOrNull(footer.tabActiveBackgroundColor);
|
|
84
|
+
// Hex-guarded on read: `theme_config` is an opaque blob an operator can
|
|
85
|
+
// hand-edit, and the EXPORT already refused a malformed colour here while the
|
|
86
|
+
// Player passed it to a style object — so the guard is what stops the two
|
|
87
|
+
// hosts painting a stored junk value differently.
|
|
88
|
+
const accent = footer.activeStyle === "accent";
|
|
89
|
+
const borderColor = hexOrNull(footer.borderColor);
|
|
90
|
+
// sc-8033: the strip's thickness is authorable down to 0, where the rail's
|
|
91
|
+
// and the header's clamp up to 1 (sc-7965). The panel OFFERS zero, so zero is
|
|
92
|
+
// a choice rather than the junk value that clamp refuses — and it resolves
|
|
93
|
+
// into the existing no-divider state, so neither host needs a second way to
|
|
94
|
+
// draw nothing.
|
|
95
|
+
const borderWidth = borderColor ? footerBorderWidthOr(footer.borderWidth) : 0;
|
|
96
|
+
const divider = borderWidth > 0;
|
|
92
97
|
return {
|
|
93
|
-
backgroundColor:
|
|
94
|
-
textColor:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
activeColor,
|
|
98
|
-
// REQ-THEME-LOOK: the divider's COLOUR is its switch. Unset draws no line at
|
|
99
|
-
// all — which the native strip did not honour before this, ruling a
|
|
100
|
-
// hairline across a tinted bar that had asked for none.
|
|
101
|
-
borderColor,
|
|
102
|
-
borderWidth: borderColor ? borderWidthOr(footer.borderWidth, sidebar.borderWidth) : null,
|
|
98
|
+
backgroundColor: hexOr(footer.backgroundColor, DEFAULT_FOOTER_SURFACE),
|
|
99
|
+
textColor: hexOr(footer.textColor, DEFAULT_CHROME_TEXT),
|
|
100
|
+
borderColor: divider ? borderColor : null,
|
|
101
|
+
borderWidth: divider ? borderWidth : null,
|
|
103
102
|
activeStyle: accent ? "accent" : "filled",
|
|
104
103
|
// sc-7951: bottom tabs are the only strip tall enough to stack the two, so
|
|
105
104
|
// the choice is the footer's alone.
|
|
106
105
|
tabLayout: footer.tabLayout === "stacked" ? "stacked" : "inline",
|
|
107
|
-
// sc-
|
|
108
|
-
// re-derives
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
activeSurface:
|
|
114
|
-
|
|
106
|
+
// sc-8033: the active tab's two halves are two KEYS, resolved here so
|
|
107
|
+
// neither host re-derives them. They used to be one overloaded
|
|
108
|
+
// `activeColor` — the fill under "filled", the label under "accent" — which
|
|
109
|
+
// is why no author could set both. Only the DEFAULTS still consult the
|
|
110
|
+
// style, and they are exactly what each style shipped: a brand-primary fill
|
|
111
|
+
// under a white label, or no fill under a brand-primary label and edge.
|
|
112
|
+
activeSurface: accent
|
|
113
|
+
? hexOrNull(footer.tabActiveBackgroundColor)
|
|
114
|
+
: hexOr(footer.tabActiveBackgroundColor, brandPrimary(config)),
|
|
115
|
+
// The active tab's label, its glyph, and — under "accent" — the edge that
|
|
116
|
+
// marks it. One colour for all three: a mark and its label disagreeing
|
|
117
|
+
// about which colour means "you are here" would be two marks, not one.
|
|
118
|
+
activeColor: hexOr(
|
|
119
|
+
footer.tabActiveTextColor,
|
|
120
|
+
accent ? brandPrimary(config) : FILLED_TAB_LABEL,
|
|
121
|
+
),
|
|
115
122
|
};
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
// REQ-THEME-14 — the rail's and the app bar's tokens, resolved once for both
|
|
119
126
|
// hosts.
|
|
120
127
|
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// what remains of the old shape. An unset `sidebar.backgroundColor` painted #ffffff on web and the app
|
|
129
|
-
// background in the export, and an unset `topBar.textColor` painted the brand
|
|
130
|
-
// primary on web and slate-700 in the export. A default that lives in the
|
|
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.
|
|
133
|
-
//
|
|
134
|
-
// Every default is what the WEB Player renders today, so the Player does not
|
|
135
|
-
// move and the export converges onto it. That is the direction the footer was
|
|
136
|
-
// converged in too (compiler.service's footerBg defaults to the web's #ffffff).
|
|
128
|
+
// Every default here is what the WEB Player renders, so the Player does not
|
|
129
|
+
// move and the export converges onto it. An unset `sidebar.backgroundColor`
|
|
130
|
+
// painted #ffffff on web and the app background in the export, and an unset
|
|
131
|
+
// `topBar.textColor` painted the brand primary on web and slate-700 in the
|
|
132
|
+
// export. A default that lives in the resolver cannot drift; one that lives in
|
|
133
|
+
// each host already had — which is why `resolveFooterTokens` above stopped
|
|
134
|
+
// returning raw-or-null too (sc-8033).
|
|
137
135
|
//
|
|
138
136
|
// sc-6596 — the ONE exception, and it moves BOTH hosts: an unset rail or bar
|
|
139
137
|
// takes the page's own colour. Converging on the Player's white left a themed
|
|
@@ -142,13 +140,16 @@ export function resolveFooterTokens(theme) {
|
|
|
142
140
|
const DEFAULT_PRIMARY_COLOR = "#3b82f6";
|
|
143
141
|
// The web nav item's `text-slate-600`.
|
|
144
142
|
const DEFAULT_CHROME_TEXT = "#475569";
|
|
143
|
+
// The strip's own surface. Both hosts already defaulted to this separately (the
|
|
144
|
+
// web `footerBg`, the compiler's `#ffffff`); sc-8033 resolved it here instead.
|
|
145
|
+
const DEFAULT_FOOTER_SURFACE = "#ffffff";
|
|
145
146
|
// The web mobile header's icon colour, `text-slate-700`.
|
|
146
147
|
const DEFAULT_TOP_BAR_TINT = "#334155";
|
|
147
148
|
// The page beneath the chrome — the web's `DEFAULT_APP_BACKGROUND_COLOR`
|
|
148
149
|
// (frontend/src/utils/theme.js) and the compiler's, which are the same slate-50.
|
|
149
150
|
const DEFAULT_APP_SURFACE = "#f8fafc";
|
|
150
|
-
//
|
|
151
|
-
//
|
|
151
|
+
// A filled tab's label where the author names none — the literal both hosts
|
|
152
|
+
// hard-coded behind an unnamed "filled" surface before the resolver decided it.
|
|
152
153
|
const FILLED_TAB_LABEL = "#ffffff";
|
|
153
154
|
|
|
154
155
|
// A gradient shows only when BOTH stops are a flat hex — the rule both hosts'
|
|
@@ -356,9 +357,38 @@ export function resolveTopBarTokens(theme) {
|
|
|
356
357
|
};
|
|
357
358
|
}
|
|
358
359
|
|
|
360
|
+
// REQ-THEME-LOOK: the divider's width in px, bounded HERE so both hosts inherit
|
|
361
|
+
// one range. The export clamped it on its way into the StyleSheet
|
|
362
|
+
// (`coerceBorderWidth(...) ?? 1`) where the Player passed any finite number
|
|
363
|
+
// straight to a style object, so a stored `1e9` ruled a billion-pixel border
|
|
364
|
+
// across the published web app and a 1px one in the exported one (sc-7965).
|
|
365
|
+
//
|
|
366
|
+
// Clamped rather than dropped, like the tab measurements above: a value past
|
|
367
|
+
// the end lands on the end instead of reverting to the hairline. Zero clamps up
|
|
368
|
+
// for the same reason the export already turned it into one — the divider's
|
|
369
|
+
// COLOUR is its switch, so a coloured 0px edge is a contradiction, not "off".
|
|
370
|
+
const BORDER_WIDTH_MIN = 1;
|
|
371
|
+
const BORDER_WIDTH_MAX = 8;
|
|
372
|
+
|
|
373
|
+
// The BOTTOM strip's own range, which starts at zero. Its thickness is a
|
|
374
|
+
// control the Design panel offers down to 0, so a stored zero is an author
|
|
375
|
+
// saying "no line" rather than the junk value the clamp above refuses — and
|
|
376
|
+
// `resolveFooterTokens` turns it back into the no-divider state both hosts
|
|
377
|
+
// already draw, so the extra bound costs neither host a branch (sc-8033).
|
|
378
|
+
function footerBorderWidthOr(value) {
|
|
379
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
380
|
+
return BORDER_WIDTH_MIN;
|
|
381
|
+
}
|
|
382
|
+
return Math.min(Math.max(Math.round(value), 0), BORDER_WIDTH_MAX);
|
|
383
|
+
}
|
|
359
384
|
function borderWidthOr(...values) {
|
|
360
385
|
for (const value of values) {
|
|
361
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
386
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
387
|
+
return Math.min(
|
|
388
|
+
Math.max(Math.round(value), BORDER_WIDTH_MIN),
|
|
389
|
+
BORDER_WIDTH_MAX,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
362
392
|
}
|
|
363
|
-
return
|
|
393
|
+
return BORDER_WIDTH_MIN;
|
|
364
394
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.147.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",
|