or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

attributes.mdcontext-system.mdcss-styling.mddata-binding.mddependency-injection.mdhtml-templates.mdindex.mdobservable-system.mdssr-hydration.mdstate-management.mdtemplate-directives.mdtesting-utilities.mdutilities.mdweb-components.md

observable-system.mddocs/

0

# Observable System

1

2

Memory-efficient observable system with automatic dependency tracking, batch updates, and array observation capabilities for building reactive applications with minimal overhead.

3

4

## Capabilities

5

6

### Observable Decorator

7

8

Decorator function for making class properties observable, enabling automatic change detection and notification when property values change.

9

10

```typescript { .api }

11

/**

12

* Decorator: Defines an observable property on the target

13

* @param target - The target to define the observable on

14

* @param nameOrAccessor - The property name or accessor to define the observable as

15

*/

16

function observable(target: {}, nameOrAccessor: string | Accessor): void;

17

18

/**

19

* Overloaded signature for use with custom accessors

20

* @param target - The target object

21

* @param nameOrAccessor - The property accessor

22

* @param descriptor - The property descriptor

23

*/

24

function observable<T, K extends keyof T>(

25

target: T,

26

nameOrAccessor: K

27

): void;

28

function observable<T, K extends keyof T>(

29

target: T,

30

nameOrAccessor: K,

31

descriptor: PropertyDescriptor

32

): PropertyDescriptor;

33

```

34

35

**Usage Examples:**

36

37

```typescript

38

import { FASTElement, customElement, html, observable, attr } from "@microsoft/fast-element";

39

40

@customElement("observable-example")

41

export class ObservableExample extends FASTElement {

42

// Basic observable property

43

@observable firstName: string = "";

44

@observable lastName: string = "";

45

@observable age: number = 0;

46

47

// Observable arrays

48

@observable items: string[] = [];

49

@observable users: User[] = [];

50

51

// Observable objects

52

@observable settings: UserSettings = {

53

theme: "light",

54

notifications: true

55

};

56

57

// Computed properties (getters that depend on observables)

58

get fullName(): string {

59

return `${this.firstName} ${this.lastName}`.trim();

60

}

61

62

get isAdult(): boolean {

63

return this.age >= 18;

64

}

65

66

get itemCount(): number {

67

return this.items?.length ?? 0;

68

}

69

70

// Methods that modify observable properties

71

addItem(item: string) {

72

this.items.push(item);

73

// Observable array mutation is automatically tracked

74

}

75

76

updateSettings(newSettings: Partial<UserSettings>) {

77

Object.assign(this.settings, newSettings);

78

// Object property changes are tracked when accessed through observables

79

}

80

81

clearAll() {

82

this.firstName = "";

83

this.lastName = "";

84

this.age = 0;

85

this.items = [];

86

this.users = [];

87

}

88

}

89

90

// Template that uses observable properties

91

const template = html<ObservableExample>`

92

<div class="user-info">

93

<h2>${x => x.fullName || 'No name'}</h2>

94

<p>Age: ${x => x.age} ${x => x.isAdult ? '(Adult)' : '(Minor)'}</p>

95

<p>Items: ${x => x.itemCount}</p>

96

</div>

97

98

<div class="items">

99

${x => x.items.map(item => `<div class="item">${item}</div>`).join('')}

100

</div>

101

102

<div class="settings">

103

Theme: ${x => x.settings.theme}

104

Notifications: ${x => x.settings.notifications ? 'On' : 'Off'}

105

</div>

106

`;

107

108

// Observable properties in nested objects

109

export class NestedObservables {

110

@observable user = {

111

profile: {

112

name: "John",

113

email: "john@example.com"

114

},

115

preferences: {

116

theme: "dark",

117

language: "en"

118

}

119

};

120

121

// Method to update nested properties

122

updateUserName(name: string) {

123

// This will trigger change notifications

124

this.user.profile.name = name;

125

}

126

127

updateTheme(theme: string) {

128

// This will trigger change notifications

129

this.user.preferences.theme = theme;

130

}

131

}

132

```

