Compare commits

...

6 Commits

6 changed files with 3247 additions and 1341 deletions

View File

@@ -1,5 +1,33 @@
# Changelog
## 2025-11-16 - 2.3.6 - fix(readme)
Normalize README formatting and improve breakpoint helpers section
- Fixed inconsistent Markdown list formatting in the Features section (added space after list dash for emoji bullets)
- Bolded the "Available breakpoint helpers" heading and added spacing for improved readability
- Clarified breakpoint helper listing to ensure cssForPhone is documented
## 2025-11-16 - 2.3.5 - fix(dependencies)
bump dependency versions in package.json
- Updated devDependencies:
- - @git.zone/tsbuild: ^2.6.4 -> ^2.7.1
- - @git.zone/tsbundle: ^2.4.0 -> ^2.5.1
- - @git.zone/tstest: ^2.3.1 -> ^2.7.0
- Updated dependencies:
- - @push.rocks/smartjson: ^5.0.20 -> ^5.2.0
- - @push.rocks/smartrouter: ^1.3.2 -> ^1.3.3
- - @push.rocks/smartstate: ^2.0.20 -> ^2.0.27
- - @push.rocks/smartstring: ^4.0.15 -> ^4.1.0
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-domtools",
"version": "2.3.3",
"version": "2.3.6",
"private": false,
"description": "A package providing tools to simplify complex CSS structures and web development tasks, featuring TypeScript support and integration with various web technologies.",
"main": "dist_ts/index.js",
@@ -15,9 +15,9 @@
"buildDocs": "tsdoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.6.4",
"@git.zone/tsbundle": "^2.4.0",
"@git.zone/tstest": "^2.3.1",
"@git.zone/tsbuild": "^2.7.1",
"@git.zone/tsbundle": "^2.5.1",
"@git.zone/tstest": "^2.7.0",
"@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^22.12.0"
},
@@ -26,13 +26,13 @@
"@design.estate/dees-comms": "^1.0.27",
"@push.rocks/lik": "^6.2.2",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartjson": "^5.0.20",
"@push.rocks/smartjson": "^5.2.0",
"@push.rocks/smartmarkdown": "^3.0.3",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrouter": "^1.3.2",
"@push.rocks/smartrouter": "^1.3.3",
"@push.rocks/smartrx": "^3.0.10",
"@push.rocks/smartstate": "^2.0.20",
"@push.rocks/smartstring": "^4.0.15",
"@push.rocks/smartstate": "^2.0.27",
"@push.rocks/smartstring": "^4.1.0",
"@push.rocks/smarturl": "^3.1.0",
"@push.rocks/webrequest": "^3.0.37",
"@push.rocks/websetup": "^3.0.19",

3951
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

500
readme.md
View File

@@ -1,100 +1,478 @@
# @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`
<div class="app" style="background: ${isDark ? '#1a1a1a' : '#ffffff'}">
<h1>My Awesome App</h1>
<button @click=${() => this.domtools?.themeManager.toggleDarkBright()}>
Toggle Theme
</button>
</div>
`;
}
}
```
## TypeScript Support
This package is written in TypeScript and provides full type definitions:
```typescript
import type {
IDomToolsState,
IDomToolsContructorOptions,
TViewport
} from '@design.estate/dees-domtools';
// Custom state interface
interface MyState extends IDomToolsState {
customProperty: string;
}
// Type-safe viewport handling
const viewport: TViewport = 'tablet';
```
## Browser Support
Targets the latest version of Chrome. For other browsers, you may need to include polyfills.
## Why @design.estate/dees-domtools?
-**Race-condition free** - Carefully designed initialization prevents common timing issues
-**TypeScript first** - Full type safety and IntelliSense support
-**Modern APIs** - Built on Lit, RxJS, and other modern web standards
-**Batteries included** - Everything you need for sophisticated web apps
-**Production ready** - Used in real-world applications at design.estate
-**Well maintained** - Active development and support
## Related Packages
This library integrates with the design.estate ecosystem:
- `@design.estate/dees-comms` - Communication utilities
- `@push.rocks/websetup` - Website setup and meta management
- `@push.rocks/smartrouter` - Client-side routing
- `@push.rocks/smartstate` - State management
## License and Legal Information

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@design.estate/dees-domtools',
version: '2.3.3',
version: '2.3.6',
description: 'A package providing tools to simplify complex CSS structures and web development tasks, featuring TypeScript support and integration with various web technologies.'
}

