diff --git a/changelog.md b/changelog.md index dcd9080..6682b64 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-11-16 - 2.3.4 - fix(domtools) +Prevent race conditions during DomTools initialization and improve runOnce error handling + +- Add a static initializationPromise to serialize and await concurrent DomTools.setupDomTools() calls to avoid race conditions when multiple initializations are requested. +- Initialize Keyboard only after the document is ready (check document.readyState and use readystatechange listener) so document.body/head are available before creating the Keyboard instance; resolve domReady and domToolsReady appropriately. +- Support ignoreGlobal path to create isolated DomTools instances while keeping global initialization guarded by initializationPromise. +- Enhance runOnce: store errors from the first execution and re-throw them to all waiting callers; always clear the running flag in a finally block to prevent permanent stuck state. + ## 2025-06-20 - 2.3.3 - fix(package.json) Update dependency versions and add pnpm package manager field diff --git a/readme.md b/readme.md index fd363fd..acb6d3e 100644 --- a/readme.md +++ b/readme.md @@ -1,104 +1,479 @@ # @design.estate/dees-domtools -tools to simplify complex css structures -## Install +> 🎨 A comprehensive TypeScript toolkit for simplifying DOM manipulation, CSS management, and web component development -To install `@design.estate/dees-domtools`, simply use npm: +Modern web development made elegant. `@design.estate/dees-domtools` provides a powerful suite of utilities for managing complex CSS structures, handling browser events, implementing smooth scrolling, and building responsive web applications with ease. + +## Features + +🚀 **Smart DOM Management** - Singleton-based DomTools instance with race-condition-free initialization +📱 **Responsive Breakpoints** - Built-in support for desktop, tablet, phablet, and phone viewports with container queries +🎭 **Theme Management** - Automatic dark/light mode detection and switching with RxJS observables +⌨️ **Keyboard Shortcuts** - Elegant keyboard event handling with combo support +📜 **Smooth Scrolling** - Native and Lenis-powered smooth scrolling with automatic detection +🎯 **State Management** - Integrated state management with smartstate +🧭 **Routing** - Client-side routing with smartrouter +🌐 **WebSetup** - Easy management of website metadata, favicons, and SEO tags +💅 **CSS Utilities** - Grid helpers, breakpoint utilities, and base styles for web components + +## Installation ```bash -npm install --save @design.estate/dees-domtools +npm install @design.estate/dees-domtools ``` -Ensure your project supports TypeScript and ESM syntax for the best development experience. +Or with pnpm: -## Usage +```bash +pnpm add @design.estate/dees-domtools +``` -This documentation aims to give you comprehensive guidance on how to utilize `@design.estate/dees-domtools` in your web projects to simplify complex CSS structures. Whether you're building a sophisticated web application or need a more manageable way to handle your CSS, this toolkit offers a variety of features designed to enhance and streamline your workflow. Throughout this guide, we'll cover the installation process, key features, examples, and best practices when working with `@design.estate/dees-domtools`. - -Before diving into the examples, please ensure that you've completed the installation step as described above. - -### Key Features - -- **CSS Simplification**: Tools and utilities designed to abstract and simplify the handling of complex CSS structures. -- **Responsive Design Helpers**: Tools for managing responsive designs more easily, with utilities for breakpoints, and adaptable grids. -- **Element Utilities**: Simplified interactions with DOM elements, including dynamic style manipulations and more. -- **Theme Management**: Simplify the process of theme toggling (dark mode/light mode) in your applications. -- **Keyboard Event Handling**: Utilities for managing keyboard events, facilitating better interaction handling. -- **External Resources Management**: Easily manage external CSS and JavaScript resources within your project. - -### Getting Started - -To get started with `@design.estate/dees-domtools`, you first need to import the modules you intend to use in your project. Here's a simple example to illustrate how to import and use the package: +## Quick Start ```typescript -import { DomTools, elementBasic, breakpoints, css } from '@design.estate/dees-domtools'; +import { DomTools } from '@design.estate/dees-domtools'; -// Setting up DomTools for your application -const domToolsInstance = await DomTools.setupDomTools(); +// Initialize DomTools (singleton pattern - safe to call multiple times) +const domtools = await DomTools.setupDomTools(); -// Example: Using elementBasic utilities -elementBasic.setup(); +// Wait for DOM to be ready +await domtools.domReady.promise; + +// Now you're ready to rock! 🎸 +console.log('DOM is ready, head and body elements are available'); ``` -### Simplifying CSS with CSS Utilities +## Core API -The package offers a variety of CSS utilities that simplify the handling of complex CSS structures. Here's how you can use them in your project: +### DomTools Instance + +The `DomTools` class is the heart of the library. It provides a singleton instance that manages all the utilities. + +```typescript +import { DomTools } from '@design.estate/dees-domtools'; + +// Setup with options +const domtools = await DomTools.setupDomTools({ + ignoreGlobal: false // Set to true to create isolated instance +}); + +// Access DOM elements (available after domReady) +await domtools.domReady.promise; +const head = domtools.elements.headElement; +const body = domtools.elements.bodyElement; +``` + +**Key Properties:** + +- `domtools.router` - SmartRouter instance for client-side routing +- `domtools.themeManager` - Theme management (dark/light mode) +- `domtools.scroller` - Smooth scrolling utilities +- `domtools.keyboard` - Keyboard event handling +- `domtools.websetup` - Website metadata management +- `domtools.smartstate` - State management +- `domtools.deesComms` - Communication utilities + +**Lifecycle Promises:** + +- `domtools.domToolsReady.promise` - Resolves when DomTools is initialized +- `domtools.domReady.promise` - Resolves when DOM is interactive/complete +- `domtools.globalStylesReady.promise` - Resolves when global styles are set + +### Responsive Breakpoints + +Built-in breakpoint system with both media queries and container queries: + +```typescript +import { breakpoints, css } from '@design.estate/dees-domtools'; +import { css as litCss } from 'lit'; + +// Breakpoint values (in pixels) +breakpoints.desktop // 1600px +breakpoints.notebook // 1240px +breakpoints.tablet // 1024px +breakpoints.phablet // 600px +breakpoints.phone // 400px + +// Use with Lit components +const myStyles = litCss` + .container { + padding: 20px; + } + + ${breakpoints.cssForTablet(litCss` + .container { + padding: 10px; + } + `)} + + ${breakpoints.cssForPhone(litCss` + .container { + padding: 5px; + } + `)} +`; +``` + +Available breakpoint helpers: +- `cssForDesktop(css)` - Styles for 1600px and above +- `cssForNotebook(css)` - Styles for 1240px and below +- `cssForTablet(css)` - Styles for 1024px and below +- `cssForPhablet(css)` - Styles for 600px and below +- `cssForPhone(css)` - Styles for 400px and below + +### Theme Management + +Automatic theme detection with system preference support: + +```typescript +const domtools = await DomTools.setupDomTools(); +const { themeManager } = domtools; + +// Toggle between dark and light +themeManager.toggleDarkBright(); + +// Set specific theme +themeManager.goDark(); +themeManager.goBright(); + +// Enable automatic global background changes +await themeManager.enableAutomaticGlobalThemeChange(); + +// Subscribe to theme changes +themeManager.themeObservable.subscribe((isBright) => { + console.log(`Theme is now: ${isBright ? 'light' : 'dark'}`); +}); + +// Check current theme +if (themeManager.goBrightBoolean) { + console.log('Light mode active'); +} +``` + +### Keyboard Shortcuts + +Handle keyboard events with ease, including complex combinations: + +```typescript +import { Keyboard, Key } from '@design.estate/dees-domtools'; + +const domtools = await DomTools.setupDomTools(); +await domtools.domReady.promise; + +// Access the keyboard instance +const { keyboard } = domtools; + +// Listen for Ctrl+S +keyboard.on([Key.Ctrl, Key.S]).subscribe((event) => { + event.preventDefault(); + console.log('Save triggered!'); +}); + +// Listen for Ctrl+Shift+P +keyboard.on([Key.Ctrl, Key.Shift, Key.P]).subscribe(() => { + console.log('Command palette opened!'); +}); + +// Programmatically trigger key presses +keyboard.triggerKeyPress([Key.Ctrl, Key.S]); + +// Clean up when done +keyboard.stopListening(); +``` + +**Available Keys:** + +All standard keyboard keys are available in the `Key` enum, including: +- Modifiers: `Ctrl`, `Shift`, `Alt` +- Letters: `A` through `Z` +- Numbers: `Zero` through `Nine` +- Function keys: `F1` through `F12` +- Navigation: `Home`, `End`, `PageUp`, `PageDown`, arrows +- And many more... + +### Smooth Scrolling + +Powerful scrolling utilities with Lenis integration: + +```typescript +const domtools = await DomTools.setupDomTools(); +const { scroller } = domtools; + +// Scroll to an element smoothly +const targetElement = document.querySelector('#section-2'); +await scroller.toElement(targetElement, { + duration: 1000, + easing: 'easeInOutQuad' +}); + +// Enable Lenis smooth scrolling +await scroller.enableLenisScroll({ + disableOnNativeSmoothScroll: true // Auto-disable if browser has native smooth scroll +}); + +// Register scroll callbacks +scroller.onScroll(() => { + console.log('Page scrolled!'); +}); + +// Detect if native smooth scrolling is enabled +const hasNativeSmooth = await scroller.detectNativeSmoothScroll(); +``` + +### CSS Utilities + +Helper functions for common CSS patterns: ```typescript import { css } from '@design.estate/dees-domtools'; -// Example: Creating a CSS grid with a specified number of columns and gap size -const gridStyles = css.cssGridColumns(3, 10); // 3 columns with a 10px gap +// Create responsive grid columns +const gridTemplate = css.cssGridColumns(4, 16); +// Returns: calc((100%/4) - (48px/4)) calc((100%/4) - (48px/4)) ... + +// Use in your styles +const styles = ` + .grid { + display: grid; + grid-template-columns: ${gridTemplate}; + gap: 16px; + } +`; ``` -### Managing Responsive Design - -Responsive design is critical for modern web development. `@design.estate/dees-domtools` provides utilities to make it easier to manage responsive designs and breakpoints. +### Global Styles & External Resources ```typescript -import { breakpoints } from '@design.estate/dees-domtools'; +const domtools = await DomTools.setupDomTools(); -// Example: Applying styles for different viewport sizes -const tabletStyles = breakpoints.cssForTablet(myCssStyles); -const phoneStyles = breakpoints.cssForPhone(myCssStyles); +// Add global CSS +await domtools.setGlobalStyles(` + body { + margin: 0; + font-family: 'Inter', sans-serif; + } +`); + +// Load external CSS +await domtools.setExternalCss('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); + +// Load external JavaScript +await domtools.setExternalScript('https://cdn.example.com/analytics.js'); ``` -### Theme Management +### Website Metadata -Managing themes (such as toggling between dark and light mode) is simplified with the built-in theme management tools. +Manage your website's metadata easily: ```typescript -// Toggle between light and dark themes -domToolsInstance.themeManager.toggleDarkBright(); -``` +const domtools = await DomTools.setupDomTools(); -### Handling Keyboard Events - -Keyboard event handling is made easy with the utilities provided by the toolkit. This can be particularly useful for adding keyboard navigation and shortcuts. - -```typescript -import { Keyboard } from '@design.estate/dees-domtools'; - -// Initialize keyboard handling -const keyboard = new Keyboard(document.body); - -// Bind an event to a specific key combination -keyboard.on([Keyboard.Key.Ctrl, Keyboard.Key.S]).subscribe(() => { - console.log('Ctrl+S was pressed'); +await domtools.setWebsiteInfo({ + metaObject: { + title: 'My Awesome App', + description: 'The best app ever created', + keywords: ['awesome', 'app', 'web'], + author: 'Your Name' + }, + faviconUrl: '/favicon.ico', + appleTouchIconUrl: '/apple-touch-icon.png' }); ``` -### Best Practices +### Web Component Base Styles -- **Organize Your CSS**: Use the provided CSS utilities to structure your styles in a way that makes them easy to maintain and scale. -- **Embrace Responsiveness**: Leverage the responsive design helpers to ensure your application looks great on any device. -- **Consistent Theme Handling**: Utilize the theme management tools to provide a seamless experience for your users, allowing them to choose their preferred theme. +Kickstart your Lit elements with pre-configured styles: -By integrating `@design.estate/dees-domtools` into your project, you can leverage a comprehensive suite of utilities designed to simplify complex CSS structures and enhance your web development workflow. +```typescript +import { LitElement } from 'lit'; +import { elementBasic } from '@design.estate/dees-domtools'; + +class MyElement extends LitElement { + static styles = [elementBasic.staticStyles]; + + async connectedCallback() { + super.connectedCallback(); + await elementBasic.setup(this); + } +} +``` + +The `elementBasic.staticStyles` includes: +- Box-sizing reset +- Smooth transitions for background and color +- Custom scrollbar styles +- Default font family (Geist Sans, Inter fallback) + +### State Management + +Integrated state management with smartstate: + +```typescript +const domtools = await DomTools.setupDomTools(); + +// Access the state part +const state = domtools.domToolsStatePart; + +// Get current state +const currentState = state.getState(); +console.log(currentState.virtualViewport); // 'native' +console.log(currentState.jwt); // null + +// Update state +state.setState({ + virtualViewport: 'tablet', + jwt: 'your-token-here' +}); + +// Subscribe to state changes +state.subscribe((newState) => { + console.log('State updated:', newState); +}); +``` + +### Run Once Pattern + +Execute expensive operations only once, even if called multiple times: + +```typescript +const domtools = await DomTools.setupDomTools(); + +// This will only execute once, even if called multiple times +const result = await domtools.runOnce('myExpensiveOperation', async () => { + console.log('Running expensive operation...'); + await someExpensiveAsyncOperation(); + return 'result'; +}); + +// Subsequent calls return the same result without re-executing +const sameResult = await domtools.runOnce('myExpensiveOperation', async () => { + console.log('This will never run!'); + return 'different result'; +}); + +console.log(result === sameResult); // true +``` + +Error handling is built-in - if the function throws, all waiting callers receive the same error. + +## Advanced Usage + +### Combining Features + +Here's a real-world example combining multiple features: + +```typescript +import { DomTools, breakpoints, elementBasic, Key } from '@design.estate/dees-domtools'; +import { LitElement, html, css as litCss } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('my-app') +class MyApp extends LitElement { + static styles = [ + elementBasic.staticStyles, + litCss` + :host { + display: block; + padding: 2rem; + } + + ${breakpoints.cssForTablet(litCss` + :host { + padding: 1rem; + } + `)} + ` + ]; + + private domtools?: DomTools; + + async connectedCallback() { + super.connectedCallback(); + + // Setup DomTools + this.domtools = await elementBasic.setup(this); + await this.domtools.domReady.promise; + + // Setup keyboard shortcuts + this.domtools.keyboard.on([Key.Ctrl, Key.K]).subscribe(() => { + this.openCommandPalette(); + }); + + // Subscribe to theme changes + this.domtools.themeManager.themeObservable.subscribe((isBright) => { + this.requestUpdate(); + }); + + // Enable smooth scrolling + await this.domtools.scroller.enableLenisScroll({ + disableOnNativeSmoothScroll: true + }); + } + + private openCommandPalette() { + console.log('Command palette opened!'); + } + + render() { + const isDark = !this.domtools?.themeManager.goBrightBoolean; + + return html` +