133

134

### Observable Core APIs

135

136

Central object providing utilities for observable creation, tracking, and notification management.

137

138

```typescript { .api }

139

/**

140

* Common Observable APIs for managing reactive properties and expressions

141

*/

142

const Observable: {

143

/**

144

* Defines an observable property on a target object

145

* @param target - The object to define the property on

146

* @param nameOrAccessor - The property name or accessor

147

*/

148

defineProperty(target: any, nameOrAccessor: string | Accessor): void;

149

150

/**

151

* Gets the notifier for a source object

152

* @param source - The object to get the notifier for

153

* @returns The notifier for change notifications

154

*/

155

getNotifier(source: any): Notifier;

156

157

/**

158

* Creates a binding observer for an expression

159

* @param evaluate - The expression to observe

160

* @param observer - The observer to notify on changes

161

* @param isVolatile - Whether the binding is volatile

162

* @returns An expression observer

163

*/

164

binding<T>(

165

evaluate: Expression<T>,

166

observer: ExpressionObserver,

167

isVolatile?: boolean

168

): ExpressionObserver<any, T>;

169

170

/**

171

* Tracks property access for dependency collection

172

* @param target - The target object

173

* @param propertyName - The property being accessed

174

*/

175

track(target: any, propertyName: PropertyKey): void;

176

177

/**

178

* Notifies observers of property changes

179

* @param source - The source object that changed

180

* @param args - Arguments describing the change

181

*/

182

notify(source: any, args: any): void;

183

184

/**

185

* Determines if an expression should be treated as volatile

186

* @param expression - The expression to check

187

* @returns True if the expression is volatile

188

*/

189

isVolatileBinding(expression: Expression<any>): boolean;

190

191

/**

192

* Marks the current evaluation as volatile

193

*/

194

trackVolatile(): void;

195

196

/**

197

* Sets the array observer factory

198

* @param factory - Function that creates array observers

199

*/

200

setArrayObserverFactory(factory: (array: any[]) => Notifier): void;

201

};

202

```

203

204

**Usage Examples:**

205

206

```typescript

207

import { Observable, Notifier, Subscriber } from "@microsoft/fast-element";

208

209

// Manual observable property definition

210

class ManualObservable {

211

private _value: string = "";

212

213

constructor() {

214

// Define observable property manually

215

Observable.defineProperty(this, "value");

216

}

217

218

get value(): string {

219

return this._value;

220

}

221

222

set value(newValue: string) {

223

if (this._value !== newValue) {

224

const oldValue = this._value;

225

this._value = newValue;

226

// Notify observers of the change

227

Observable.notify(this, { oldValue, newValue });

228

}

229

}

230

}

231

232

// Custom subscriber for observable changes

233

class CustomSubscriber implements Subscriber {

234

handleChange(source: any, args: any): void {

235

console.log("Property changed:", args);

236

}

237

}

238

239

// Track property access and subscribe to changes

240

const obj = new ManualObservable();

241

const notifier = Observable.getNotifier(obj);

242

const subscriber = new CustomSubscriber();

243

244

notifier.subscribe(subscriber, "value");

245

246

obj.value = "new value"; // Triggers subscriber notification

247

248

// Custom expression observer

249

class LoggingObserver implements Subscriber {

250

handleChange(source: any, args: any): void {

251

console.log("Expression changed:", source, args);

252

}

253

}

254

255

const target = { name: "John", age: 30 };

256

Observable.defineProperty(target, "name");

257

Observable.defineProperty(target, "age");

258

259

// Create expression observer

260

const fullNameExpression = (source: typeof target) => `${source.name} (${source.age})`;

261

const observer = new LoggingObserver();

262

const binding = Observable.binding(fullNameExpression, observer, false);

263

264

// Simulate binding

265

const controller = {

266

source: target,

267

context: { index: 0, length: 1, parent: null, parentContext: null },

268

isBound: true,

269

onUnbind: () => {},

270

sourceLifetime: undefined

271

};

272

273

binding.bind(controller); // Sets up observation

274

275

target.name = "Jane"; // Triggers expression re-evaluation and logging

276

target.age = 31; // Triggers expression re-evaluation and logging

277

```

