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

context-system.mddocs/

0

# Context System

1

2

Context protocol for sharing data and services across component trees using events and dependency injection integration for loosely coupled communication.

3

4

## Capabilities

5

6

### Context Creation

7

8

Functions for creating context objects that can share data across component hierarchies using events.

9

10

```typescript { .api }

11

/**

12

* Creates a context for sharing data across component trees

13

* @param name - A unique name for the context

14

* @param initialValue - Optional initial value for the context

15

* @returns A context object

16

*/

17

function createContext<T>(name: string, initialValue?: T): FASTContext<T>;

18

19

/**

20

* Context object for data sharing

21

*/

22

interface FASTContext<T> extends ContextDecorator<T> {

23

/**

24

* Gets the context value from a target element

25

* @param target - The target element to get the context from

26

* @returns The context value or undefined

27

*/

28

get(target: EventTarget): T | undefined;

29

30

/**

31

* Provides a context value to a target element

32

* @param target - The target element to provide the context to

33

* @param value - The value to provide

34

*/

35

provide(target: EventTarget, value: T): void;

36

37

/**

38

* Requests a context value from a target element

39

* @param target - The target element to request from

40

* @param callback - Callback to receive the context value

41

* @param multiple - Whether to receive multiple values

42

*/

43

request(

44

target: EventTarget,

45

callback: ContextCallback<T>,

46

multiple?: boolean

47

): void;

48

}

49

50

/**

51

* Context utilities

52

*/

53

const Context: {

54

/**

55

* Creates a new context

56

* @param name - The context name

57

* @param initialValue - Optional initial value

58

*/

59

create<T>(name: string, initialValue?: T): FASTContext<T>;

60

61

/**

62

* Sets the request strategy for context resolution

63

* @param strategy - The strategy to use

64

*/

65

setRequestStrategy(strategy: FASTContextRequestStrategy): void;

66

};

67

68

/**

69

* Context callback type

70

*/

71

type ContextCallback<T> = (value: T, dispose?: () => void) => void;

72

73

/**

74

* Context decorator type

75

*/

76

interface ContextDecorator<T> {

77

/** The context name */

78

readonly name: string;

79

80

/** Optional initial value */

81

readonly initialValue?: T;

82

}

83

```

84

85

**Usage Examples:**

86

87

```typescript

88

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

89

import { Context } from "@microsoft/fast-element/context.js";

90

91

// Create shared contexts

92

const userContext = Context.create<User>('user');

93

const themeContext = Context.create<Theme>('theme', { mode: 'light', primaryColor: '#007ACC' });

94

const authContext = Context.create<AuthState>('auth', { isAuthenticated: false });

95

96

// Root application provider

97

@customElement("app-root")

98

export class AppRoot extends FASTElement {

99

private currentUser: User | null = null;

100

private currentTheme: Theme = { mode: 'light', primaryColor: '#007ACC' };

101

private authState: AuthState = { isAuthenticated: false, permissions: [] };

102

103

connectedCallback() {

104

super.connectedCallback();

105

this.setupContextProviders();

106

this.loadInitialData();

107

}

108

109

private setupContextProviders() {

110

// Provide contexts to the component tree

111

userContext.provide(this, this.currentUser);

112

themeContext.provide(this, this.currentTheme);

113

authContext.provide(this, this.authState);

114

}

115

116

private async loadInitialData() {

117

// Simulate loading user data

118

try {

119

const user = await this.fetchCurrentUser();

120

this.setUser(user);

121

122

const theme = await this.loadUserTheme();

123

this.setTheme(theme);

124

125

this.setAuthState({

126

isAuthenticated: true,

127

permissions: user.permissions

128

});

129

} catch (error) {

130

console.error('Failed to load initial data:', error);

131

}

132

}

133

134

setUser(user: User | null) {

135

this.currentUser = user;

136

userContext.provide(this, user);

137

}

138

139

setTheme(theme: Theme) {

140

this.currentTheme = theme;

141

themeContext.provide(this, theme);

142

this.applyTheme(theme);

143

}

144

145

setAuthState(authState: AuthState) {

146

this.authState = authState;

147

authContext.provide(this, authState);

148

}

149

150

private async fetchCurrentUser(): Promise<User> {

151

// Simulate API call

152

return {

153

id: '1',

154

name: 'John Doe',

155

email: 'john@example.com',

156

permissions: ['read', 'write']

157

};

158

}

159

160

private async loadUserTheme(): Promise<Theme> {

161

// Simulate loading user's preferred theme

162

return { mode: 'dark', primaryColor: '#FF6B6B' };

163

}

164

165

private applyTheme(theme: Theme) {

166

document.documentElement.setAttribute('data-theme', theme.mode);

167

document.documentElement.style.setProperty('--primary-color', theme.primaryColor);

168

}

169

170

static template = html<AppRoot>`

