Skip to main content

FragmentView

FragmentView mounts one AppConfig view fragment as a normal Hosanna view. Use it when a screen, dialog, or composite control needs the same fragment tree used by a CollectionView DynamicCell, without creating a CollectionView row.

It supports:

  • views.base child trees and status overlays
  • ${data.*} and ${fn.*} field bindings
  • computed values and fragment constraints
  • contentHeightAnchorViewId auto-height
  • fragment callbacks
  • focus through the fragment root
  • provider pooling and reuse

Use View Fragments for the shared fragment format and Fragment Callbacks and Lifecycle for callback and pooling rules.

Complete Style and Host Example

Define the complete initial appearance, dimensions, bindings, and layout in AppConfig. The example includes a data-dependent computed field and constraints, so a data update runs the complete FragmentView pipeline.

{
"viewFragments": {
"heroCard": {
"$supportsDataMap": true,
"width": 560,
"height": 220,
"views": {
"base": [
{
"id": "background",
"subType": "Rectangle",
"color": "#172033",
"opacity": 1,
"width": "{{constraint.matchSize(cell)}}"
},
{
"id": "poster",
"subType": "Poster",
"uri": "${data.imageUrl}",
"translation": [16, 16],
"width": 188,
"height": 188,
"opacity": 0.88,
"scale": [1, 1]
},
{
"id": "title",
"subType": "Label",
"text": "${data.title}",
"translation": [
"{{constraint.pin(left, poster.right, 20)}}",
"{{constraint.pin(top, poster.top, 4)}}"
],
"width": "{{constraint.fillX(left, cell.right, 0, 20)}}",
"height": 58,
"fontKey": "~theme.fonts.text-bold-24",
"color": "#ffffff",
"wrap": true,
"maxLines": 2
},
{
"id": "premiumBadge",
"subType": "Label",
"text": "PREMIUM",
"translation": [
"{{constraint.pin(right, cell.right, -16)}}",
"{{constraint.pin(top, cell.top, 16)}}"
],
"width": 112,
"height": 30,
"visible": "computed.showPremium",
"color": "#ffd166"
}
],
"normal": {
"background": { "color": "#172033", "opacity": 1 },
"poster": { "opacity": 0.88, "scale": [1, 1] },
"title": { "color": "#ffffff" }
},
"focused": {
"background": { "color": "#263a5e", "opacity": 1 },
"poster": { "opacity": 1, "scale": [1.04, 1.04] },
"title": { "color": "#ffd166" }
},
"selected": {
"background": { "color": "#294936", "opacity": 1 },
"poster": { "opacity": 1, "scale": [1, 1] },
"title": { "color": "#ffffff" }
},
"disabled": {
"background": { "color": "#172033", "opacity": 0.55 },
"poster": { "opacity": 0.35, "scale": [1, 1] },
"title": { "color": "#8d96a8" }
}
},
"_dataMap": {
"computed": [
{
"name": "showPremium",
"fn": "compare",
"args": ["data.isPremium", "eq", true]
}
]
}
}
}
}

Mount it by passing the style key, the current content object, and any host view fields:

import { ViewStatus } from '@hs-src/hosanna-ui/hosanna-api';
import {
FragmentView,
FragmentViewView,
} from '@hs-src/hosanna-ui/views/controls/FragmentView';
import type {
ViewState,
ViewStruct,
} from '@hs-src/hosanna-ui/views/lib/view-api';

// Inside a Hosanna view class:
private heroContent = {
id: 'hero-1',
title: 'Featured tonight',
imageUrl: 'pkg:/images/featured.png',
isPremium: true,
};

protected override getViews(): ViewStruct<ViewState>[] {
return [
FragmentView({
id: 'heroCard',
fragmentStyleKey: 'viewFragments.heroCard',
itemContent: this.heroContent,
viewStatus: ViewStatus.Normal,
canReceiveFocus: true,
})
.translation([80, 120])
.isInitialFocus(),
];
}

private replaceHero(next: typeof this.heroContent): void {
this.heroContent = next;

const card = this.getSubView<FragmentViewView>('heroCard');
card?.setField('itemContent', next);
}

The AppConfig style owns the fragment's starting width and height. The host owns its position, visibility, focusability, and later runtime changes.

Style Requirements

RequirementWhy it matters
A valid fragmentStyleKeyThe key must resolve before the fragment can be acquired. It can point to cells.* or another fragment-style branch.
Positive width and height on the styleFragmentView initializes its measured host size from these fields. Missing dimensions resolve to 0.
A views.base arrayThis is the SceneGraph tree created for each pooled fragment instance.
A complete initial normal appearanceA new fragment can already have Normal status, so views.normal is not guaranteed to run before first display.
Stable, unique descendant IDsBindings, status maps, callbacks, computed targets, and constraints address children through viewsById.
$supportsDataMap: true for ${data.*} and ${fn.*}Without it, AppConfig does not compile those bindings into the runtime map.

views.normal is still important even though views.base starts in the normal appearance. Status maps are sparse updates, not fresh style snapshots. normal must restore every field that another status changes.

Avoid a fixed fragment root id in the AppConfig style. Fragment instances are pooled, and a declared root ID is reused by every instance. Omit it so ViewFragmentProvider creates a unique root ID. IDs inside views.base are per-fragment lookup keys and should remain stable.

Host Fields

FieldBehavior
fragmentStyleKeySelects the named style during host initialization. Set it before the view mounts.
itemContentStored content used for bindings, computed values, callbacks, and later status passes.
viewStatusInitial or current ViewStatus. Use updateViewStatus(...) for an imperative status change.
canReceiveFocusDefaults to false. Set it to true when the fragment should participate in focus navigation.
width / heightInitially read from the fragment style. Later changes update measurement and rerun computed values and constraints.
customDataAccepted for backward compatibility as inherited host metadata. It does not replace itemContent as fragment binding data.

