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

data-binding.mddocs/

Data Binding

Reactive data binding system supporting one-way, two-way, and event bindings with automatic dependency tracking and efficient updates for creating dynamic user interfaces.

Capabilities

One-Way Binding

Creates standard reactive bindings that automatically update when source data changes, supporting both function expressions and binding configurations.

/**
 * Creates a standard one-way binding
 * @param expression - The binding expression to evaluate
 * @param policy - The security policy to associate with the binding
 * @param isVolatile - Indicates whether the binding is volatile or not
 * @returns A binding configuration
 */
function oneWay<T = any>(
  expression: Expression<T>,
  policy?: DOMPolicy,
  isVolatile?: boolean
): Binding<T>;

/**
 * Creates an event listener binding
 * @param expression - The binding to invoke when the event is raised
 * @param options - Event listener options
 * @returns A binding configuration
 */
function listener<T = any>(
  expression: Expression<T>,
  options?: AddEventListenerOptions
): Binding<T>;

Usage Examples:

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

const template = html<MyComponent>`
  <!-- Simple property binding -->
  <div class="status">${x => x.status}</div>
  
  <!-- Conditional content binding -->
  <div class="message">${x => x.showMessage ? x.message : ''}</div>
  
  <!-- Complex expression binding -->
  <span class="count">${x => `Items: ${x.items?.length ?? 0}`}</span>
  
  <!-- Attribute binding -->
  <input type="text" 
    value="${x => x.currentValue}"
    ?disabled="${x => x.isReadonly}"
    class="${x => x.isValid ? 'valid' : 'invalid'}"
  >
  
  <!-- Event listener binding -->
  <button @click="${x => x.handleClick}"
          @mouseenter="${listener((x, e) => x.handleHover(e), { passive: true })}"
  >
    ${x => x.buttonText}
  </button>
  
  <!-- Custom event binding with options -->
  <custom-element @custom-event="${listener(
    (x, e) => x.handleCustomEvent(e.detail), 
    { once: true }
  )}">
  </custom-element>
`;

@customElement({
  name: "my-component",
  template
})
export class MyComponent extends FASTElement {
  @attr status: string = "ready";
  @attr message: string = "";
  @attr showMessage: boolean = false;
  @attr isReadonly: boolean = false;
  @attr isValid: boolean = true;
  @attr buttonText: string = "Click me";
  @attr currentValue: string = "";
  
  items: string[] = [];
  
  handleClick() {
    console.log("Button clicked");
    this.showMessage = !this.showMessage;
  }
  
  handleHover(event: MouseEvent) {
    console.log("Button hovered", event);
  }
  
  handleCustomEvent(detail: any) {
    console.log("Custom event received", detail);
  }
}

// Advanced one-way binding with security policy
const secureBinding = oneWay(
  x => x.userContent,
  myDOMPolicy, // Security policy for sanitization
  false // Not volatile
);

// Volatile binding that always re-evaluates
const volatileBinding = oneWay(
  x => Math.random(), // Always different
  undefined,
  true // Volatile - always re-evaluate
);

Two-Way Binding

Creates bidirectional bindings that synchronize data between source and view, automatically detecting changes in both directions.

/**
 * Creates a two-way data binding
 * @param expression - The binding expression to synchronize
 * @param optionsOrChangeEvent - The binding options or change event name
 * @param policy - The security policy to associate with the binding
 * @param isBindingVolatile - Indicates whether the binding is volatile
 * @returns A two-way binding configuration
 */
function twoWay<T = any>(
  expression: Expression<T>,
  optionsOrChangeEvent?: TwoWayBindingOptions | string,
  policy?: DOMPolicy,
  isBindingVolatile?: boolean
): Binding<T>;

/**
 * Two-way binding configuration options
 */
interface TwoWayBindingOptions {
  /** The event name to listen for changes from the view */
  changeEvent?: string;
  