171

<div class="app-root">

172

<app-header></app-header>

173

<main>

174

<user-profile></user-profile>

175

<theme-selector></theme-selector>

176

<protected-content></protected-content>

177

</main>

178

<app-footer></app-footer>

179

</div>

180

`;

181

}

182

183

// Component that consumes user context

184

@customElement("user-profile")

185

export class UserProfile extends FASTElement {

186

private user: User | null = null;

187

188

connectedCallback() {

189

super.connectedCallback();

190

this.subscribeToUserContext();

191

}

192

193

private subscribeToUserContext() {

194

userContext.request(this, (value, dispose) => {

195

this.user = value;

196

this.$fastController.update();

197

198

// Store dispose function to clean up later if needed

199

this.contextDisposer = dispose;

200

});

201

}

202

203

disconnectedCallback() {

204

super.disconnectedCallback();

205

this.contextDisposer?.();

206

}

207

208

private contextDisposer?: () => void;

209

210

static template = html<UserProfile>`

211

<div class="user-profile">

212

${x => x.user ? html`

213

<div class="user-info">

214

<h2>${x => x.user!.name}</h2>

215

<p>${x => x.user!.email}</p>

216

<div class="permissions">

217

Permissions: ${x => x.user!.permissions.join(', ')}

218

</div>

219

</div>

220

` : html`

221

<div class="no-user">No user logged in</div>

222

`}

223

</div>

224

`;

225

}

226

227

// Component that consumes and modifies theme context

228

@customElement("theme-selector")

229

export class ThemeSelector extends FASTElement {

230

private theme: Theme = { mode: 'light', primaryColor: '#007ACC' };

231

232

connectedCallback() {

233

super.connectedCallback();

234

this.subscribeToThemeContext();

235

}

236

237

private subscribeToThemeContext() {

238

themeContext.request(this, (value) => {

239

this.theme = value;

240

this.$fastController.update();

241

});

242

}

243

244

private updateTheme(updates: Partial<Theme>) {

245

const newTheme = { ...this.theme, ...updates };

246

247

// Update theme in the root component

248

const appRoot = this.closest('app-root') as AppRoot;

249

if (appRoot) {

250

appRoot.setTheme(newTheme);

251

}

252

}

253

254

private toggleMode() {

255

this.updateTheme({

256

mode: this.theme.mode === 'light' ? 'dark' : 'light'

257

});

258

}

259

260

private changePrimaryColor(color: string) {

261

this.updateTheme({ primaryColor: color });

262

}

263

264

static template = html<ThemeSelector>`

265

<div class="theme-selector">

266

<h3>Theme Settings</h3>

267

268

<div class="current-theme">

269

<p>Mode: ${x => x.theme.mode}</p>

270

<p>Primary Color: ${x => x.theme.primaryColor}</p>

271

</div>

272

273

<div class="theme-controls">

274

<button @click="${x => x.toggleMode()}">

275

Toggle Mode (${x => x.theme.mode === 'light' ? 'Switch to Dark' : 'Switch to Light'})

276

</button>

277

278

<div class="color-options">

279

<button @click="${x => x.changePrimaryColor('#007ACC')}">Blue</button>

280

<button @click="${x => x.changePrimaryColor('#FF6B6B')}">Red</button>

281

<button @click="${x => x.changePrimaryColor('#4ECDC4')}">Teal</button>

