CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-microsoft--fast-element

A library for constructing Web Components

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

context-system.mddocs/

Context System

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

Capabilities

Context Creation

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

/**
 * Creates a context for sharing data across component trees
 * @param name - A unique name for the context
 * @param initialValue - Optional initial value for the context
 * @returns A context object
 */
function createContext<T>(name: string, initialValue?: T): FASTContext<T>;

/**
 * Context object for data sharing
 */
interface FASTContext<T> extends ContextDecorator<T> {
  /**
   * Gets the context value from a target element
   * @param target - The target element to get the context from
   * @returns The context value or undefined
   */
  get(target: EventTarget): T | undefined;
  
  /**
   * Provides a context value to a target element
   * @param target - The target element to provide the context to
   * @param value - The value to provide
   */
  provide(target: EventTarget, value: T): void;
  
  /**
   * Requests a context value from a target element
   * @param target - The target element to request from
   * @param callback - Callback to receive the context value
   * @param multiple - Whether to receive multiple values
   */
  request(
    target: EventTarget,
    callback: ContextCallback<T>,
    multiple?: boolean
  ): void;
}

/**
 * Context utilities
 */
const Context: {
  /**
   * Creates a new context
   * @param name - The context name
   * @param initialValue - Optional initial value
   */
  create<T>(name: string, initialValue?: T): FASTContext<T>;
  
  /**
   * Sets the request strategy for context resolution
   * @param strategy - The strategy to use
   */
  setRequestStrategy(strategy: FASTContextRequestStrategy): void;
};

/**
 * Context callback type
 */
type ContextCallback<T> = (value: T, dispose?: () => void) => void;

/**
 * Context decorator type
 */
interface ContextDecorator<T> {
  /** The context name */
  readonly name: string;
  
  /** Optional initial value */
  readonly initialValue?: T;
}

Usage Examples:

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

// Create shared contexts
const userContext = Context.create<User>('user');
const themeContext = Context.create<Theme>('theme', { mode: 'light', primaryColor: '#007ACC' });
const authContext = Context.create<AuthState>('auth', { isAuthenticated: false });

// Root application provider
@customElement("app-root")
export class AppRoot extends FASTElement {
  private currentUser: User | null = null;
  private currentTheme: Theme = { mode: 'light', primaryColor: '#007ACC' };
  private authState: AuthState = { isAuthenticated: false, permissions: [] };
  
  connectedCallback() {
    super.connectedCallback();
    this.setupContextProviders();
    this.loadInitialData();
  }
  
  private setupContextProviders() {
    // Provide contexts to the component tree
    userContext.provide(this, this.currentUser);
    themeContext.provide(this, this.currentTheme);
    authContext.provide(this, this.authState);
  }
  
  private async loadInitialData() {
    // Simulate loading user data
    try {
      const user = await this.fetchCurrentUser();
      this.setUser(user);
      
      const theme = await this.loadUserTheme();
      this.setTheme(theme);
      
      this.setAuthState({ 
        isAuthenticated: true, 
        permissions: user.permissions 
      });
    } catch (error) {
      console.error('Failed to load initial data:', error);
    }
  }
  
  setUser(user: User | null) {
    this.currentUser = user;
    userContext.provide(this, user);
  }
  
  setTheme(theme: Theme) {
    this.currentTheme = theme;
    themeContext.provide(this, theme);
    this.applyTheme(theme);
  }
  
  setAuthState(authState: AuthState) {
    this.authState = authState;
    authContext.provide(this, authState);
  }
  
  private async fetchCurrentUser(): Promise<User> {
    // Simulate API call
    return {
      id: '1',
      name: 'John Doe',
      email: 'john@example.com',
      permissions: ['read', 'write']
    };
  }
  
  private async loadUserTheme(): Promise<Theme> {
    // Simulate loading user's preferred theme
    return { mode: 'dark', primaryColor: '#FF6B6B' };
  }
  
  private applyTheme(theme: Theme) {
    document.documentElement.setAttribute('data-theme', theme.mode);
    document.documentElement.style.setProperty('--primary-color', theme.primaryColor);
  }
  
  static template = html<AppRoot>`
    <div class="app-root">
      <app-header></app-header>
      <main>
        <user-profile></user-profile>
        <theme-selector></theme-selector>
        <protected-content></protected-content>
      </main>
      <app-footer></app-footer>
    </div>
  `;
}

// Component that consumes user context
@customElement("user-profile")
export class UserProfile extends FASTElement {
  private user: User | null = null;
  
  connectedCallback() {
    super.connectedCallback();
    this.subscribeToUserContext();
  }
  
  private subscribeToUserContext() {
    userContext.request(this, (value, dispose) => {
      this.user = value;
      this.$fastController.update();
      
      // Store dispose function to clean up later if needed
      this.contextDisposer = dispose;
    });
  }
  