  /** Function to transform values coming from the view */
  fromView?: (value: any) => any;
}

/**
 * Settings for configuring two-way binding behavior
 */
interface TwoWaySettings {
  /**
   * Determines which event to listen to for detecting view changes
   * @param bindingSource - The directive to determine the change event for
   * @param target - The target element to determine the change event for
   */
  determineChangeEvent(bindingSource: BindingDirective, target: HTMLElement): string;
}

/**
 * Utilities for configuring two-way binding system
 */
const TwoWaySettings: {
  /**
   * Configures global two-way binding behavior
   * @param settings - The settings to use for the two-way binding system
   */
  configure(settings: TwoWaySettings): void;
};

Usage Examples:

import { FASTElement, customElement, html, attr, observable } from "@microsoft/fast-element";
import { twoWay } from "@microsoft/fast-element/binding/two-way.js";

const template = html<FormComponent>`
  <!-- Basic two-way binding -->
  <input type="text" :value="${x => x.name}">
  
  <!-- Two-way binding with custom change event -->
  <input type="text" 
    :value="${twoWay(x => x.email, 'input')}"
    placeholder="Email">
  
  <!-- Two-way binding with value transformation -->
  <input type="number" 
    :value="${twoWay(
      x => x.age, 
      { 
        changeEvent: 'input',
        fromView: (value) => parseInt(value, 10) || 0
      }
    )}">
  
  <!-- Two-way binding with checkbox -->
  <input type="checkbox" 
    :checked="${twoWay(x => x.isSubscribed, 'change')}">
  
  <!-- Two-way binding with select -->
  <select :value="${twoWay(x => x.category)}">
    <option value="web">Web Development</option>
    <option value="mobile">Mobile Development</option>
    <option value="data">Data Science</option>
  </select>
  
  <!-- Two-way binding with custom element -->
  <custom-slider 
    :value="${twoWay(x => x.volume, 'value-changed')}"
    min="0" 
    max="100">
  </custom-slider>
  
  <!-- Display bound values -->
  <div class="preview">
    <p>Name: ${x => x.name}</p>
    <p>Email: ${x => x.email}</p>
    <p>Age: ${x => x.age}</p>
    <p>Subscribed: ${x => x.isSubscribed ? 'Yes' : 'No'}</p>
    <p>Category: ${x => x.category}</p>
    <p>Volume: ${x => x.volume}%</p>
  </div>
`;

@customElement({
  name: "form-component",
  template
})
export class FormComponent extends FASTElement {
  @observable name: string = "";
  @observable email: string = "";
  @observable age: number = 0;
  @observable isSubscribed: boolean = false;
  @observable category: string = "web";
  @observable volume: number = 50;
}

// Configure custom two-way binding behavior
TwoWaySettings.configure({
  determineChangeEvent(bindingSource, target) {
    // Custom logic for determining change events
    if (target.tagName === 'INPUT') {
      const type = target.getAttribute('type');
      switch (type) {
        case 'text':
        case 'email':
        case 'password':
          return 'input'; // Real-time updates
        case 'checkbox':
        case 'radio':
          return 'change';
        default:
          return 'input';
      }
    }
    if (target.tagName === 'SELECT') {
      return 'change';
    }
    if (target.tagName === 'TEXTAREA') {
      return 'input';
    }
    
    // Default for custom elements
    return 'change';
  }
});

// Custom two-way binding with complex transformation
const currencyBinding = twoWay(
  x => x.price,
  {
    changeEvent: 'input',
    fromView: (value: string) => {
      // Transform currency input to number
      const cleaned = value.replace(/[^0-9.]/g, '');
      const parsed = parseFloat(cleaned);
      return isNaN(parsed) ? 0 : parsed;
    }
  }
);

One-Time Binding

Creates bindings that evaluate once during initial render and do not update automatically, useful for static content and performance optimization.