282

<button @click="${x => x.changePrimaryColor('#45B7D1')}">Sky Blue</button>

283

</div>

284

</div>

285

</div>

286

`;

287

}

288

289

// Component that uses multiple contexts

290

@customElement("protected-content")

291

export class ProtectedContent extends FASTElement {

292

private user: User | null = null;

293

private authState: AuthState = { isAuthenticated: false, permissions: [] };

294

private theme: Theme = { mode: 'light', primaryColor: '#007ACC' };

295

296

connectedCallback() {

297

super.connectedCallback();

298

this.subscribeToContexts();

299

}

300

301

private subscribeToContexts() {

302

// Subscribe to multiple contexts

303

userContext.request(this, (value) => {

304

this.user = value;

305

this.$fastController.update();

306

});

307

308

authContext.request(this, (value) => {

309

this.authState = value;

310

this.$fastController.update();

311

});

312

313

themeContext.request(this, (value) => {

314

this.theme = value;

315

this.$fastController.update();

316

});

317

}

318

319

private get canViewContent(): boolean {

320

return this.authState.isAuthenticated &&

321

this.authState.permissions.includes('read');

322

}

323

324

private get canEditContent(): boolean {

325

return this.authState.isAuthenticated &&

326

this.authState.permissions.includes('write');

327

}

328

329

static template = html<ProtectedContent>`

330

<div class="protected-content"

331

style="border-color: ${x => x.theme.primaryColor}">

332

<h3>Protected Content</h3>

333

334

${x => x.canViewContent ? html`

335

<div class="content">

336

<p>This is protected content that requires authentication.</p>

337

<p>Current user: ${x => x.user?.name || 'Unknown'}</p>

338

<p>Theme: ${x => x.theme.mode} mode</p>

339

340

${x => x.canEditContent ? html`

341

<div class="edit-controls">

342

<button>Edit Content</button>

343

<button>Delete Content</button>

344

</div>

345

` : html`

346

<p><em>You don't have edit permissions</em></p>

347

`}

348

</div>

349

` : html`

350

<div class="access-denied">

351

<p>You need to be logged in to view this content.</p>

352

</div>

353

`}

354

</div>

355

`;

356

}

357

358

// Context for complex state with actions

359

interface AppStateContext {

360

notifications: Notification[];

361

loading: boolean;

362

error: string | null;

363

}

364

365

interface AppActions {

366

addNotification(notification: Notification): void;

367

removeNotification(id: string): void;

368

setLoading(loading: boolean): void;

369

setError(error: string | null): void;

370

}

371

372

const appStateContext = Context.create<AppStateContext & AppActions>('appState');

373

374

// Provider component with actions

375

@customElement("app-state-provider")

376

export class AppStateProvider extends FASTElement {

377

private state: AppStateContext = {

378

notifications: [],

379

loading: false,

380

error: null

381

};

382

383

private actions: AppActions = {

384

addNotification: (notification: Notification) => {

385

this.state.notifications.push(notification);

386

this.updateContext();

387

},

388

389

removeNotification: (id: string) => {

390

this.state.notifications = this.state.notifications.filter(n => n.id !== id);

391

this.updateContext();

392

},

393

394

setLoading: (loading: boolean) => {

395

this.state.loading = loading;

396

this.updateContext();

397

},

398

399

setError: (error: string | null) => {

400

this.state.error = error;

401

this.updateContext();

402

}

403

};

404

405

connectedCallback() {

406

super.connectedCallback();

407

this.provideContext();

408

}

409

410

private provideContext() {

411

const contextValue = { ...this.state, ...this.actions };

412

appStateContext.provide(this, contextValue);

413

}

414

415

private updateContext() {

416

const contextValue = { ...this.state, ...this.actions };

417

appStateContext.provide(this, contextValue);

418

}

419

420

static template = html<AppStateProvider>`

421

<div class="app-state-provider">

422

<slot></slot>

423

</div>

424

