Compare commits

...

30 Commits

Author SHA1 Message Date
918769e416 v2.3.4 2025-11-16 15:29:23 +00:00
039ee19ffd fix(domtools): Prevent race conditions during DomTools initialization and improve runOnce error handling 2025-11-16 15:29:23 +00:00
d16bc1652d 2.3.3 2025-06-20 00:05:02 +00:00
6fa99f8ed8 fix(package.json): Update dependency versions and add pnpm package manager field 2025-06-20 00:05:02 +00:00
5ce92130b4 2.3.2 2025-02-01 00:32:57 +01:00
b0774b0cba fix(scroller): Rename method from scrollToElement to toElement for consistency 2025-02-01 00:32:56 +01:00
c44e19f3e0 2.3.1 2025-01-31 23:57:07 +01:00
07f09b4457 fix(scroller): Removed passive option from scroll event listener 2025-01-31 23:57:07 +01:00
10ea4ca265 2.3.0 2025-01-31 23:10:33 +01:00
dfd61ce744 feat(scroller): Enhance Scroller class with callback execution and adaptive scroll listener 2025-01-31 23:10:33 +01:00
3093ccd4f6 2.2.0 2025-01-31 23:03:10 +01:00
eb1ac75e49 feat(core): Enhance scrolling capabilities by integrating Scroller class and adding lenis support 2025-01-31 23:03:10 +01:00
0683378e39 2.1.1 2025-01-09 00:33:46 +01:00
25a813d35f fix(themamanager): Fixed automatic global theme change subscription for background updates. 2025-01-09 00:33:46 +01:00
916fd48858 2.1.0 2025-01-09 00:24:17 +01:00
90bb1eb432 feat(themeManager): Exposed method to enable automatic global theme change. 2025-01-09 00:24:17 +01:00
126e0fc900 2.0.65 2024-10-21 17:25:08 +02:00
a20b321bb0 fix(ThemeManager): Refactor ThemeManager class to separate global style setting logic 2024-10-21 17:25:08 +02:00
33721f86ab 2.0.64 2024-10-06 23:37:21 +02:00
987c821eed fix(pluginexports): Add missing import for smartrouter in pluginexports. 2024-10-06 23:37:21 +02:00
b0bed44810 2.0.63 2024-10-06 23:31:45 +02:00
0a7da5132d fix(dependencies): Update @push.rocks/smartrouter to version ^1.3.2 for better compatibility 2024-10-06 23:31:44 +02:00
b0bb8e9e2b 2.0.62 2024-10-06 21:42:11 +02:00
1f346e24db fix(dependencies): Update dependencies to resolve potential issues and improve stability 2024-10-06 21:42:10 +02:00
c34cca7eb6 2.0.61 2024-10-04 15:28:01 +02:00
15e58fbf5d fix(core): Correct import statement for SweetScroll. 2024-10-04 15:28:01 +02:00
2383e6ec21 2.0.60 2024-10-02 17:49:28 +02:00
5820bf81f6 fix(dependencies): Update dependencies to latest versions 2024-10-02 17:49:27 +02:00
9c6cfa3603 2.0.59 2024-10-02 15:45:46 +02:00
cc934a9c0e fix(core): Refactor plugin exports to improve modularity 2024-10-02 15:45:45 +02:00
12 changed files with 5822 additions and 2001 deletions

View File

@@ -1,128 +0,0 @@
# gitzone ci_default
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: '$CI_BUILD_STAGE'
stages:
- security
- test
- release
- metadata
before_script:
- pnpm install -g pnpm
- pnpm install -g @shipzone/npmci
- npmci npm prepare
# ====================
# security stage
# ====================
# ====================
# security stage
# ====================
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --prod
tags:
- lossless
- docker
allow_failure: true
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --dev
tags:
- lossless
- docker
allow_failure: true
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
testBuild:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci npm build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
only:
- tags
tags:
- lossless
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
allow_failure: true
only:
- tags
script:
- npmci command npm install -g typescript
- npmci npm prepare
- npmci npm install
tags:
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker
- notpriv
pages:
stage: metadata
script:
- npmci node install stable
- npmci npm install
- npmci command npm run buildDocs
tags:
- lossless
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true

View File

