Skip to main content

Fragment Styles

Fragments are reusable view trees defined in AppConfig. DynamicCell uses them for CollectionView cells, and FragmentView can mount the same fragment model directly in a screen or control.

A fragment style has three main parts:

  • views.base: the SG child nodes created when the fragment is allocated.
  • Status styles such as views.normal, views.focused, views.selected, and views.disabled: field updates applied to existing child nodes when the fragment status changes.
  • Optional $supportsDataMap, data bindings, function bindings, constraints, and callbacks: runtime behavior that connects row data or host view data to the fragment.
  • Optional computed values: declarative primitive values that can drive fragment fields and numeric constraint arguments.

Fragments are resolved through AppConfig.get(styleKey), then created and pooled by ViewFragmentProvider. The provider keeps fragments by styleKey, reuses released fragments, tracks child views by id, and falls back to cells.missingCell when an unknown cell style is requested and that fallback exists.

Fragment AppConfig resolution pipeline

AppConfig turns authored fragment metadata into static SG fields plus runtime data-map, computed, constraint, status, and callback behavior.

Fragment Shape

{
"cells": {
"hero": {
"$supportsDataMap": true,
"id": "heroCell",
"views": {
"base": [
{
"id": "poster",
"subType": "Poster",
"width": 384,
"height": 216,
"uri": "${data.imageSet.$RES$}"
},
{
"id": "title",
"subType": "Label",
"fontKey": "~theme.fonts.heading-32",
"text": "${data.title}",
"translation": [0, 230],
"width": 384
}
],
"normal": {
"poster": { "opacity": 0.85 },
"title": { "color": "~theme.colors.white" }
},
"focused": {
"poster": { "opacity": 1, "scale": [1.05, 1.05] },
"title": { "color": "~theme.colors.accent" }
},
"selected": {
"poster": { "opacity": 0.95 }
},
"disabled": {
"poster": { "opacity": 0.4 },
"title": { "color": "~theme.colors.disabledText" }
}
}
}
}
}

Every child in views.base that should receive status updates, data updates, or callback-driven updates needs a stable id. ViewFragmentProvider builds viewsById from those IDs; duplicate IDs make later updates ambiguous.

views.base is applied once when the fragment view is created. Status sections are maps from child ID to SG field updates. When applyViewStatus(fragment, status) runs, the provider resolves ${styleKey}.views.${status} and updates only the fields declared for that status.

Dynamic Bindings

$supportsDataMap enables ${...} bindings inside fragment field values. During AppConfig resolution, those bindings are removed from the static views.base fields and stored in the fragment's internal data map. At runtime, DynamicCell and FragmentView call applyDataMap(fragment, data) to update the target child fields from the current content.

Use data bindings for direct field updates:

{
"id": "poster",
"subType": "Poster",
"uri": "${data.imageSet.$RES$}",
"width": 384,
"height": 216
}

The path after data. is read from the row item or FragmentView data object. Array-style paths are normalized into dot paths, so ${data.images[0].url} is tracked as images.0.url. When $RES$ appears inside a data binding path, AppConfig replaces it with the current resolution suffix, such as fhd, hd, or sd.

Use fn bindings when a field must be computed rather than copied directly:

{
"id": "progress",
"subType": "Rectangle",
"height": 8,
"width": "${fn.progressWidth(320)}"
}

The function name must be registered as an onSpecificViewDataChange callback. AppConfig parses the function name and arguments, and applyDataMap calls the registered function with (data, childView, field, args). The return value is assigned to that field.

Function arguments support booleans, null, undefined, quoted strings, and numbers:

{
"width": "${fn.metricWidth(320, 'compact', true)}"
}

Fragment Constraints

Fragment constraints let AppConfig reposition and resize fragment children after the fragment is mounted or its data changes. The usual authoring form is an inline {{constraint.*(...)}} binding on width, height, x, y, or translation; AppConfig compiles those bindings into _dataMap.constraints.