  disconnectedCallback() {
    super.disconnectedCallback();
    this.contextDisposer?.();
  }
  
  private contextDisposer?: () => void;
  
  static template = html<UserProfile>`
    <div class="user-profile">
      ${x => x.user ? html`
        <div class="user-info">
          <h2>${x => x.user!.name}</h2>
          <p>${x => x.user!.email}</p>
          <div class="permissions">
            Permissions: ${x => x.user!.permissions.join(', ')}
          </div>
        </div>
      ` : html`
        <div class="no-user">No user logged in</div>
      `}
    </div>
  `;
}

// Component that consumes and modifies theme context
@customElement("theme-selector")
export class ThemeSelector extends FASTElement {
  private theme: Theme = { mode: 'light', primaryColor: '#007ACC' };
  
  connectedCallback() {
    super.connectedCallback();
    this.subscribeToThemeContext();
  }
  
  private subscribeToThemeContext() {
    themeContext.request(this, (value) => {
      this.theme = value;
      this.$fastController.update();
    });
  }
  
  private updateTheme(updates: Partial<Theme>) {
    const newTheme = { ...this.theme, ...updates };
    
    // Update theme in the root component
    const appRoot = this.closest('app-root') as AppRoot;
    if (appRoot) {
      appRoot.setTheme(newTheme);
    }
  }
  
  private toggleMode() {
    this.updateTheme({ 
      mode: this.theme.mode === 'light' ? 'dark' : 'light' 
    });
  }
  
  private changePrimaryColor(color: string) {
    this.updateTheme({ primaryColor: color });
  }
  
  static template = html<ThemeSelector>`
    <div class="theme-selector">
      <h3>Theme Settings</h3>
      
      <div class="current-theme">
        <p>Mode: ${x => x.theme.mode}</p>
        <p>Primary Color: ${x => x.theme.primaryColor}</p>
      </div>
      
      <div class="theme-controls">
        <button @click="${x => x.toggleMode()}">
          Toggle Mode (${x => x.theme.mode === 'light' ? 'Switch to Dark' : 'Switch to Light'})
        </button>
        
        <div class="color-options">
          <button @click="${x => x.changePrimaryColor('#007ACC')}">Blue</button>
          <button @click="${x => x.changePrimaryColor('#FF6B6B')}">Red</button>
          <button @click="${x => x.changePrimaryColor('#4ECDC4')}">Teal</button>
          <button @click="${x => x.changePrimaryColor('#45B7D1')}">Sky Blue</button>
        </div>
      </div>
    </div>
  `;
}

// Component that uses multiple contexts
@customElement("protected-content")
export class ProtectedContent extends FASTElement {
  private user: User | null = null;
  private authState: AuthState = { isAuthenticated: false, permissions: [] };
  private theme: Theme = { mode: 'light', primaryColor: '#007ACC' };
  
  connectedCallback() {
    super.connectedCallback();
    this.subscribeToContexts();
  }
  
  private subscribeToContexts() {
    // Subscribe to multiple contexts
    userContext.request(this, (value) => {
      this.user = value;
      this.$fastController.update();
    });
    
    authContext.request(this, (value) => {
      this.authState = value;
      this.$fastController.update();
    });
    
    themeContext.request(this, (value) => {
      this.theme = value;
      this.$fastController.update();
    });
  }
  
  private get canViewContent(): boolean {
    return this.authState.isAuthenticated && 
           this.authState.permissions.includes('read');
  }
  
  private get canEditContent(): boolean {
    return this.authState.isAuthenticated && 
           this.authState.permissions.includes('write');
  }
  
  static template = html<ProtectedContent>`
    <div class="protected-content" 
         style="border-color: ${x => x.theme.primaryColor}">
      <h3>Protected Content</h3>
      
      ${x => x.canViewContent ? html`
        <div class="content">
          <p>This is protected content that requires authentication.</p>
          <p>Current user: ${x => x.user?.name || 'Unknown'}</p>
          <p>Theme: ${x => x.theme.mode} mode</p>
          
          ${x => x.canEditContent ? html`
            <div class="edit-controls">
              <button>Edit Content</button>
              <button>Delete Content</button>
            </div>
          ` : html`
            <p><em>You don't have edit permissions</em></p>
          `}
        </div>
      ` : html`
        <div class="access-denied">
          <p>You need to be logged in to view this content.</p>
        </div>
      `}
    </div>
  `;
}

// Context for complex state with actions
interface AppStateContext {
  notifications: Notification[];
  loading: boolean;
  error: string | null;
}

interface AppActions {
  addNotification(notification: Notification): void;
  removeNotification(id: string): void;
  setLoading(loading: boolean): void;
  setError(error: string | null): void;
}

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

// Provider component with actions
@customElement("app-state-provider")
export class AppStateProvider extends FASTElement {
  private state: AppStateContext = {
    notifications: [],
    loading: false,
    error: null
  };
  