278

279

### Property Change Notifier

280

281

Implementation that manages change notifications for individual properties on objects.

282

283

```typescript { .api }

284

/**

285

* Manages property change notifications for an object

286

*/

287

class PropertyChangeNotifier implements Notifier {

288

/**

289

* Creates a notifier for the specified source object

290

* @param source - The object to create notifications for

291

*/

292

constructor(source: any);

293

294

/**

295

* Subscribes to changes for a specific property

296

* @param subscriber - The subscriber to notify of changes

297

* @param propertyToWatch - The property name to watch

298

*/

299

subscribe(subscriber: Subscriber, propertyToWatch?: string): void;

300

301

/**

302

* Unsubscribes from changes for a specific property

303

* @param subscriber - The subscriber to remove

304

* @param propertyToWatch - The property name to stop watching

305

*/

306

unsubscribe(subscriber: Subscriber, propertyToWatch?: string): void;

307

308

/**

309

* Notifies subscribers of property changes

310

* @param args - Arguments describing the change

311

*/

312

notify(args: any): void;

313

}

314

315

/**

316

* Set of subscribers for efficient notification management

317

*/

318

class SubscriberSet {

319

/**

320

* Adds a subscriber to the set

321

* @param subscriber - The subscriber to add

322

*/

323

add(subscriber: Subscriber): boolean;

324

325

/**

326

* Removes a subscriber from the set

327

* @param subscriber - The subscriber to remove

328

*/

329

remove(subscriber: Subscriber): boolean;

330

331

/**

332

* Notifies all subscribers in the set

333

* @param args - Arguments to pass to subscribers

334

*/

335

notify(args: any): void;

336

}

337

```

338

339

**Usage Examples:**

340

341

```typescript

342

import { PropertyChangeNotifier, Subscriber, Observable } from "@microsoft/fast-element";

343

344

// Custom object with manual change notifications

345

class DataModel {

346

private _name: string = "";

347

private _status: string = "pending";

348

private notifier: PropertyChangeNotifier;

349

350

constructor() {

351

this.notifier = new PropertyChangeNotifier(this);

352

}

353

354

get name(): string {

355

Observable.track(this, "name");

356

return this._name;

357

}

358

359

set name(value: string) {

360

if (this._name !== value) {

361

const oldValue = this._name;

362

this._name = value;

363

this.notifier.notify({

364

type: "change",

365

propertyName: "name",

366

oldValue,

367

newValue: value

368

});

369

}

370

}

371

372

get status(): string {

373

Observable.track(this, "status");

374

return this._status;

375

}

376

377

set status(value: string) {

378

if (this._status !== value) {

379

const oldValue = this._status;

380

this._status = value;

381

this.notifier.notify({

382

type: "change",

383

propertyName: "status",

384

oldValue,

385

newValue: value

386

});

387

}

388

}

389

390

// Subscribe to specific property changes

391

onNameChanged(callback: (oldValue: string, newValue: string) => void): void {

392

const subscriber: Subscriber = {

393

handleChange: (source, args) => {

394

if (args.propertyName === "name") {

395

callback(args.oldValue, args.newValue);

396

}

397

}

398

};

399

this.notifier.subscribe(subscriber, "name");

400

}

401

402

onStatusChanged(callback: (oldValue: string, newValue: string) => void): void {

403

const subscriber: Subscriber = {

404

handleChange: (source, args) => {

405

if (args.propertyName === "status") {

406

callback(args.oldValue, args.newValue);

407

}

408

}

409

};

410

this.notifier.subscribe(subscriber, "status");

411

}

412

}

413

414

// Usage

415

const model = new DataModel();

416

417

model.onNameChanged((oldVal, newVal) => {

418

console.log(`Name changed from ${oldVal} to ${newVal}`);

419

});

420

421

model.onStatusChanged((oldVal, newVal) => {

422

console.log(`Status changed from ${oldVal} to ${newVal}`);

423

});

424

425

model.name = "John"; // Logs: "Name changed from to John"

426

model.status = "active"; // Logs: "Status changed from pending to active"

427

```

