npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@xcons/widget

v3.0.8

Published

XCon Studio widget utilities with advanced template rendering, reactive binding system and registry pattern support

Downloads

161

Readme

@xcons/widget

npm version TypeScript License: MIT

XCon Studio widget tools offering advanced template rendering engine, reactive binding system, lifecycle interfaces and registry pattern support for modern web applications. Designed with the "Write once, run everywhere" philosophy.

Features

  • 🎯 Decorator-Based Widget System - Simple @Widget decorator for component registration
  • 🔄 Advanced Reactive System - @xproperty and @xcomputed decorators with automatic dependency tracking
  • 🌐 Template Rendering Engine - Dynamic template processing with reactive data binding
  • 📋 Registry Pattern - Centralized widget management and automatic DOM scanning
  • 🔧 Lifecycle Hooks - Flexible lifecycle interfaces (OnWidgetInit, OnWidgetDestroy, etc.)
  • 🎨 Style Encapsulation - Multiple encapsulation modes (none, emulated, shadow, component)
  • 💉 Dependency Injection - Service injection with @xinject decorator
  • 🔌 Two-Way Data Binding - Model binding with x:model directive
  • 🔍 SEO Friendly - Static HTML rendering support and search engine friendly structure
  • 📱 TypeScript First - Full TypeScript support with comprehensive type definitions
  • 🚀 Zero Dependencies - Lightweight and performant

Installation

npm install @xcons/widget

Quick Start

1. Creating a Basic Widget with Reactive Properties

import { Widget, xproperty, xcomputed, OnWidgetInit } from '@xcons/widget';

@Widget({
    selector: '.hello-widget',
    widgetName: 'HelloWidget',
    template: `
    <div>
      <h3>Hello <span x:text="name"></span>!</h3>
      <p>Full Name: <span x:text="fullName"></span></p>
      <p>Counter: <span x:text="count"></span></p>
      <button x:on:click="incrementCount">Increment</button>
    </div>
  `,
    styles: [`
    .hello-widget {
      padding: 20px;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
  `]
})
export class HelloWidget implements OnWidgetInit {
    @xproperty() name: string = 'World';
    @xproperty() firstName: string = 'John';
    @xproperty() lastName: string = 'Doe';
    @xproperty() count: number = 0;

    @xcomputed({ dependencies: ['firstName', 'lastName'] })
    get fullName(): string {
        return `${this.firstName} ${this.lastName}`;
    }

    onWidgetInit(): void {
        console.log('HelloWidget initialized!');
    }

    incrementCount(): void {
        this.count++;
    }
}

2. Bootstrap Your Application

import { XConBootstrap } from '@xcons/widget';
import { HelloWidget } from './hello-widget';

// Bootstrap the application with widgets
XConBootstrap.run({
    widgets: [
        { widget: HelloWidget, initMode: 'auto' }
    ]
});

3. Using Widget in HTML

<div class="hello-widget"></div>

Advanced Usage

Services with Dependency Injection

import { xinjectable, xsingleton, xinject } from '@xcons/widget';

// Service definition
@xsingleton()
export class UserService {
    @xproperty() users: User[] = [];

    async loadUsers(): Promise<void> {
        // Load users from API
        this.users = await fetch('/api/users').then(r => r.json());
    }

    getUsers(): User[] {
        return this.users;
    }
}

// Using service in widget
@Widget({
    selector: '.user-list',
    template: `
    <div>
      <h3>User List (<span x:text="userCount"></span>)</h3>
      <ul>
        <template x:for="user in filteredUsers">
          <li x:on:click="selectUser(user.id)">
            <span x:text="user.name"></span>
          </li>
        </template>
      </ul>
      <input x:model="searchQuery" placeholder="Search..." />
    </div>
  `
})
export class UserListWidget implements OnWidgetInit, OnWidgetReady {
    @xproperty() searchQuery: string = '';
    @xproperty() selectedUserId: number | null = null;

    @xinject(UserService)
    private userService: UserService;

    @xcomputed({ dependencies: ['userService.users'] })
    get userCount(): number {
        return this.userService.getUsers().length;
    }

    @xcomputed({ dependencies: ['userService.users', 'searchQuery'] })
    get filteredUsers(): User[] {
        if (!this.searchQuery) {
            return this.userService.getUsers();
        }

        const query = this.searchQuery.toLowerCase();
        return this.userService.getUsers().filter(user =>
            user.name.toLowerCase().includes(query)
        );
    }

    onWidgetInit(): void {
        console.log('UserListWidget initialized');
    }

    async onWidgetReady(templateReady: boolean): Promise<void> {
        if (templateReady) {
            await this.userService.loadUsers();
        }
    }