  private actions: AppActions = {
    addNotification: (notification: Notification) => {
      this.state.notifications.push(notification);
      this.updateContext();
    },
    
    removeNotification: (id: string) => {
      this.state.notifications = this.state.notifications.filter(n => n.id !== id);
      this.updateContext();
    },
    
    setLoading: (loading: boolean) => {
      this.state.loading = loading;
      this.updateContext();
    },
    
    setError: (error: string | null) => {
      this.state.error = error;
      this.updateContext();
    }
  };
  
  connectedCallback() {
    super.connectedCallback();
    this.provideContext();
  }
  
  private provideContext() {
    const contextValue = { ...this.state, ...this.actions };
    appStateContext.provide(this, contextValue);
  }
  
  private updateContext() {
    const contextValue = { ...this.state, ...this.actions };
    appStateContext.provide(this, contextValue);
  }
  
  static template = html<AppStateProvider>`
    <div class="app-state-provider">
      <slot></slot>
    </div>
  `;
}

// Consumer component using state and actions
@customElement("notification-center")
export class NotificationCenter extends FASTElement {
  private appState?: AppStateContext & AppActions;
  
  connectedCallback() {
    super.connectedCallback();
    this.subscribeToAppState();
  }
  
  private subscribeToAppState() {
    appStateContext.request(this, (value) => {
      this.appState = value;
      this.$fastController.update();
    });
  }
  
  private addSampleNotification() {
    this.appState?.addNotification({
      id: `notification-${Date.now()}`,
      message: 'This is a sample notification',
      type: 'info',
      timestamp: new Date()
    });
  }
  
  private removeNotification(id: string) {
    this.appState?.removeNotification(id);
  }
  
  static template = html<NotificationCenter>`
    <div class="notification-center">
      <h3>Notifications ${x => x.appState ? `(${x.appState.notifications.length})` : ''}</h3>
      
      <button @click="${x => x.addSampleNotification()}">
        Add Notification
      </button>
      
      <div class="notifications">
        ${x => x.appState?.notifications.map(notification => 
          `<div class="notification ${notification.type}">
            <span>${notification.message}</span>
            <button onclick="this.getRootNode().host.removeNotification('${notification.id}')">×</button>
          </div>`
        ).join('') || ''}
      </div>
      
      ${x => x.appState?.loading ? html`<div class="loading">Loading...</div>` : ''}
      ${x => x.appState?.error ? html`<div class="error">${x => x.appState!.error}</div>` : ''}
    </div>
  `;
}

interface User {
  id: string;
  name: string;
  email: string;
  permissions: string[];
}

interface Theme {
  mode: 'light' | 'dark';
  primaryColor: string;
}

interface AuthState {
  isAuthenticated: boolean;
  permissions: string[];
}

interface Notification {
  id: string;
  message: string;
  type: 'info' | 'warning' | 'error' | 'success';
  timestamp: Date;
}

Context Events

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

/**
 * Context event class for requesting context values
 */
class ContextEvent<T> extends CustomEvent<ContextRequestEventDetail<T>> {
  /**
   * Creates a context event
   * @param context - The context being requested
   * @param callback - Callback to receive the context value
   * @param multiple - Whether to collect multiple providers
   */
  constructor(
    context: UnknownContext,
    callback: ContextCallback<T>,
    multiple?: boolean
  );
  
  /** The context being requested */
  readonly context: UnknownContext;
  
  /** Callback to provide the context value */
  readonly callback: ContextCallback<T>;
  
  /** Whether to collect multiple providers */
  readonly multiple: boolean;
}

/**
 * Context request strategy type
 */
type FASTContextRequestStrategy = (
  target: EventTarget,
  context: UnknownContext,
  callback: ContextCallback<unknown>,
  multiple?: boolean
) => void;

/**
 * Context event detail
 */
interface ContextRequestEventDetail<T> {
  readonly context: UnknownContext;
  readonly callback: ContextCallback<T>;
  readonly multiple?: boolean;
}

Types

/**
 * Unknown context type for generic handling
 */
type UnknownContext = Context<unknown>;

/**
 * Extracts the value type from a context
 */
type ContextType<T extends Context<any>> = T extends Context<infer U> ? U : never;

/**
 * Base context interface
 */
interface Context<T> {
  readonly name: string;
  readonly initialValue?: T;
}

/**
 * Context callback function signature
 */
type ContextCallback<T> = (
  value: T,
  dispose?: () => void
) => void;

/**
 * Context decorator interface
 */
interface ContextDecorator<T> {
  readonly name: string;
  readonly initialValue?: T;
}

docs

attributes.md

context-system.md

css-styling.md

data-binding.md

dependency-injection.md

html-templates.md

index.md

observable-system.md

ssr-hydration.md

state-management.md

template-directives.md

testing-utilities.md

utilities.md

web-components.md

tile.json