428

429

### Array Observation

430

431

Specialized system for tracking changes in arrays, including splice operations, sorting, and length changes.

432

433

```typescript { .api }

434

/**

435

* Observes changes to arrays, tracking splices and mutations

436

*/

437

class ArrayObserver implements Notifier {

438

/**

439

* Creates an array observer for the specified array

440

* @param array - The array to observe

441

*/

442

constructor(array: any[]);

443

444

/** Subscribes to array change notifications */

445

subscribe(subscriber: Subscriber): void;

446

447

/** Unsubscribes from array change notifications */

448

unsubscribe(subscriber: Subscriber): void;

449

450

/** Notifies subscribers of array changes */

451

notify(args: any): void;

452

}

453

454

/**

455

* Observes array length changes specifically

456

*/

457

class LengthObserver {

458

/**

459

* Creates a length observer for the specified array

460

* @param array - The array to observe length changes for

461

*/

462

constructor(array: any[]);

463

464

/** Gets the current length of the observed array */

465

getValue(): number;

466

}

467

468

/**

469

* Gets an observable version of array length

470

* @param array - The array to get the length of

471

* @returns Observable length value

472

*/

473

function lengthOf(array: any[]): number;

474

475

/**

476

* Represents a splice operation on an array

477

*/

478

class Splice {

479

/** Indicates that this splice represents a complete array reset */

480

reset?: boolean;

481

482

/**

483

* Creates a splice record

484

* @param index - The index where the splice occurs

485

* @param removed - The items that were removed

486

* @param addedCount - The number of items that were added

487

*/

488

constructor(

489

public index: number,

490

public removed: any[],

491

public addedCount: number

492

);

493

494

/**

495

* Adjusts the splice index based on the provided array

496

* @param array - The array to adjust to

497

* @returns The same splice, mutated based on the reference array

498

*/

499

adjustTo(array: any[]): this;

500

}

501

502

/**

503

* Represents a sort operation on an array

504

*/

505

class Sort {

506

/**

507

* Creates a sort update record

508

* @param sorted - The updated index positions of sorted items

509

*/

510

constructor(public sorted?: number[]);

511

}

512

513

/**

514

* Strategy for handling array splice operations

515

*/

516

interface SpliceStrategy {

517

/** The level of support provided by this strategy */

518

readonly support: SpliceStrategySupport;

519

520

/**

521

* Processes array changes and returns splice records

522

* @param array - The array that changed

523

* @param oldArray - The previous state of the array

524

* @returns Array of splice records describing the changes

525

*/

526

process(array: any[], oldArray?: any[]): Splice[];

527

528

/** Resets the strategy state */

529

reset(): void;

530

}

531

532

/** Available splice strategy support levels */

533

const SpliceStrategySupport: {

534

readonly reset: 1;

535

readonly splice: 2;

536

readonly optimized: 3;

537

};

538

```

539

540

**Usage Examples:**

541

542