@@ -1,5 +1,104 @@
# 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
- Update @git.zone/tsbuild from ^2.2.1 to ^2.6.4
- Update @git.zone/tsbundle from ^2.2.5 to ^2.4.0
- Update @git.zone/tstest from ^1.0.96 to ^2.3.1
- Update @push.rocks/tapbundle from ^5.5.6 to ^6.0.3
- Update @push.rocks/lik from ^6.1.0 to ^6.2.2
- Update @push.rocks/smartpromise from ^4.2.2 to ^4.2.3
- Update @push.rocks/smartrx from ^3.0.7 to ^3.0.10
- Update @push.rocks/smartstate from ^2.0.19 to ^2.0.20
- Update lenis from ^1.1.20 to ^1.3.4
- Update lit from ^3.2.1 to ^3.3.0
- Add packageManager field for pnpm
## 2025-02-01 - 2.3.2 - fix(scroller)
Rename method from scrollToElement to toElement for consistency
- Updated method name in Scroller class for better coherence with existing naming conventions.
## 2025-01-31 - 2.3.1 - fix(scroller)
Removed passive option from scroll event listener
- The 'passive: true' option was removed from the native scroll event listener attachment.
## 2025-01-31 - 2.3.0 - feat(scroller)
Enhance Scroller class with callback execution and adaptive scroll listener
- Added support for executing scroll callbacks in the Scroller class.
- Integrated adaptive scrolling mechanism using Lenis, with native smooth scrolling detection.
- Ensured seamless switching between native scroll listener and Lenis scroll listener.
## 2025-01-31 - 2.2.0 - feat(core)
Enhance scrolling capabilities by integrating Scroller class and adding lenis support
- Replaced SweetScroll setup in domtools.classes.domtools.ts with Scroller class.
- Moved SweetScroll initialization and methods to new Scroller class.
- Added lenis support in the Scroller class for enhanced scrolling capabilities.
- Updated dev and dependencies versions in package.json.
## 2025-01-09 - 2.1.1 - fix(themamanager)
Fixed automatic global theme change subscription for background updates.
- Corrected the logic for updating the document's background color upon theme changes using themeObservable subscription.
## 2025-01-09 - 2.1.0 - feat(themeManager)
Exposed method to enable automatic global theme change.
- Enable easier application of dark and bright themes by exposing a method.
- Updated devDependencies and dependencies in package.json to latest versions.
## 2024-10-21 - 2.0.65 - fix(ThemeManager)
Refactor ThemeManager class to separate global style setting logic
- Moved logic to set global styles into a dedicated function setGlobalStylesOnPurpose in ThemeManager.
## 2024-10-06 - 2.0.64 - fix(pluginexports)
Add missing import for smartrouter in pluginexports.
- Fixed a missing import for smartrouter in ts/domtools.pluginexports.ts.
## 2024-10-06 - 2.0.63 - fix(dependencies)
Update @push.rocks/smartrouter to version ^1.3.2 for better compatibility
- Updated @push.rocks/smartrouter from version ^1.2.1 to ^1.3.2 in package.json.
## 2024-10-06 - 2.0.62 - fix(dependencies)
Update dependencies to resolve potential issues and improve stability
## 2024-10-04 - 2.0.61 - fix(core)
Correct import statement for SweetScroll.
- Fix import syntax for SweetScroll in domtools.pluginexports.ts
## 2024-10-02 - 2.0.60 - fix(dependencies)
Update dependencies to latest versions
- Bump @git.zone/tsbuild version from 2.1.82 to 2.1.84
- Bump @push.rocks/tapbundle version from 5.0.23 to 5.3.0
- Bump @types/node version from 20.14.9 to 22.7.4
- Bump @api.global/typedrequest version from 3.0.30 to 3.0.32
- Bump @push.rocks/smartstate version from 2.0.17 to 2.0.18
- Bump lit version from 3.1.4 to 3.2.0
## 2024-10-02 - 2.0.59 - fix(core)
Refactor plugin exports to improve modularity
- Refactored the plugin exports to be more modular and organized.
- Replaced redundant plugin object definition in ts/index.ts with a simpler import and export pattern.
## 2024-07-01 - 2.0.58 - fix(dependencies)
Update dependencies and correct font family in styles

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-domtools",
"version": "2.0.58",
"version": "2.3.4",
"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,29 +15,30 @@
"buildDocs": "tsdoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.82",
"@git.zone/tsbundle": "^2.0.15",
"@git.zone/tstest": "^1.0.90",
"@push.rocks/tapbundle": "^5.0.23",
"@types/node": "^20.14.9"
"@git.zone/tsbuild": "^2.6.4",
"@git.zone/tsbundle": "^2.4.0",
"@git.zone/tstest": "^2.3.1",
"@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^22.12.0"
},
"dependencies": {
"@api.global/typedrequest": "^3.0.30",
"@api.global/typedrequest": "^3.1.10",
"@design.estate/dees-comms": "^1.0.27",
"@push.rocks/lik": "^6.0.15",
"@push.rocks/lik": "^6.2.2",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartjson": "^5.0.20",
"@push.rocks/smartmarkdown": "^3.0.3",
"@push.rocks/smartpromise": "^4.0.4",
"@push.rocks/smartrouter": "^1.0.16",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartstate": "^2.0.17",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrouter": "^1.3.2",
"@push.rocks/smartrx": "^3.0.10",
"@push.rocks/smartstate": "^2.0.20",
"@push.rocks/smartstring": "^4.0.15",
"@push.rocks/smarturl": "^3.0.6",
"@push.rocks/smarturl": "^3.1.0",
"@push.rocks/webrequest": "^3.0.37",
"@push.rocks/websetup": "^3.0.19",
"@push.rocks/webstore": "^2.0.20",
"lit": "^3.1.4",
"lenis": "^1.3.4",
"lit": "^3.3.0",
"sweet-scroll": "^4.0.0"
},
"files": [
@@ -70,5 +71,6 @@
"state management",
"routing",
"performance optimization"
]
],
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
}

