@colixsystems/widget-sdk 0.156.0 → 0.158.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 CHANGED
@@ -73,7 +73,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
73
73
 
74
74
  ## Status
75
75
 
76
- `v0.156.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**.
76
+ `v0.157.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**.
77
+
78
+ ### What's new in 0.157.0 (contract 1.122.0 — unchanged)
79
+
80
+ **The app header's row has its own text colour (sc-8518).** `resolveTopStripeLayout(theme)` now returns **`tintColor`** and **`titleColor`** beside the resolved `backgroundColor`, each reading `theme.sidebar.stripe.textColor` and falling back through `resolveTopBarTokens` when it is unset — exactly as the surface already falls back. One authored colour drives both, as the bar's own does; unset they part, the icons taking slate and the app name the brand.
81
+
82
+ Before this the row's ink lived on `topBar.textColor`, which also paints the tab row every top navigation bar draws — so recolouring the app header moved every one of those tab rows with it. What the two colours reach is what `slots` describes: the phone header, the desktop strip, the full-width bar, and the export's screen header. The drawer's own account rows, the sign-in control, the notification bell and the site-mode sheet are not that row and keep reading the bar.
83
+
84
+ Additive — nothing in the existing `./host` surface changed, an unset value resolves to what it resolved to before, and the contract is untouched.
77
85
 
78
86
  ### What's new in 0.156.0 (contract 1.122.0)
79
87
 
@@ -111,7 +119,7 @@ Behaviour: a bare date given to a `mode="datetime"` picker now shows local midni
111
119
 
112
120
  **The app header's row is arrangeable, and `./host` publishes the resolver both hosts read (sc-8023).** The row runs across the top of the content outside the side panel, and is drawn three times — as the phone header where the panel is a drawer, as the desktop strip, and as the full-width bar when one is switched on. Each surface hard-coded its own copy of it, so an author could colour the row but never move anything in it.
113
121
 
114
- - **`resolveTopStripeLayout(theme)`** reads `theme.sidebar.stripe` and returns the arrangement fully resolved: `slots` (already filtered and sequenced, so no host re-derives it), `order`, the box (`height`, `paddingX`, `contentOffsetY`), the resolved `backgroundColor`, and `menuSide` — which edge the drawer must arrive from, since the brand grows and a burger ordered after it sits at the trailing edge. The web Player picks the drawer's side with it; the export makes it `drawerPosition`.
122
+ - **`resolveTopStripeLayout(theme)`** reads `theme.sidebar.stripe` and returns the arrangement fully resolved: `slots` (already filtered and sequenced, so no host re-derives it), `order`, the box (`height`, `paddingX`, `contentOffsetY`, and `barHeight` — the height once the nudge is held, since pushing the contents down grows the bar rather than letting them fall out of it), the resolved `backgroundColor` (joined in 0.155.0 by `tintColor` and `titleColor`), and `menuSide` — which edge the drawer must arrive from, since the brand grows and a burger ordered after it sits at the trailing edge. The web Player picks the drawer's side with it; the export makes it `drawerPosition`.
115
123
  - **`TOP_STRIPE_SLOTS`** is the vocabulary (`back`, `menu`, `brand`, `notifications`) and **`TOP_STRIPE_ALWAYS_SHOWN_SLOTS`** the slots that move but never hide. The burger is the only member: it is drawn only where the panel is a drawer, and there it is the sole route into the menu, so hiding it would strand the visitor. It keeps its place in `order` regardless.
116
124
  - **`topStripeShowKey(slot)`** derives the `theme_config` key that shows or hides one slot, in one place, so a control, the write path and the resolver cannot spell it differently.
117
125
  - **The bounds travel with the resolver** — `MIN`/`MAX_TOP_STRIPE_HEIGHT_PX`, `MIN`/`MAX_TOP_STRIPE_PADDING_X_PX`, `MAX_TOP_STRIPE_OFFSET_Y_PX` — so a control offers exactly the range the resolver clamps to. `DEFAULT_TOP_STRIPE_HEIGHT_PX` / `DEFAULT_TOP_STRIPE_PADDING_X_PX` are what the DESKTOP row measures with nothing stored, published so a control can show the number an author is departing from; the resolver still answers `null`, because each surface keeps its own box until one is named.