```typescript

543

import {

544

ArrayObserver,

545

lengthOf,

546

Splice,

547

Sort,

548

Subscriber,

549

Observable

550

} from "@microsoft/fast-element";

551

552

// Observable array in a component

553

@customElement("array-example")

554

export class ArrayExample extends FASTElement {

555

@observable items: string[] = ["apple", "banana", "orange"];

556

@observable numbers: number[] = [1, 2, 3, 4, 5];

557

558

// Computed property using lengthOf

559

get itemCount(): number {

560

return lengthOf(this.items);

561

}

562

563

// Methods that modify arrays

564

addItem(item: string) {

565

this.items.push(item); // Triggers array change notification

566

}

567

568

removeItem(index: number) {

569

this.items.splice(index, 1); // Triggers splice notification

570

}

571

572

sortItems() {

573

this.items.sort(); // Triggers sort notification

574

}

575

576

clearItems() {

577

this.items.length = 0; // Triggers array reset

578

}

579

580

replaceItems(newItems: string[]) {

581

this.items = newItems; // Triggers complete array replacement

582

}

583

584

// Bulk operations

585

addMultipleItems(items: string[]) {

586

this.items.push(...items); // Single splice operation

587

}

588

589

insertItem(index: number, item: string) {

590

this.items.splice(index, 0, item); // Insert at specific index

591

}

592

}

593

594

// Custom array subscriber

595

class ArrayChangeLogger implements Subscriber {

596

handleChange(source: any[], args: Splice | Sort): void {

597

if (args instanceof Splice) {

598

console.log("Array splice:", {

599

index: args.index,

600

removed: args.removed,

601

added: args.addedCount,

602

reset: args.reset

603

});

604

} else if (args instanceof Sort) {

605

console.log("Array sorted:", args.sorted);

606

}

607

}

608

}

609

610

// Manual array observation

611

const observableArray = ["a", "b", "c"];

612

Observable.setArrayObserverFactory((array) => new ArrayObserver(array));

613

614

const arrayNotifier = Observable.getNotifier(observableArray);

615

const logger = new ArrayChangeLogger();

616

arrayNotifier.subscribe(logger);

617

618

observableArray.push("d"); // Logs splice operation

619

observableArray.sort(); // Logs sort operation

620

observableArray.splice(1, 2, "x", "y"); // Logs splice with removal and addition

621

622

// Template that reacts to array changes

623

const arrayTemplate = html<ArrayExample>`

624

<div class="summary">

625

Total items: ${x => lengthOf(x.items)}

626

</div>

627

628

<ul class="items">

629

${x => x.items.map((item, index) =>

630

`<li data-index="${index}">${item}</li>`

631

).join('')}

632

</ul>

633

634

<div class="numbers">

635

Sum: ${x => x.numbers.reduce((sum, num) => sum + num, 0)}

636

</div>

637

638

<button @click="${x => x.addItem('new item')}">Add Item</button>

639

<button @click="${x => x.sortItems()}">Sort Items</button>

640

<button @click="${x => x.clearItems()}">Clear All</button>

641

`;

642

643

// Advanced array operations with custom splice strategy

644

class CustomSpliceStrategy implements SpliceStrategy {

645

readonly support = SpliceStrategySupport.optimized;

646

647

process(array: any[], oldArray?: any[]): Splice[] {

648

// Custom logic for optimizing splice detection

649

if (!oldArray) {

650

return [new Splice(0, [], array.length)];

651

}

652

653

// Implement custom diffing algorithm

654

const splices: Splice[] = [];

655

// ... custom splice detection logic

656

return splices;

657

}

658

659

reset(): void {

660

// Reset strategy state

661

}

662

}

663

```

664

665

### Volatile Properties

666

667

System for marking properties as volatile, ensuring they are always re-evaluated even when dependencies haven't changed.

668

669

```typescript { .api }

670

/**

671

* Decorator: Marks a property getter as having volatile observable dependencies

672

* @param target - The target that the property is defined on

673

* @param name - The property name or accessor

674

* @param descriptor - The existing property descriptor

675

* @returns Modified property descriptor that tracks volatility

676

*/

677

function volatile(

678

target: {},

679

name: string | Accessor,

680

descriptor: PropertyDescriptor

681

): PropertyDescriptor;

682

```

683

684

**Usage Examples:**

685

686