/**
 * Creates a one-time binding that evaluates once
 * @param expression - The binding expression to evaluate
 * @param policy - The security policy to associate with the binding
 * @returns A one-time binding configuration
 */
function oneTime<T = any>(
  expression: Expression<T>,
  policy?: DOMPolicy
): Binding<T>;

Usage Examples:

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

const template = html<StaticComponent>`
  <!-- One-time binding for static content -->
  <div class="header">${oneTime(x => x.appName)}</div>
  <div class="version">Version: ${oneTime(x => x.version)}</div>
  
  <!-- One-time binding for configuration -->
  <div class="config" data-theme="${oneTime(x => x.theme)}">
    Content that uses theme but doesn't need updates
  </div>
  
  <!-- Mixed bindings: static and dynamic -->
  <div class="status-panel">
    <h3>${oneTime(x => x.panelTitle)}</h3>
    <span class="current-status">${x => x.currentStatus}</span>
    <div class="build-info">
      Built on: ${oneTime(x => x.buildDate)}
    </div>
  </div>
  
  <!-- Performance optimization for expensive calculations -->
  <div class="expensive-calc">
    ${oneTime(x => x.performExpensiveCalculation())}
  </div>
`;

@customElement({
  name: "static-component",
  template
})
export class StaticComponent extends FASTElement {
  @attr appName: string = "My Application";
  @attr version: string = "1.0.0";
  @attr theme: string = "light";
  @attr panelTitle: string = "System Status";
  @attr currentStatus: string = "Running";
  @attr buildDate: string = new Date().toISOString();
  
  // Expensive calculation that only needs to run once
  performExpensiveCalculation(): string {
    console.log("Performing expensive calculation...");
    // Simulate expensive operation
    return "Calculated result: " + Math.random().toString(36);
  }
}

// One-time binding with security policy
const secureOneTime = oneTime(
  x => x.trustedContent,
  mySecurityPolicy
);

Signal Binding

Creates signal-based bindings that update when specific signals are sent, providing a publish-subscribe pattern for reactive updates.

/**
 * Creates a signal binding configuration
 * @param expression - The binding to refresh when signaled
 * @param options - The signal name or expression to retrieve the signal name
 * @param policy - The security policy to associate with the binding
 * @returns A signal binding configuration
 */
function signal<T = any>(
  expression: Expression<T>,
  options: string | Expression<T>,
  policy?: DOMPolicy
): Binding<T>;

/**
 * Gateway to signal APIs for publish-subscribe communication
 */
const Signal: {
  /**
   * Subscribes to a signal
   * @param signal - The signal name to subscribe to
   * @param subscriber - The subscriber to receive notifications
   */
  subscribe(signal: string, subscriber: Subscriber): void;
  
  /**
   * Unsubscribes from a signal
   * @param signal - The signal name to unsubscribe from
   * @param subscriber - The subscriber to remove
   */
  unsubscribe(signal: string, subscriber: Subscriber): void;
  
  /**
   * Sends the specified signal to all subscribers
   * @param signal - The signal name to send
   */
  send(signal: string): void;
};

Usage Examples:

import { FASTElement, customElement, html, attr } from "@microsoft/fast-element";
import { signal, Signal } from "@microsoft/fast-element/binding/signal.js";

const template = html<SignalComponent>`
  <!-- Signal binding with static signal name -->
  <div class="theme-indicator">
    Current theme: ${signal(x => x.getCurrentTheme(), "theme-changed")}
  </div>
  
  <!-- Signal binding with dynamic signal name -->
  <div class="user-status">
    Status: ${signal(
      x => x.getUserStatus(x.userId),
      x => `user-${x.userId}-changed`
    )}
  </div>
  
  <!-- Multiple signal bindings -->
  <div class="notifications">
    Messages: ${signal(x => x.getMessageCount(), "messages-updated")}
    Alerts: ${signal(x => x.getAlertCount(), "alerts-updated")}
  </div>
  
  <!-- Signal binding for real-time data -->
  <div class="live-data">
    <span>Price: $${signal(x => x.getCurrentPrice(), "price-update")}</span>
    <span>Change: ${signal(x => x.getPriceChange(), "price-update")}%</span>
  </div>
  
  <button @click="${x => x.refreshData}">Refresh All</button>
`;