package/dist/host.d.ts CHANGED
@@ -332,8 +332,12 @@ export interface TopStripeLayout {
332
332
  * bar's 64px, the phone header's content-sized row. */
333
333
  height: number | null;
334
334
  paddingX: number | null;
335
- /** How far the row is pushed down, 0-60px. */
335
+ /** How far the row's contents are pushed down, 0-60px. */
336
336
  contentOffsetY: number;
337
+ /** The bar's total height once the nudge is held — its box plus
338
+ * `contentOffsetY`, so contents never fall out of the bottom. Null only
339
+ * while both are unset, where each surface keeps its own box. */
340
+ barHeight: number | null;
337
341
  /** Every slot in the author's sequence, hidden ones included. */
338
342
  order: TopStripeSlot[];
339
343
  /** What the bar actually SHOWS, already filtered and sequenced. A surface
@@ -403,15 +403,18 @@ function topStripeOrder(value) {
403
403
  * bar left holding nothing is not drawn at all.
404
404
  *
405
405
  * @param {unknown} theme — the whole `theme_config`.
406
- * @returns {{ backgroundColor: string, height: number|null,
407
- * paddingX: number|null, contentOffsetY: number, order: string[],
408
- * slots: string[], menuSide: "start"|"end" }}
406
+ * @returns {{ backgroundColor: string, tintColor: string, titleColor: string,
407
+ * height: number|null, paddingX: number|null, contentOffsetY: number,
408
+ * barHeight: number|null,
409
+ * order: string[], slots: string[], menuSide: "start"|"end" }}
409
410
  */
410
411
  function resolveTopStripeLayout(theme) {
411
412
  const config = isPlainObject(theme) ? theme : {};
412
413
  const sidebar = isPlainObject(config.sidebar) ? config.sidebar : {};
413
414
  const stripe = isPlainObject(sidebar.stripe) ? sidebar.stripe : {};
414
415
  const order = topStripeOrder(stripe.order);
416
+ // Resolved once: the row's colours all fall back through the app-header bar.
417
+ const barTokens = resolveTopBarTokens(theme);
415
418
  const shown = (slot) => {
416
419
  if (TOP_STRIPE_ALWAYS_SHOWN_SLOTS.includes(slot)) return true;
417
420
  const value = stripe[topStripeShowKey(slot)];
@@ -420,39 +423,69 @@ function resolveTopStripeLayout(theme) {
420
423
  : TOP_STRIPE_SLOT_SHOWN_BY_DEFAULT[slot];
421
424
  };
422
425
  const slots = order.filter(shown);
426
+ // Unset defers to each surface's own box — the desktop bar's fixed 64px, the
427
+ // phone header's content-sized row.
428
+ const height = topStripeClampedOr(
429
+ stripe.height,
430
+ MIN_TOP_STRIPE_HEIGHT_PX,
431
+ MAX_TOP_STRIPE_HEIGHT_PX,
432
+ null,
433
+ );
434
+ const contentOffsetY = topStripeClampedOr(
435
+ stripe.contentOffsetY,
436
+ 0,
437
+ MAX_TOP_STRIPE_OFFSET_Y_PX,
438
+ 0,
439
+ );
423
440
  return {
424
441
  // There is no alignment to resolve: the brand slot GROWS, so the row has no
425
442
  // free space for `justify-content` to distribute and every value looked
426
443
  // identical. The row spreads because the brand takes the slack.
427
- // Unset defers to each surface's own box — the desktop bar's fixed 64px,
428
- // the phone header's content-sized row.
429
- height: topStripeClampedOr(
430
- stripe.height,
431
- MIN_TOP_STRIPE_HEIGHT_PX,
432
- MAX_TOP_STRIPE_HEIGHT_PX,
433
- null,
434
- ),
444
+ height,
435
445
  paddingX: topStripeClampedOr(
436
446
  stripe.paddingX,
437
447
  MIN_TOP_STRIPE_PADDING_X_PX,
438
448
  MAX_TOP_STRIPE_PADDING_X_PX,
439
449
  null,
440
450
  ),
441
- contentOffsetY: topStripeClampedOr(
442
- stripe.contentOffsetY,
443
- 0,
444
- MAX_TOP_STRIPE_OFFSET_Y_PX,
445
- 0,
446
- ),
451
+ contentOffsetY,
452
+ // sc-8552: the bar GROWS by the nudge rather than letting its contents fall
453
+ // out of the bottom — the box the author sized, plus what they pushed into
454
+ // it. For a surface that must state a height (the desktop bar, the native
455
+ // header); the phone row is content-sized and grows on its padding alone.
456
+ barHeight:
457
+ height === null && contentOffsetY === 0
458
+ ? null
459
+ : (height ?? DEFAULT_TOP_STRIPE_HEIGHT_PX) + contentOffsetY,
447
460
  // sc-8023: the stripe's OWN surface, inheriting the app-header colour when
448
461
  // unset — so an untouched app is unmoved, and an author who dresses a
449
462
  // page's top navigation bar no longer repaints the stripe above the panel by
450
463
  // accident. Fully resolved here, not raw-or-null, for the reason sc-8033
451
464
  // resolved the footer's: a default that lives in each host drifts.
452
- backgroundColor: hexOr(
453
- stripe.backgroundColor,
454
- resolveTopBarTokens(theme).backgroundColor,
455
- ),
465
+ backgroundColor: hexOr(stripe.backgroundColor, barTokens.backgroundColor),
466
+ // sc-8518: the row's OWN text, the half sc-8023 left on the bar. An author
467
+ // recolouring the app header used to move every top navigation bar's tab
468
+ // labels with it, because both read `topBar.textColor` — and had to open a
469
+ // panel named after the other part to do it.
470
+ //
471
+ // One authored colour drives BOTH slots, exactly as the bar's own does:
472
+ // unset they part (icons take slate, the app name takes the brand), and
473
+ // naming one is a decision about the whole row. Unset inherits the bar,
474
+ // so an app that never opens this field is unmoved.
475
+ //
476
+ // The row is what `slots` above describes — the desktop bar, the phone
477
+ // header, and native's screen header. The drawer's own account rows, the
478
+ // sign-in control and the site-mode sheet are NOT this row and keep
479
+ // reading the bar; so does the SITE header, which is shown instead of this
480
+ // row rather than beside it.
481
+ //
482
+ // The notification bell is the one member of `slots` this does not reach.
483
+ // It keeps its own resting colour on both hosts — web reads the authored
484
+ // `topBar.textColor` and the export tints it not at all — and moving it
485
+ // here would widen that difference rather than close it. Whoever closes it
486
+ // should move both hosts at once.
487
+ tintColor: hexOr(stripe.textColor, barTokens.tintColor),
488
+ titleColor: hexOr(stripe.textColor, barTokens.titleColor),
456
489
  order,
457
490
  slots,
458
491
  // sc-8023: which edge the panel the burger opens should come in from. The
@@ -394,15 +394,18 @@ function topStripeOrder(value) {
394
394
  * bar left holding nothing is not drawn at all.
395
395
  *
396
396
  * @param {unknown} theme — the whole `theme_config`.
397
- * @returns {{ backgroundColor: string, height: number|null,
398
- * paddingX: number|null, contentOffsetY: number, order: string[],
399
- * slots: string[], menuSide: "start"|"end" }}
397
+ * @returns {{ backgroundColor: string, tintColor: string, titleColor: string,
398
+ * height: number|null, paddingX: number|null, contentOffsetY: number,
399
+ * barHeight: number|null,
400
+ * order: string[], slots: string[], menuSide: "start"|"end" }}
400
401
  */
401
402
  export function resolveTopStripeLayout(theme) {
402
403
  const config = isPlainObject(theme) ? theme : {};
403
404
  const sidebar = isPlainObject(config.sidebar) ? config.sidebar : {};
404
405
  const stripe = isPlainObject(sidebar.stripe) ? sidebar.stripe : {};
405
406
  const order = topStripeOrder(stripe.order);
407
+ // Resolved once: the row's colours all fall back through the app-header bar.
408
+ const barTokens = resolveTopBarTokens(theme);
406
409
  const shown = (slot) => {
407
410
  if (TOP_STRIPE_ALWAYS_SHOWN_SLOTS.includes(slot)) return true;
408
411
  const value = stripe[topStripeShowKey(slot)];
@@ -411,39 +414,69 @@ export function resolveTopStripeLayout(theme) {
411
414
  : TOP_STRIPE_SLOT_SHOWN_BY_DEFAULT[slot];
412
415
  };
413
416
  const slots = order.filter(shown);
417
+ // Unset defers to each surface's own box — the desktop bar's fixed 64px, the
418
+ // phone header's content-sized row.
419
+ const height = topStripeClampedOr(
420
+ stripe.height,
421
+ MIN_TOP_STRIPE_HEIGHT_PX,
422
+ MAX_TOP_STRIPE_HEIGHT_PX,
423
+ null,
424
+ );
425
+ const contentOffsetY = topStripeClampedOr(
426
+ stripe.contentOffsetY,
427
+ 0,
428
+ MAX_TOP_STRIPE_OFFSET_Y_PX,
429
+ 0,
430
+ );
414
431
  return {
415
432
  // There is no alignment to resolve: the brand slot GROWS, so the row has no
416
433
  // free space for `justify-content` to distribute and every value looked
417
434
  // identical. The row spreads because the brand takes the slack.
418
- // Unset defers to each surface's own box — the desktop bar's fixed 64px,
419
- // the phone header's content-sized row.
420
- height: topStripeClampedOr(
421
- stripe.height,
422
- MIN_TOP_STRIPE_HEIGHT_PX,
423
- MAX_TOP_STRIPE_HEIGHT_PX,
424
- null,
425
- ),
435
+ height,
426
436
  paddingX: topStripeClampedOr(
427
437
  stripe.paddingX,
428
438
  MIN_TOP_STRIPE_PADDING_X_PX,
429
439
  MAX_TOP_STRIPE_PADDING_X_PX,
430
440
  null,
431
441
  ),
432
- contentOffsetY: topStripeClampedOr(
433
- stripe.contentOffsetY,
434
- 0,
435
- MAX_TOP_STRIPE_OFFSET_Y_PX,
436
- 0,
437
- ),
442
+ contentOffsetY,
443
+ // sc-8552: the bar GROWS by the nudge rather than letting its contents fall
444
+ // out of the bottom — the box the author sized, plus what they pushed into
445
+ // it. For a surface that must state a height (the desktop bar, the native
446
+ // header); the phone row is content-sized and grows on its padding alone.
447
+ barHeight:
448
+ height === null && contentOffsetY === 0
449
+ ? null
450
+ : (height ?? DEFAULT_TOP_STRIPE_HEIGHT_PX) + contentOffsetY,
438
451
  // sc-8023: the stripe's OWN surface, inheriting the app-header colour when
439
452
  // unset — so an untouched app is unmoved, and an author who dresses a
440
453
  // page's top navigation bar no longer repaints the stripe above the panel by
441
454
  // accident. Fully resolved here, not raw-or-null, for the reason sc-8033
442
455
  // resolved the footer's: a default that lives in each host drifts.
443
- backgroundColor: hexOr(
444
- stripe.backgroundColor,
445
- resolveTopBarTokens(theme).backgroundColor,
446
- ),
456
+ backgroundColor: hexOr(stripe.backgroundColor, barTokens.backgroundColor),
457
+ // sc-8518: the row's OWN text, the half sc-8023 left on the bar. An author
458
+ // recolouring the app header used to move every top navigation bar's tab
459
+ // labels with it, because both read `topBar.textColor` — and had to open a
460
+ // panel named after the other part to do it.
461
+ //
462
+ // One authored colour drives BOTH slots, exactly as the bar's own does:
463
+ // unset they part (icons take slate, the app name takes the brand), and
464
+ // naming one is a decision about the whole row. Unset inherits the bar,
465
+ // so an app that never opens this field is unmoved.
466
+ //
467
+ // The row is what `slots` above describes — the desktop bar, the phone
468
+ // header, and native's screen header. The drawer's own account rows, the
469
+ // sign-in control and the site-mode sheet are NOT this row and keep
470
+ // reading the bar; so does the SITE header, which is shown instead of this
471
+ // row rather than beside it.
472
+ //
473
+ // The notification bell is the one member of `slots` this does not reach.
474
+ // It keeps its own resting colour on both hosts — web reads the authored
475
+ // `topBar.textColor` and the export tints it not at all — and moving it
476
+ // here would widen that difference rather than close it. Whoever closes it
477
+ // should move both hosts at once.
478
+ tintColor: hexOr(stripe.textColor, barTokens.tintColor),
479
+ titleColor: hexOr(stripe.textColor, barTokens.titleColor),
447
480
  order,
448
481
  slots,
449
482
  // sc-8023: which edge the panel the burger opens should come in from. The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.156.0",
3
+ "version": "0.158.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",