```typescript

687

import { FASTElement, customElement, html, observable, volatile } from "@microsoft/fast-element";

688

689

@customElement("volatile-example")

690

export class VolatileExample extends FASTElement {

691

@observable currentTime: Date = new Date();

692

@observable counter: number = 0;

693

694

// Volatile property - always re-evaluates

695

@volatile

696

get timestamp(): string {

697

return new Date().toISOString(); // Always returns current time

698

}

699

700

// Volatile computed property

701

@volatile

702

get randomValue(): number {

703

return Math.random(); // Always returns a new random number

704

}

705

706

// Volatile property that depends on external state

707

@volatile

708

get windowWidth(): number {

709

return window.innerWidth; // Always gets current window width

710

}

711

712

// Non-volatile computed property (for comparison)

713

get formattedCounter(): string {

714

return `Count: ${this.counter}`;

715

}

716

717

// Volatile property with complex logic

718

@volatile

719

get systemStatus(): string {

720

// This might depend on external factors that can't be tracked

721

const isOnline = navigator.onLine;

722

const memoryUsage = (performance as any).memory?.usedJSHeapSize;

723

const currentLoad = Math.random(); // Simulated system load

724

725

if (!isOnline) return "offline";

726

if (memoryUsage > 50000000) return "high-memory";

727

if (currentLoad > 0.8) return "high-load";

728

return "normal";

729

}

730

731

connectedCallback() {

732

super.connectedCallback();

733

734

// Update non-volatile observable periodically

735

setInterval(() => {

736

this.currentTime = new Date();

737

this.counter++;

738

}, 1000);

739

}

740

}

741

742

// Template that uses volatile properties

743

const volatileTemplate = html<VolatileExample>`

744

<div class="timestamps">

745

<p>Stored Time: ${x => x.currentTime.toISOString()}</p>

746

<p>Current Time: ${x => x.timestamp}</p>

747

</div>

748

749

<div class="values">

750

<p>Counter: ${x => x.formattedCounter}</p>

751

<p>Random: ${x => x.randomValue}</p>

752

<p>Window Width: ${x => x.windowWidth}px</p>

753

</div>

754

755

<div class="status">

756

System Status: ${x => x.systemStatus}

757

</div>

758

`;

759

760

// Example showing difference between volatile and non-volatile

761

@customElement("comparison-example")

762

export class ComparisonExample extends FASTElement {

763

@observable triggerUpdate: number = 0;

764

765

// Non-volatile: only re-evaluates when triggerUpdate changes

766

get nonVolatileRandom(): number {

767

console.log("Non-volatile random calculated");

768

return Math.random();

769

}

770

771

// Volatile: re-evaluates on every template update

772

@volatile

773

get volatileRandom(): number {

774

console.log("Volatile random calculated");

775

return Math.random();

776

}

777

778

triggerChange() {

779

this.triggerUpdate++;

780

}

781

}

782

783

const comparisonTemplate = html<ComparisonExample>`

784

<div>

785

<p>Trigger: ${x => x.triggerUpdate}</p>

786

<p>Non-volatile: ${x => x.nonVolatileRandom}</p>

787

<p>Volatile: ${x => x.volatileRandom}</p>

788

<button @click="${x => x.triggerChange()}">Update</button>

789

</div>

790

`;

791

792

// Custom volatile behavior

793

class CustomVolatileClass {

794

@observable baseValue: number = 10;

795

796

// Volatile getter that depends on external timing

797

@volatile

798

get timeBasedValue(): number {

799

const seconds = new Date().getSeconds();

800

return this.baseValue * (seconds % 10);

801

}

802

803

// Volatile getter for external API calls

804

@volatile

805

get externalData(): Promise<any> {

806

// Always fetches fresh data

807

return fetch('/api/current-data').then(r => r.json());

808

}

809

}

810

```

811

812

### Update Queue System

813

814

Batch update system that manages asynchronous property change notifications for optimal performance.

815

816