`;

425

}

426

427

// Consumer component using state and actions

428

@customElement("notification-center")

429

export class NotificationCenter extends FASTElement {

430

private appState?: AppStateContext & AppActions;

431

432

connectedCallback() {

433

super.connectedCallback();

434

this.subscribeToAppState();

435

}

436

437

private subscribeToAppState() {

438

appStateContext.request(this, (value) => {

439

this.appState = value;

440

this.$fastController.update();

441

});

442

}

443

444

private addSampleNotification() {

445

this.appState?.addNotification({

446

id: `notification-${Date.now()}`,

447

message: 'This is a sample notification',

448

type: 'info',

449

timestamp: new Date()

450

});

451

}

452

453

private removeNotification(id: string) {

454

this.appState?.removeNotification(id);

455

}

456

457

static template = html<NotificationCenter>`

458

<div class="notification-center">

459

<h3>Notifications ${x => x.appState ? `(${x.appState.notifications.length})` : ''}</h3>

460

461

<button @click="${x => x.addSampleNotification()}">

462

Add Notification

463

</button>

464

465

<div class="notifications">

466

${x => x.appState?.notifications.map(notification =>

467

`<div class="notification ${notification.type}">

468

<span>${notification.message}</span>

469

<button onclick="this.getRootNode().host.removeNotification('${notification.id}')">×</button>

470

</div>`

471

).join('') || ''}

472

</div>

473

474

${x => x.appState?.loading ? html`<div class="loading">Loading...</div>` : ''}

475

${x => x.appState?.error ? html`<div class="error">${x => x.appState!.error}</div>` : ''}

476

</div>

477

`;

478

}

479

480

interface User {

481

id: string;

482

name: string;

483

email: string;

484

permissions: string[];

485

}

486

487

interface Theme {

488

mode: 'light' | 'dark';

489

primaryColor: string;

490

}

491

492

interface AuthState {

493

isAuthenticated: boolean;

494

permissions: string[];

495

}

496

497

interface Notification {

498

id: string;

499

message: string;

500

type: 'info' | 'warning' | 'error' | 'success';

501

timestamp: Date;

502

}

503

```

504

505

### Context Events

506

507

Event-based system for context communication with custom event handling and bubbling.

508

509

```typescript { .api }

510

/**

511

* Context event class for requesting context values

512

*/

513

class ContextEvent<T> extends CustomEvent<ContextRequestEventDetail<T>> {

514

/**

515

* Creates a context event

516

* @param context - The context being requested

517

* @param callback - Callback to receive the context value

518

* @param multiple - Whether to collect multiple providers

519

*/

520

constructor(

521

context: UnknownContext,

522

callback: ContextCallback<T>,

523

multiple?: boolean

524

);

525

526

/** The context being requested */

527

readonly context: UnknownContext;

528

529

/** Callback to provide the context value */

530

readonly callback: ContextCallback<T>;

531

532

/** Whether to collect multiple providers */

533

readonly multiple: boolean;

534

}

535

536

/**

537

* Context request strategy type

538

*/

539

type FASTContextRequestStrategy = (

540

target: EventTarget,

541

context: UnknownContext,

542

callback: ContextCallback<unknown>,

543

multiple?: boolean

544

) => void;

545

546

/**

547

* Context event detail

548

*/

549

interface ContextRequestEventDetail<T> {

550

readonly context: UnknownContext;

551

readonly callback: ContextCallback<T>;

552

readonly multiple?: boolean;

553

}

554

```

555

556

## Types

557

558

```typescript { .api }

559

/**

560

* Unknown context type for generic handling

561

*/

562

type UnknownContext = Context<unknown>;

563

564

/**

565

* Extracts the value type from a context

566

*/

567

type ContextType<T extends Context<any>> = T extends Context<infer U> ? U : never;

568

569

/**

570

* Base context interface

571

*/

572

interface Context<T> {

573

readonly name: string;

574

readonly initialValue?: T;

575

}

576

577

/**

578

* Context callback function signature

579

*/

580

type ContextCallback<T> = (

581

value: T,

582

dispose?: () => void

583

) => void;

584

585

/**

586

* Context decorator interface

587

*/

588

interface ContextDecorator<T> {

589

readonly name: string;

590

readonly initialValue?: T;

591

}

592

```