    selectUser(userId: number): void {
        this.selectedUserId = userId;
    }
}

// Start the application
XConBootstrap.run({
    widgets: [
        { widget: UserListWidget, initMode: 'auto' }
    ],
    services: [
        { service: UserService }
    ]
});

SEO and Search Engine Compatibility

XCon Widget allows you to create SEO-friendly web applications with its search engine optimized structure:

@Widget({
    selector: '.product-widget',
    template: `
    <article itemscope itemtype="https://schema.org/Product">
      <h1 itemprop="name" x:text="product.name"></h1>
      <img itemprop="image" x:attr:src="product.imageUrl" x:attr:alt="product.name" />
      <div itemprop="description" x:text="product.description"></div>
      <div itemprop="offers" itemscope itemtype="https://schema.org/Offer">
        <span itemprop="price" x:text="product.price"></span>
        <meta itemprop="priceCurrency" content="TRY" />
      </div>
    </article>
  `
})
export class ProductWidget implements OnWidgetInit {
    @xproperty() product: Product = {
        name: 'Product Name',
        description: 'Product description',
        imageUrl: '/images/product.jpg',
        price: '199.99'
    };

    onWidgetInit(): void {
        // Load initial data for server-side rendering
        this.loadProductData();
    }

    async loadProductData(): Promise<void> {
        // Render critical content first for SEO
        const productId = this.getProductIdFromUrl();
        this.product = await this.fetchProduct(productId);
    }
}

SEO Advantages:

  • ✅ Static HTML content support - Search engines can read content directly
  • ✅ Semantic HTML structure - Use of meaningful HTML elements
  • ✅ Structured Data support - Rich snippets with Schema.org markups
  • ✅ Progressive Enhancement - Basic content can be displayed without JavaScript
  • ✅ Fast initial render - Widgets bind on top of existing HTML

Template Syntax

Text Binding (x:text)

<span x:text="message"></span>
<div x:text="username"></div>
<p x:text="computedValue"></p>

HTML Binding (x:html)

⚠️ Security Warning: Be careful when using x:html, it can lead to XSS (Cross-Site Scripting) vulnerabilities with untrusted content. Only use with trusted or sanitized HTML content.

<div x:html="trustedHtmlContent"></div>

Example Usage:

@Widget({
  selector: '.content-widget',
  template: `
    <div class="content">
      <div x:html="articleContent"></div>
    </div>
  `
})
export class ContentWidget {
  @xproperty() articleContent: string = '<h2>Title</h2><p>Content text</p>';
  
  // Example of creating safe HTML content
  setSafeContent(userInput: string): void {
    // Sanitize content first
    this.articleContent = this.sanitizeHtml(userInput);
  }
  
  private sanitizeHtml(html: string): string {
    // Use HTML sanitization library (e.g., DOMPurify)
    // return DOMPurify.sanitize(html);
    return html;
  }
}

Directives

Conditional Rendering (x:if)

<div x:if="isVisible">This content is visible</div>
<div x:if="count > 0">Counter is greater than zero</div>

List Rendering (x:for)

<template x:for="item in items">
  <li x:text="item.name"></li>
</template>

<!-- With index -->
<template x:for="item, index in items">
  <div>
    <span x:text="index"></span>: <span x:text="item.name"></span>
  </div>
</template>

Switch Cases (x:switch)

<div x:switch="status">
  <div x:switch:case="loading">Loading...</div>
  <div x:switch:case="success">Successful!</div>
  <div x:switch:case="error">An error occurred</div>
  <div x:switch:default>Unknown state</div>
</div>

Event Binding (x:on:*)

<button x:on:click="handleClick">Click</button>
<button x:on:click="handleClickWithArgs('hello', 123)">With Arguments</button>
<input x:on:input="handleInput" />
<form x:on:submit="handleSubmit">...</form>

Attribute Binding (x:attr:*)

<input x:attr:disabled="isDisabled" x:attr:placeholder="placeholderText" />
<img x:attr:src="imageUrl" x:attr:alt="imageAlt" />
<a x:attr:href="linkUrl" x:attr:target="linkTarget"></a>

Class Binding (x:class:*)

<div x:class:active="isActive" x:class:highlighted="isHighlighted">Content</div>
<button x:class:disabled="!isEnabled" x:class:primary="isPrimary">Button</button>

Two-Way Binding (x:model)

<input x:model="username" />
<textarea x:model="description"></textarea>
<select x:model="selectedOption">
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
</select>

<!-- With configuration -->
<input x:model="username" x:model:config='{"debounce": 500, "trim": true}' />

Isolation (x:isolate)