Use Fragment Constraints for inline constraint syntax, explicit rule shape, priorities, supported functions, and runtime semantics.

Fragment Callbacks

Fragments can declare lifecycle, status, and data callbacks in AppConfig. Register callback names with ViewFragmentProvider, then reference those names from the fragment style.

import {
ViewFragmentCallbackType,
type ViewFragmentCallbackHandler,
type ISpecificViewFragmentCallbackHandler,
} from 'hosanna-ui/views/lib/AppConfig';
import type { IViewFragmentProvider } from 'hosanna-ui/views/lib/ViewFragmentProvider';
import { AppUtils } from 'hosanna-ui/hosanna-bridge-lib/AppUtils';

const provider = AppUtils.resolve<IViewFragmentProvider>('viewFragmentProvider');

provider.registerViewFragmentCallback<ViewFragmentCallbackHandler>(
ViewFragmentCallbackType.OnDataChange,
'layoutMetadata',
(host, fragment, data, status) => {
const title = fragment.viewsById.title;
const background = fragment.viewsById.background;
background.width = title.localBoundingRect().width + 40;
}
);

provider.registerViewFragmentCallback<ISpecificViewFragmentCallbackHandler>(
ViewFragmentCallbackType.OnSpecificViewDataChange,
'progressWidth',
(data, child, field, args) => {
const fullWidth = args[0] as number;
return fullWidth * Number(data.progress ?? 0);
}
);

Supported callback groups:

CallbackHandler argumentsWhen it runs
onMount(host, fragment, data, status)After a fragment view is attached to a DynamicCell or FragmentView host.
onUnmount(host, fragment, data, status)Before a fragment is released back to the provider pool.
onApplyViewStatus(host, fragment, data, status)After a status style such as normal, focused, selected, or disabled is applied.
onDataChange(host, fragment, data, status)After applyDataMap updates data-bound fields.
onSpecificViewDataChange(data, childView, field, args)Registered on the provider and invoked by ${fn.name(...)} bindings during applyDataMap. The return value becomes the field value.

Use direct ${data.*} bindings for copied values such as text, image URI, opacity, or dimensions. Use onDataChange when the update needs multiple child views, measurement, host cell dimensions, or layout side effects. Use onSpecificViewDataChange when a single field should be computed from item data.

Use Fragment Computed Values when a single field can be derived declaratively from item data, cell size, focus state, screen size, or safe-area insets.

Callback arrays participate in $extends merging. If an extending fragment defines a new callback group, it is added. If it defines the same callback group as the base style, that group is replaced.

Fragment Lifecycle

For CollectionView cells, DynamicCell manages fragment lifecycle:

  1. Choose the style key from the row, loading item, or item-level cellSettingsKey.
  2. Release the previous fragment and run onUnmount if the style or row container changed.
  3. Get a fragment from ViewFragmentProvider, reusing a pooled fragment when available.
  4. Append the fragment view to the row container and run onMount.
  5. Apply the data map from the current item and run onDataChange.
  6. Apply the current view status and run onApplyViewStatus when the status changes.

For FragmentView, the host view owns the fragment lifecycle but uses the same provider, status application, data map, constraints, and callback system.

Fragment Provider Behavior

ViewFragmentProvider is responsible for:

  • Creating fragment SG groups from views.base.
  • Applying fonts from fontKey or fontStyleKey before child nodes are created.
  • Scaling maskSize by device resolution when a fragment is first resolved.
  • Assigning the fragment ID from the style id, or generating fragment_# when no ID is provided.
  • Pooling released fragments by styleKey for reuse.
  • Tracking all child nodes in viewsById.
  • Tracking data-bound child nodes in dataMapViewsById.
  • Resolving callback names into functions from the callback registry.
  • Tracking fragments for debugger and cleanup workflows.

Keep fragment styles deterministic. Do not rely on fragment creation order, generated IDs, or callback side effects for basic field values that can be represented directly in AppConfig.