UNPKG

@xcons/widget

Version:

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

539 lines (431 loc) • 15.1 kB
# @xcons/widget [![npm version](https://badge.fury.io/js/%40xcons%2Fwidget.svg)](https://badge.fury.io/js/%40xcons%2Fwidget) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/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 ```bash npm install @xcons/widget ``` ## Quick Start ### 1. Creating a Basic Widget with Reactive Properties ```typescript 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 ```typescript 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 ```html <div class="hello-widget"></div> ``` ## Advanced Usage ### Services with Dependency Injection ```typescript 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: ```typescript @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) ```html <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. ```html <div x:html="trustedHtmlContent"></div> ``` **Example Usage:** ```typescript @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) ```html <div x:if="isVisible">This content is visible</div> <div x:if="count > 0">Counter is greater than zero</div> ``` #### List Rendering (x:for) ```html <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) ```html <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:*) ```html <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:*) ```html <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:*) ```html <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) ```html <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: ```html <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: ```typescript @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: ```typescript @xproperty({ autoUpdate?: boolean, // Auto update (default: true) deepWatch?: boolean // Deep watching (default: false) }) ``` #### @xcomputed(config?) Computed property decorator: ```typescript @xcomputed({ dependencies?: string[], // Explicit dependencies autoDetectDependencies?: boolean // Auto dependency detection (default: true) }) ``` #### @xinject(token) Service injection decorator: ```typescript @xinject(ServiceClass) // Inject by class @xinject('ServiceToken') // Inject by token ``` #### @xinjectable(config?) Service registration decorator: ```typescript @xinjectable({ scope?: 'singleton' | 'transient', // Service scope (default: 'singleton') providedIn?: 'root' // Auto registration in root }) ``` ### Lifecycle Interfaces ```typescript 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 ```typescript // 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: ```typescript // 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](LICENSE) file for details. **XCon Studio Team** | [🌐 Documentation](https://xcon.studio/) --- **Made with ā¤ļø by XCon Studio Team**