Ensures child widgets maintain their own independent binding contexts:

<div class="parent-widget">
  <p x:text="parentProperty">Parent content</p>
  
  <!-- This area and its child elements are isolated from parent binding -->
  <div x:isolate class="child-widget">
    <p x:text="childProperty">Child content</p>
  </div>
</div>

API Reference

Decorators

@Widget(config)

Widget class decorator:

@Widget({
  selector: string,              // CSS selector (required)
  widgetName?: string,           // Widget name
  template?: string,             // Inline HTML template
  templateUrl?: string,          // External HTML file path
  styles?: string[],             // Inline CSS styles
  styleUrls?: string[],          // External CSS file paths
  encapsulation?: EncapsulationMode, // Style encapsulation mode
  initMode?: InitializationMode, // Initialization mode
  logger?: IWidgetLoggerConfig   // Logger configuration
})

@xproperty(config?)

Reactive property decorator:

@xproperty({
  autoUpdate?: boolean,  // Auto update (default: true)
  deepWatch?: boolean    // Deep watching (default: false)
})

@xcomputed(config?)

Computed property decorator:

@xcomputed({
  dependencies?: string[],       // Explicit dependencies
  autoDetectDependencies?: boolean  // Auto dependency detection (default: true)
})

@xinject(token)

Service injection decorator:

@xinject(ServiceClass)           // Inject by class
@xinject('ServiceToken')         // Inject by token

@xinjectable(config?)

Service registration decorator:

@xinjectable({
  scope?: 'singleton' | 'transient',  // Service scope (default: 'singleton')
  providedIn?: 'root'                 // Auto registration in root
})

Lifecycle Interfaces

interface OnWidgetInit {
  onWidgetInit(): void;
}

interface OnWidgetReady {
  onWidgetReady(templateReady: boolean): void;
}

interface OnWidgetDestroy {
  onWidgetDestroy(): void;
}

interface OnWidgetRendered {
  onWidgetRendered(container: HTMLElement): void;
}

interface OnWidgetDataUpdated {
  onWidgetDataUpdated(): void;
}

interface OnWidgetResize {
  onWidgetResize(): void;
}

interface OnWidgetEditModeChanged {
  onWidgetEditModeChanged(): void;
}

interface OnWidgetMobileModeChanged {
  onWidgetMobileModeChanged(): void;
}

interface OnWidgetPropertyChanged {
  onWidgetPropertyChanged(propertyKey?: string, oldValue?: any, newValue?: any): void;
}

interface OnWidgetInitializationError {
  onWidgetInitializationError(error: any): void;
}

Bootstrap API

// Bootstrap configuration
interface BootstrapConfig {
    rootWidget?: any;              // Single root widget
    widgets?: BootstrapWidget[];   // Widget list
    services?: BootstrapService[]; // Services to register
    providers?: BootstrapProvider[]; // Custom providers
}

// Start the application
XConBootstrap.run(config: BootstrapConfig): Promise<void>

// Rescan DOM for new widgets
XConBootstrap.rescanDOM(): void

// Setup auto-scan for dynamic content
XConBootstrap.setupAutoScan(): void

// Get widget instance from DOM element
XConBootstrap.getWidgetInstance(element: HTMLElement): any

// Destroy widget on element
XConBootstrap.destroyWidget(element: HTMLElement): boolean

Global XConWidgets API

The framework provides a global XConWidgets object for widget management:

// Registry access
XConWidgets.registry          // Widget registry instance
XConWidgets.domManager        // DOM management instance

// Widget registration
XConWidgets.registerWidget(selector, widgetClass, config, initMode)

// Widget activation
XConWidgets.activateWidget(widgetClass, context)

// Registry queries
XConWidgets.isRegistered(selector)             // Check if registered
XConWidgets.getWidgets()                       // Get all selectors
XConWidgets.getRegisteredWidgets()             // Get all with details
XConWidgets.getWidgetBySelector(selector)      // Get widget by selector

// DOM management
XConWidgets.scanDOM()                          // Scan DOM for widgets
XConWidgets.rescanDOM()                        // Rescan DOM
XConWidgets.setupAutoScan()                    // Setup auto-scan

Browser Support

  • Chrome 60+
  • Firefox 60+
  • Safari 12+
  • Edge 79+

Dependencies

Peer Dependencies

  • @xcons/core ^2.0.1
  • @xcons/common ^2.0.16
  • typescript >=4.5.0

Zero Runtime Dependencies

The package has no runtime dependencies to be lightweight and performant.

📄 License

MIT License - see LICENSE file for details.

XCon Studio Team | 🌐 Documentation


Made with ❤️ by XCon Studio Team