@customElement({
  name: "signal-component",
  template
})
export class SignalComponent extends FASTElement {
  @attr userId: string = "user123";
  
  private currentTheme: string = "light";
  private messageCount: number = 0;
  private alertCount: number = 0;
  private currentPrice: number = 100.0;
  private priceChange: number = 0.0;
  
  connectedCallback() {
    super.connectedCallback();
    
    // Setup signal listeners for external updates
    this.setupSignalListeners();
  }
  
  getCurrentTheme(): string {
    return this.currentTheme;
  }
  
  getUserStatus(userId: string): string {
    // Simulate getting user status
    return "online";
  }
  
  getMessageCount(): number {
    return this.messageCount;
  }
  
  getAlertCount(): number {
    return this.alertCount;
  }
  
  getCurrentPrice(): number {
    return this.currentPrice;
  }
  
  getPriceChange(): number {
    return this.priceChange;
  }
  
  refreshData() {
    // Manually trigger updates
    Signal.send("theme-changed");
    Signal.send(`user-${this.userId}-changed`);
    Signal.send("messages-updated");
    Signal.send("alerts-updated");
    Signal.send("price-update");
  }
  
  private setupSignalListeners() {
    // Listen for external theme changes
    window.addEventListener('theme-changed', () => {
      this.currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
      Signal.send("theme-changed");
    });
    
    // Listen for WebSocket updates
    this.setupWebSocketListeners();
    
    // Listen for user events
    document.addEventListener('user-event', (e: CustomEvent) => {
      Signal.send(`user-${e.detail.userId}-changed`);
    });
  }
  
  private setupWebSocketListeners() {
    // Simulate WebSocket connection
    setInterval(() => {
      // Simulate price updates
      this.currentPrice += (Math.random() - 0.5) * 2;
      this.priceChange = (Math.random() - 0.5) * 10;
      Signal.send("price-update");
    }, 1000);
    
    setInterval(() => {
      // Simulate message updates
      this.messageCount = Math.floor(Math.random() * 10);
      Signal.send("messages-updated");
    }, 5000);
  }
}

// External signal senders
class DataService {
  static updateTheme(newTheme: string) {
    // Update theme and signal change
    document.documentElement.setAttribute('data-theme', newTheme);
    Signal.send("theme-changed");
  }
  
  static updateMessages(count: number) {
    // Signal message count change
    Signal.send("messages-updated");
  }
  
  static updateAlerts(count: number) {
    // Signal alert count change
    Signal.send("alerts-updated");
  }
}

Binding Base Class

Abstract base class for all binding types, providing common functionality for expression evaluation and observer creation.

/**
 * Captures a binding expression along with related information and capabilities
 */
abstract class Binding<TSource = any, TReturn = any, TParent = any> {
  /** Options associated with the binding */
  options?: any;
  
  /**
   * Creates a binding
   * @param evaluate - Function that evaluates the binding
   * @param policy - The security policy to associate with this binding
   * @param isVolatile - Indicates whether the binding is volatile
   */
  constructor(
    public evaluate: Expression<TSource, TReturn, TParent>,
    public policy?: DOMPolicy,
    public isVolatile: boolean = false
  );
  
  /**
   * Creates an observer capable of notifying a subscriber when the binding output changes
   * @param subscriber - The subscriber to changes in the binding
   * @param directive - The binding directive to create the observer for
   */
  abstract createObserver(
    subscriber: Subscriber,
    directive: BindingDirective
  ): ExpressionObserver<TSource, TReturn, TParent>;
}

/**
 * The directive from which a binding originates
 */