FragmentView also inherits ordinary Hosanna view fields such as translation, visible, opacity, scale, rotation, clipping, layout participation, and transform/opacity inheritance. Updates to common renderer fields are forwarded directly to the fragment root where possible.

Choosing a Fragment Style

Set fragmentStyleKey in the FragmentView's initial state or authored view definition. The current setter acquires the new fragment but does not replace the already attached renderer, so changing the key on a mounted FragmentView is not a supported live-style-switching API.

When the fragment structure must change, recreate the FragmentView host with the new key, or keep separately authored hosts and switch which one is mounted or visible. In either case, register every callback before either style is acquired and do not retain child-node references after the old host unmounts.

Updating Content

Set itemContent when the supplied object should become the host's persistent current data:

card?.setField('itemContent', nextContent);

The itemContent state setter calls updateData(...) and then stores the new object. This is the safest general update.

Call updateData(...) directly for a targeted hot-path update when the model object retained by itemContent has already been updated:

Object.assign(this.heroContent, nextContent);
card?.updateData(this.heroContent);

updateData(...) applies the supplied object immediately but does not replace the stored itemContent field. Keep the authoritative model and the host state in sync; a later status pass can reapply stored itemContent.

When $supportsDataMap is enabled, a data update applies direct and function bindings and then runs onDataChange. Constraints can still run on updateData(...) without $supportsDataMap, but direct bindings and the data callback require it.

Current computed-only update limitation

On standalone FragmentView, a data-only updateData(...) recomputes computed values when a constraint pass exists. A fragment whose data map has computed view-field mappings but no constraints may keep its previous computed result until its status or host dimensions change.

Until the runtime behavior changes, add a real constraint pass when data-dependent computed fields must refresh in FragmentView, or perform that specific update in a registered callback. Do not add a meaningless constraint only to hide the limitation without documenting why it exists.

Status and Focus

Set the initial status in the host struct and use updateViewStatus(...) for a live change:

card?.updateViewStatus(ViewStatus.Selected);

The status pass evaluates focus-dependent computed values and applies the matching sparse status overlay. FragmentView then invokes the status callback and reapplies stored content; that data pass can update computed fields, constraints, and the data callback.

FragmentView returns its fragment root from getFocusHolder(). Setting canReceiveFocus: true makes that root the focus target; nested fragment children do not independently become Hosanna focus targets. Define both focused and restorative normal fields for every visual field changed by focus.

The complete status set and transition hazards are covered in Fragment Callbacks and Lifecycle.

Computed Values, Constraints, and Auto-height

FragmentView supplies these runtime inputs to fragment layout:

  • its resolved host width and height as cell.width and cell.height
  • the current itemContent as data.*
  • the current focus status as focus
  • shared screen, safe-area, and keyboard metrics

Use Fragment Computed Values for derived primitives and Fragment Constraints for child geometry.

For variable-height content, set contentHeightAnchorViewId to a measured descendant ID. FragmentView applies data and constraints, measures the anchor's bottom edge, updates its host height, and applies the constraints again so rules using cell.bottom see the final height. A standalone auto-height style needs a constraint pass; otherwise updateData(...) does not enter the content-height pipeline.

Changing the host width or height later reruns computed values and constraints. This is useful when a FragmentView is placed in a responsive container.

Lifecycle, Recycling, and Pooling

FragmentView acquires its SceneGraph tree from ViewFragmentProvider. On recycle() or host unmount, it:

  1. invokes the fragment's onUnmount handlers
  2. marks the fragment root for removal from its current parent
  3. releases the whole fragment to the provider pool
  4. clears its current style and item content
  5. resets its status to Normal

The child nodes are not recreated or reset through NodePool on every use. Fields written by callbacks, observers, timers, media nodes, animations, and status overlays can survive in the pooled fragment. Reset every callback-owned field on new data, and release external resources on unmount.

See Fragment Callbacks and Lifecycle for callback ordering, cleanup patterns, tree identity, and host differences.

Fallbacks and Failures

  • An empty fragmentStyleKey logs a warning and creates no fragment.
  • An unknown style key uses cells.missingCell when that fallback exists and emits a warning.
  • If neither the requested style nor cells.missingCell exists, fragment acquisition throws.
  • Missing style dimensions produce a zero-sized host.
  • Missing or duplicate child IDs make bindings, status updates, and constraints ambiguous or unreachable.
  • Computed and constraint configuration errors are not converted into silent defaults.
  • Callback exceptions are not isolated by the provider and can stop later handlers and the current host operation.

Use Fragment Performance and Debugging for provider counts, dangling-fragment diagnostics, constraint errors, and layout-cache boundaries.

Canonical Framework Samples

The source repositories contain the most complete runtime examples:

  • Basic standalone creation and targeted data updates: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/views/BaseViewFragmentRig.ts
  • Standalone callbacks, status changes, style registration, and variable width: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/views/FragmentViewRig.ts
  • Computed-value fixtures and resolved-value editor: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/views/ViewFragmentComputedFixtures.ts and hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/views/FragmentComputedEditor.ts
  • Constraint and auto-height fixtures: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/views/ViewFragmentConstraintFixtures.ts
  • Screen, safe-area, focus, cell-size, and data-driven computed values: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/collection-view/CollectionViewFragmentComputedVariablesRig.ts
  • Integration coverage: hosanna-ui-samples-public/integration/fragments/fragments.test.ts

DynamicCellRig.ts manually acquires internal CollectionView cell objects and is a framework test harness, not the recommended way to mount a fragment.

Talk to us