```typescript { .api }

817

/**

818

* Manages batched updates for optimal performance

819

*/

820

const Updates: {

821

/**

822

* Enqueues a task for batch processing

823

* @param callable - The task to enqueue

824

*/

825

enqueue(callable: Callable): void;

826

827

/**

828

* Processes all enqueued updates immediately

829

*/

830

process(): void;

831

832

/**

833

* Sets the update mode

834

* @param isAsync - Whether updates should be processed asynchronously

835

*/

836

setMode(isAsync: boolean): void;

837

};

838

839

/**

840

* Update queue for managing batched notifications

841

*/

842

const UpdateQueue: {

843

/**

844

* Enqueues an observer for update processing

845

* @param observer - The observer to enqueue

846

*/

847

enqueue(observer: any): void;

848

849

/**

850

* Processes the update queue

851

*/

852

process(): void;

853

};

854

```

855

856

## Types

857

858

```typescript { .api }

859

/**

860

* Represents a getter/setter property accessor on an object

861

*/

862

interface Accessor {

863

/** The name of the property */

864

name: string;

865

866

/**

867

* Gets the value of the property on the source object

868

* @param source - The source object to access

869

*/

870

getValue(source: any): any;

871

872

/**

873

* Sets the value of the property on the source object

874

* @param source - The source object to access

875

* @param value - The value to set the property to

876

*/

877

setValue(source: any, value: any): void;

878

}

879

880

/**

881

* A record of observable property access

882

*/

883

interface ObservationRecord {

884

/** The source object with an observable property that was accessed */

885

propertySource: any;

886

887

/** The name of the observable property that was accessed */

888

propertyName: string;

889

}

890

891

/**

892

* Describes how the source's lifetime relates to its controller's lifetime

893

*/

894

type SourceLifetime = undefined | 1; // unknown | coupled

895

896

/**

897

* Controls the lifecycle of an expression and provides relevant context

898

*/

899

interface ExpressionController<TSource = any, TParent = any> {

900

/** The source the expression is evaluated against */

901

readonly source: TSource;

902

903

/** Indicates how the source's lifetime relates to the controller's lifetime */

904

readonly sourceLifetime?: SourceLifetime;

905

906

/** The context the expression is evaluated against */

907

readonly context: ExecutionContext<TParent>;

908

909

/** Indicates whether the controller is bound */

910

readonly isBound: boolean;

911

912

/**

913

* Registers an unbind handler with the controller

914

* @param behavior - An object to call when the controller unbinds

915

*/

916

onUnbind(behavior: { unbind(controller: ExpressionController<TSource, TParent>): void }): void;

917

}

918

919

/**

920

* Observes an expression for changes

921

*/

922

interface ExpressionObserver<TSource = any, TReturn = any, TParent = any> {

923

/**

924

* Binds the expression to the source

925

* @param controller - The controller that manages the lifecycle and context

926

*/

927

bind(controller: ExpressionController<TSource, TParent>): TReturn;

928

}

929

930

/**

931

* Provides additional contextual information available to behaviors and expressions

932

*/

933

interface ExecutionContext<TParent = any> {

934

/** The index of the current item within a repeat context */

935

index: number;

936

937

/** The length of the current collection within a repeat context */

938

length: number;

939

940

/** The parent data source within a nested context */

941

parent: TParent;

942

943

/** The parent execution context when in nested context scenarios */

944

parentContext: ExecutionContext<TParent>;

945

946

/** The current event within an event handler */

947

readonly event: Event;

948

949

/** Indicates whether the current item has an even index */

950

readonly isEven: boolean;

951

952

/** Indicates whether the current item has an odd index */

953

readonly isOdd: boolean;

954

955

/** Indicates whether the current item is the first item */

956

readonly isFirst: boolean;

957

958

/** Indicates whether the current item is in the middle */

959

readonly isInMiddle: boolean;

960

961

/** Indicates whether the current item is the last item */

962

readonly isLast: boolean;

963

964

/** Returns the typed event detail of a custom event */

965

eventDetail<TDetail>(): TDetail;

966

967

/** Returns the typed event target of the event */

968

eventTarget<TTarget extends EventTarget>(): TTarget;

969

}

970

```