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.basechild trees and status overlays${data.*}and${fn.*}field bindings- computed values and fragment constraints
contentHeightAnchorViewIdauto-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
| Requirement | Why it matters |
|---|---|
A valid fragmentStyleKey | The 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 style | FragmentView initializes its measured host size from these fields. Missing dimensions resolve to 0. |
A views.base array | This is the SceneGraph tree created for each pooled fragment instance. |
| A complete initial normal appearance | A new fragment can already have Normal status, so views.normal is not guaranteed to run before first display. |
| Stable, unique descendant IDs | Bindings, 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
| Field | Behavior |
|---|---|
fragmentStyleKey | Selects the named style during host initialization. Set it before the view mounts. |
itemContent | Stored content used for bindings, computed values, callbacks, and later status passes. |
viewStatus | Initial or current ViewStatus. Use updateViewStatus(...) for an imperative status change. |
canReceiveFocus | Defaults to false. Set it to true when the fragment should participate in focus navigation. |
width / height | Initially read from the fragment style. Later changes update measurement and rerun computed values and constraints. |
customData | Accepted 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.
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.widthandcell.height - the current
itemContentasdata.* - 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:
- invokes the fragment's
onUnmounthandlers - marks the fragment root for removal from its current parent
- releases the whole fragment to the provider pool
- clears its current style and item content
- 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
fragmentStyleKeylogs a warning and creates no fragment. - An unknown style key uses
cells.missingCellwhen that fallback exists and emits a warning. - If neither the requested style nor
cells.missingCellexists, 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.tsandhosanna-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.