6744
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

497
readme.md
View File

@@ -1,100 +1,475 @@
# @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.0.58',
version: '2.3.4',
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;
}
}
/**
@@ -93,11 +115,9 @@ export class DomTools {
};
public deesComms = new plugins.deesComms.DeesComms();
public scroller = new plugins.SweetScroll({
/* some options */
}); // TODO: switch to scroller class
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();
@@ -107,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
@@ -118,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);
}
);

View File

@@ -1,5 +1,175 @@
import type { DomTools } from './domtools.classes.domtools.js';
import * as plugins from './domtools.plugins.js';
export class Scroller {
// TODO: move sweet scroll over to here;
public domtoolsInstance: DomTools;
// Array to store scroll callback functions.
private scrollCallbacks: Array<() => void> = [];
// Lenis instance (if activated) or null.
private lenisInstance: plugins.Lenis | null = null;
// Bound handlers to allow removal from event listeners.
private handleNativeScroll = (event: Event): void => {
this.executeScrollCallbacks();
};
private handleLenisScroll = (info: any): void => {
this.executeScrollCallbacks();
};
constructor(domtoolsInstanceArg: DomTools) {
this.domtoolsInstance = domtoolsInstanceArg;
// Attach the native scroll listener by default.
this.attachNativeScrollListener();
}
private sweetScroller = new plugins.SweetScroll({});
/**
* Scrolls to a given element with options.
*/
public async toElement(
elementArg: HTMLElement,
optionsArg: Parameters<typeof this.sweetScroller.toElement>[1]
) {
this.sweetScroller.toElement(elementArg, optionsArg);
await plugins.smartdelay.delayFor(optionsArg.duration);
}
/**
* Detects whether native smooth scrolling is enabled.
*/
public async detectNativeSmoothScroll() {
const done = plugins.smartpromise.defer<boolean>();
const sampleSize = 100;
const acceptableDeltaDifference = 3;
const minimumSmoothRatio = 0.75;
const eventDeltas: number[] = [];
function onWheel(event: WheelEvent) {
eventDeltas.push(event.deltaY);
if (eventDeltas.length >= sampleSize) {
window.removeEventListener('wheel', onWheel);
analyzeEvents();
}
}
function analyzeEvents() {
const totalDiffs = eventDeltas.length - 1;
let smallDiffCount = 0;
for (let i = 0; i < totalDiffs; i++) {
const diff = Math.abs(eventDeltas[i + 1] - eventDeltas[i]);
if (diff <= acceptableDeltaDifference) {
smallDiffCount++;
}
}
const smoothRatio = smallDiffCount / totalDiffs;
if (smoothRatio >= minimumSmoothRatio) {
console.log('Smooth scrolling detected.');
done.resolve(true);
} else {
console.log('Smooth scrolling NOT detected.');
done.resolve(false);
}
}
window.addEventListener('wheel', onWheel);
return done.promise;
}
/**
* Enables Lenis scrolling.
* If optionsArg.disableOnNativeSmoothScroll is true and native smooth scrolling is detected,
* Lenis will be destroyed immediately.
*/
public async enableLenisScroll(optionsArg?: { disableOnNativeSmoothScroll?: boolean }) {
const lenis = new plugins.Lenis({
autoRaf: true,
});
if (optionsArg?.disableOnNativeSmoothScroll) {
if (await this.detectNativeSmoothScroll()) {
lenis.destroy();
return;
}
}
// Activate Lenis scrolling.
this.lenisInstance = lenis;
// Switch from native scroll listener to Lenis scroll listener.
this.detachNativeScrollListener();
this.attachLenisScrollListener();
// Monkey-patch the destroy method so that when Lenis is destroyed,
// the native scroll listener is reattached.
const originalDestroy = lenis.destroy.bind(lenis);
lenis.destroy = () => {
originalDestroy();
this.detachLenisScrollListener();
this.attachNativeScrollListener();
this.lenisInstance = null;
};
}
/**
* Registers a callback to be executed on scroll.
* @param callback A function to execute on each scroll event.
*/
public onScroll(callback: () => void): void {
this.scrollCallbacks.push(callback);
}
/**
* Executes all registered scroll callbacks concurrently.
*/
private executeScrollCallbacks(): void {
// Execute all callbacks in parallel.
this.scrollCallbacks.forEach((callback) => {
try {
callback();
} catch (error) {
console.error('Error in scroll callback:', error);
}
});
}
/**
* Attaches the native scroll event listener.
*/
private attachNativeScrollListener(): void {
window.addEventListener('scroll', this.handleNativeScroll);
}
/**
* Detaches the native scroll event listener.
*/
private detachNativeScrollListener(): void {
window.removeEventListener('scroll', this.handleNativeScroll);
}
/**
* Attaches the Lenis scroll event listener.
*/
private attachLenisScrollListener(): void {
if (this.lenisInstance) {
// Assuming that Lenis exposes an `on` method to listen to scroll events.
this.lenisInstance.on('scroll', this.handleLenisScroll);
}
}
/**
* Detaches the Lenis scroll event listener.
*/
private detachLenisScrollListener(): void {
if (this.lenisInstance) {
// Assuming that Lenis exposes an `off` method to remove scroll event listeners.
this.lenisInstance.off('scroll', this.handleLenisScroll);
}
}
}