interface BindingDirective {
  /** The binding */
  readonly dataBinding: Binding;
  
  /** The evaluated target aspect */
  readonly targetAspect?: string;
  
  /** The type of aspect to target */
  readonly aspectType?: DOMAspect;
}

Usage Examples:

import { Binding, BindingDirective, Subscriber, ExpressionObserver } from "@microsoft/fast-element";

// Custom binding implementation
class CustomBinding<TSource = any, TReturn = any, TParent = any> 
  extends Binding<TSource, TReturn, TParent> {
  
  createObserver(
    subscriber: Subscriber,
    directive: BindingDirective
  ): ExpressionObserver<TSource, TReturn, TParent> {
    return new CustomObserver(this, subscriber, directive);
  }
}

// Custom observer implementation
class CustomObserver<TSource = any, TReturn = any, TParent = any>
  implements ExpressionObserver<TSource, TReturn, TParent> {
  
  constructor(
    private binding: CustomBinding<TSource, TReturn, TParent>,
    private subscriber: Subscriber,
    private directive: BindingDirective
  ) {}
  
  bind(controller: ExpressionController<TSource, TParent>): TReturn {
    // Custom binding logic
    const result = this.binding.evaluate(controller.source, controller.context);
    
    // Set up custom change detection
    this.setupChangeDetection(controller);
    
    return result;
  }
  
  unbind(controller: ExpressionController<TSource, TParent>): void {
    // Custom cleanup logic
    this.cleanupChangeDetection();
  }
  
  private setupChangeDetection(controller: ExpressionController<TSource, TParent>): void {
    // Custom change detection implementation
  }
  
  private cleanupChangeDetection(): void {
    // Custom cleanup implementation
  }
}

// Factory function for custom binding
function customBinding<T = any>(
  expression: Expression<T>,
  options?: any
): Binding<T> {
  const binding = new CustomBinding(expression);
  binding.options = options;
  return binding;
}

Expression Normalization

Utilities for normalizing and processing binding expressions to ensure consistent behavior across different binding types.

/**
 * Normalizes binding expressions for consistent processing
 * @param expression - The expression to normalize
 * @returns The normalized binding expression
 */
function normalizeBinding<T = any>(expression: Expression<T>): Binding<T>;

Types

/**
 * A function or string that represents a binding expression
 */
type Expression<TReturn = any, TSource = any, TParent = any> = 
  | ((source: TSource, context: ExecutionContext<TParent>) => TReturn)
  | string;

/**
 * Execution context for binding evaluation
 */
interface ExecutionContext<TParent = any> {
  /** Current index in a repeat context */
  index: number;
  
  /** Length of the collection in a repeat context */
  length: number;
  
  /** Parent data source */
  parent: TParent;
  
  /** Parent execution context */
  parentContext: ExecutionContext<TParent>;
}

/**
 * Observer interface for expression changes
 */
interface ExpressionObserver<TSource = any, TReturn = any, TParent = any> {
  /**
   * Binds the observer to a controller
   * @param controller - The expression controller
   */
  bind(controller: ExpressionController<TSource, TParent>): TReturn;
  
  /**
   * Unbinds the observer from a controller
   * @param controller - The expression controller
   */
  unbind?(controller: ExpressionController<TSource, TParent>): void;
}

/**
 * Controller for managing expression lifecycle
 */
interface ExpressionController<TSource = any, TParent = any> {
  /** The data source */
  source: TSource;
  
  /** The execution context */
  context: ExecutionContext<TParent>;
  
  /**
   * Registers a callback for when the controller unbinds
   * @param callback - The callback to register
   */
  onUnbind(callback: any): void;
}

/**
 * Subscriber interface for change notifications
 */
interface Subscriber {
  /**
   * Handles changes in observed values
   * @param source - The source of the change
   * @param args - Arguments related to the change
   */
  handleChange(source: any, args: any): void;
}

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