View File

@@ -18,12 +18,22 @@ export class DomTools {
// ======
// STATIC
// ======
private static initializationPromise: Promise<DomTools> | null = null;
/**
* setups domtools
*/
public static async setupDomTools(optionsArg: IDomToolsContructorOptions = {}) {
public static async setupDomTools(optionsArg: IDomToolsContructorOptions = {}): Promise<DomTools> {
// If initialization is already in progress and we're not ignoring global, wait for it
if (!optionsArg.ignoreGlobal && DomTools.initializationPromise) {
return await DomTools.initializationPromise;
}
// Create initialization promise to prevent race conditions
if (!optionsArg.ignoreGlobal) {
DomTools.initializationPromise = (async () => {
let domToolsInstance: DomTools;
if (!globalThis.deesDomTools && !optionsArg.ignoreGlobal) {
if (!globalThis.deesDomTools) {
globalThis.deesDomTools = new DomTools(optionsArg);
domToolsInstance = globalThis.deesDomTools;
@@ -32,18 +42,30 @@ export class DomTools {
if (document.readyState === 'interactive' || document.readyState === 'complete') {
domToolsInstance.elements.headElement = document.querySelector('head');
domToolsInstance.elements.bodyElement = document.querySelector('body');
// Initialize keyboard now that document.body exists
domToolsInstance.keyboard = new Keyboard(document.body);
domToolsInstance.domReady.resolve();
}
};
// Check current state immediately to avoid race condition
if (document.readyState === 'interactive' || document.readyState === 'complete') {
readyStateChangedFunc();
} else {
document.addEventListener('readystatechange', readyStateChangedFunc);
}
domToolsInstance.domToolsReady.resolve();
} else if (optionsArg.ignoreGlobal) {
domToolsInstance = new DomTools(optionsArg);
} else {
domToolsInstance = globalThis.deesDomTools;
}
await domToolsInstance.domToolsReady.promise;
return domToolsInstance;
})();
return await DomTools.initializationPromise;
} else {
// ignoreGlobal case - create isolated instance
const domToolsInstance = new DomTools(optionsArg);
return domToolsInstance;
}
}
/**
@@ -95,7 +117,7 @@ export class DomTools {
public deesComms = new plugins.deesComms.DeesComms();
public scroller = new Scroller(this);
public themeManager = new ThemeManager(this);
public keyboard = new Keyboard(document.body);
public keyboard: Keyboard = null; // Initialized after DOM ready to avoid accessing document.body before it exists
public domToolsReady = plugins.smartpromise.defer();
public domReady = plugins.smartpromise.defer();
@@ -105,6 +127,7 @@ export class DomTools {
private runOnceTrackerStringMap = new plugins.lik.Stringmap();
private runOnceResultMap = new plugins.lik.FastMap();
private runOnceErrorMap = new plugins.lik.FastMap();
/**
* run a function once and always get the Promise of the first execution
@@ -116,15 +139,27 @@ export class DomTools {
if (!this.runOnceTrackerStringMap.checkString(identifierArg)) {
this.runOnceTrackerStringMap.addString(identifierArg);
this.runOnceTrackerStringMap.addString(runningId);
try {
const result = await funcArg();
this.runOnceResultMap.addToMap(identifierArg, result);
} catch (error) {
// Store error so waiting callers can receive it
this.runOnceErrorMap.addToMap(identifierArg, error);
} finally {
// Always remove running flag to prevent permanent stuck state
this.runOnceTrackerStringMap.removeString(runningId);
}
}
return await this.runOnceTrackerStringMap.registerUntilTrue(
(stringMap) => {
return !stringMap.includes(runningId);
},
() => {
// Check if there was an error and re-throw it
const error = this.runOnceErrorMap.getByKey(identifierArg);
if (error) {
throw error;
}
return this.runOnceResultMap.getByKey(identifierArg);
}
);