View File

@@ -21,10 +21,17 @@ export class ThemeManager {
this.updateAllConnectedElements();
}
private async updateAllConnectedElements() {
public async enableAutomaticGlobalThemeChange() {
if (document.body && document.body.style) {
document.body.style.background = this.goBrightBoolean ? '#fff' : '#000';
this.themeObservable.subscribe({
next: (goBright) => {
document.body.style.background = goBright ? '#fff' : '#000';
}
});
}
}
private async updateAllConnectedElements() {
this.themeObservable.next(this.goBrightBoolean);
}

View File

@@ -0,0 +1,23 @@
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartmarkdown from '@push.rocks/smartmarkdown';
import * as smartpromise from '@push.rocks/smartpromise';
import SweetScroll from 'sweet-scroll';
import * as smartstate from '@push.rocks/smartstate';
import * as smartrouter from '@push.rocks/smartrouter';
import * as smartrx from '@push.rocks/smartrx';
import * as smartstring from '@push.rocks/smartstring';
import * as smarturl from '@push.rocks/smarturl';
import * as typedrequest from '@api.global/typedrequest';
export {
smartdelay,
smartmarkdown,
smartpromise,
SweetScroll,
smartstate,
smartrouter,
smartrx,
smartstring,
smarturl,
typedrequest
};

View File

@@ -49,6 +49,7 @@ export {
};
// third party scope
import Lenis from 'lenis'
import SweetScroll from 'sweet-scroll';
export { SweetScroll };
export { Lenis, SweetScroll };

View File

@@ -9,19 +9,8 @@ export { DomTools, type IDomToolsContructorOptions } from './domtools.classes.do
export { TypedRequest } from '@api.global/typedrequest';
export { type IWebSetupConstructorOptions } from '@push.rocks/websetup';
import * as allPlugins from './domtools.plugins.js';
export const plugins = {
smartdelay: allPlugins.smartdelay,
smartmarkdown: allPlugins.smartmarkdown,
smartpromise: allPlugins.smartpromise,
SweetScroll: allPlugins.SweetScroll,
smartstate: allPlugins.smartstate,
smartrx: allPlugins.smartrx,
smartstring: allPlugins.smartstring,
smarturl: allPlugins.smarturl,
typedrequest: allPlugins.typedrequest,
};
import * as plugins from './domtools.pluginexports.js';
export { plugins };
// type exports
import type { rxjs } from '@push.rocks/smartrx';