minimact-bundle
Declarative DOM Selector Primitives
Behavioral anchors without wrappers. Apply attributes, classes, and styles to arbitrary DOM elements declaratively.
Overview
minimact-bundle provides declarative DOM selector primitives that let you control arbitrary DOM elements without wrapper divs or manual DOM manipulation. It's pure declarative control - state-driven attribute application using JSX primitives.
Revolutionary Concept
Traditional: Wrapper divs or imperative DOM manipulation
// ❌ Wrapper hell
<div className="fade-in">
<h1>Title</h1>
<p>Description</p>
</div>
// ❌ Manual DOM manipulation
useEffect(() => {
document.querySelectorAll('.hero h1').forEach(el => {
el.classList.add('fade-in');
});
}, []);Bundle: Declarative, no wrappers needed
// ✅ Clean, declarative
registerBundle("hero", ".hero h1, .hero p");
useBundle("hero", { class: "fade-in" });
<section className="hero">
<h1>Title</h1>
<p>Description</p>
</section>Installation
npm install minimact-bundleQuick Start
import { registerBundle, useBundle } from 'minimact-bundle';
function MyComponent() {
const [visible, setVisible] = useState(false);
// Register bundle (what it targets)
useEffect(() => {
registerBundle("hero-animation", ".hero h1, .hero p, button");
}, []);
// Use bundle (what to apply)
useBundle("hero-animation", {
class: visible ? "fade-in visible" : "fade-in"
});
return (
<section className="hero">
<h1>Welcome</h1>
<p>This is awesome</p>
<button onClick={() => setVisible(true)}>Show</button>
</section>
);
}Result: All matching elements (h1, p, button) get the fade-in class, and when visible is true, they also get the visible class. No wrapper divs needed!
Core Concepts
1. Bundle Registration
Define what the bundle targets:
// CSS selector
registerBundle("hero", ".hero h1, .hero p");
// Function (dynamic selection)
registerBundle("visible", () => {
return Array.from(document.querySelectorAll('.item'))
.filter(el => isInViewport(el));
});
// Direct elements
registerBundle("specific", [element1, element2]);2. Bundle Application
Define how to modify the targets:
useBundle("hero", {
class: "fade-in visible",
style: { opacity: 1, transform: 'translateY(0)' },
'data-animated': 'true'
});API Reference
Registration
registerBundle(id, selector)
Register a bundle with a selector.
Parameters:
id: string- Unique bundle identifierselector: string | (() => Element[]) | Element[]- Target selector
Selector types:
string- CSS selector() => Element[]- Function returning elementsElement[]- Direct element array
Example:
registerBundle(
"hero-animation",
".hero h1, .hero p, button"
);unregisterBundle(id)
Remove a bundle registration.
Parameters:
id: string- Bundle identifier to remove
Example:
unregisterBundle("hero-animation");Hook (Minimact Integration)
useBundle(id, attributes)
Apply attributes to bundled elements (Minimact hook).
Parameters:
id: string- Bundle identifierattributes: BundleAttributes- Attributes to apply
Attributes:
classorclassName- CSS classes to addstyle- Inline styles (object or string)data-*- Data attributes- Any other valid HTML attributes
Example:
const bundle = useBundle("hero-animation", {
class: "fade-in",
style: { opacity: 1 },
'data-animated': 'true'
});Standalone API
Bundle Class
For use without Minimact/hooks.
import { Bundle, registerBundle } from 'minimact-bundle';
registerBundle("hero", ".hero h1, .hero p");
const bundle = new Bundle({
id: "hero",
attributes: {
class: "fade-in"
}
});
bundle.apply(); // Apply attributes
bundle.update({ class: "fade-out" }); // Update
bundle.cleanup(); // Remove attributesReal-World Examples
Example 1: Scroll Reveal Animation
function HomePage() {
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
registerBundle("scroll-reveal", ".hero h1, .hero p, .feature-card");
const handleScroll = () => setScrollY(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useBundle("scroll-reveal", {
class: scrollY > 100 ? "fade-in visible" : "fade-in",
style: {
opacity: scrollY > 100 ? 1 : 0,
transform: scrollY > 100 ? 'translateY(0)' : 'translateY(20px)'
}
});
return (
<div>
<section className="hero">
<h1>Welcome to Our Site</h1>
<p>Scroll down to reveal content</p>
</section>
<div className="feature-card">Feature 1</div>
<div className="feature-card">Feature 2</div>
<div className="feature-card">Feature 3</div>
</div>
);
}Example 2: Theme Switching
function App() {
const [theme, setTheme] = useState('light');
useEffect(() => {
registerBundle("themed-elements", () => {
return [
...Array.from(document.querySelectorAll('.card')),
...Array.from(document.querySelectorAll('.panel')),
document.querySelector('body')
].filter(Boolean) as Element[];
});
}, []);
useBundle("themed-elements", {
class: `theme-${theme}`,
'data-theme': theme
});
return (
<div>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle Theme ({theme})
</button>
<div className="card">Card 1</div>
<div className="card">Card 2</div>
<div className="panel">Panel Content</div>
</div>
);
}Example 3: Loading States
function Dashboard() {
const [loading, setLoading] = useState(false);
useEffect(() => {
registerBundle("interactive", "button, input, select, a[href]");
}, []);
useBundle("interactive", {
class: loading ? "disabled loading" : "",
'data-loading': loading,
style: {
pointerEvents: loading ? 'none' : 'auto',
opacity: loading ? 0.5 : 1
}
});
const handleRefresh = async () => {
setLoading(true);
await fetchData();
setLoading(false);
};
return (
<div>
<button onClick={handleRefresh}>Refresh Data</button>
<div className="content">
<button>Action 1</button>
<button>Action 2</button>
<input placeholder="Search..." />
<select>
<option>Option 1</option>
<option>Option 2</option>
</select>
</div>
</div>
);
}Example 4: Print Styling
function Invoice() {
const [printing, setPrinting] = useState(false);
useEffect(() => {
registerBundle("no-print", ".sidebar, .header, .footer, button");
}, []);
useBundle("no-print", {
style: { display: printing ? 'none' : undefined },
'data-print-hidden': printing
});
const handlePrint = () => {
setPrinting(true);
window.print();
setTimeout(() => setPrinting(false), 100);
};
return (
<div>
<div className="header">
<h1>Company Logo</h1>
<button onClick={handlePrint}>Print Invoice</button>
</div>
<div className="invoice-content">
<h2>Invoice #12345</h2>
<p>Amount: $1,234.56</p>
</div>
<div className="sidebar">Navigation</div>
<div className="footer">Footer Content</div>
</div>
);
}Example 5: Responsive Layout
function Gallery() {
const { width } = useWindowSize();
const layout = width < 768 ? 'mobile' : width < 1024 ? 'tablet' : 'desktop';
useEffect(() => {
registerBundle("gallery-items", ".gallery-item");
}, []);
useBundle("gallery-items", {
class: `layout-${layout}`,
'data-layout': layout,
style: {
gridColumn: layout === 'mobile' ? 'span 1' :
layout === 'tablet' ? 'span 2' : 'span 3'
}
});
return (
<div className="gallery">
<div className="gallery-item">Item 1</div>
<div className="gallery-item">Item 2</div>
<div className="gallery-item">Item 3</div>
<div className="gallery-item">Item 4</div>
</div>
);
}Example 6: Dynamic Grid System
function Dashboard() {
const [columns, setColumns] = useState(3);
useEffect(() => {
registerBundle("grid-items", ".dashboard-widget");
}, []);
useBundle("grid-items", {
style: {
gridColumn: `span ${Math.floor(12 / columns)}`,
transition: 'all 0.3s ease'
},
'data-columns': columns
});
return (
<div>
<div className="controls">
<button onClick={() => setColumns(2)}>2 Columns</button>
<button onClick={() => setColumns(3)}>3 Columns</button>
<button onClick={() => setColumns(4)}>4 Columns</button>
</div>
<div className="dashboard-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(12, 1fr)' }}>
<div className="dashboard-widget">Sales Chart</div>
<div className="dashboard-widget">User Stats</div>
<div className="dashboard-widget">Revenue</div>
<div className="dashboard-widget">Conversions</div>
</div>
</div>
);
}Example 7: Focus Management
function Modal({ isOpen }) {
useEffect(() => {
registerBundle("focusable", "a, button, input, select, textarea, [tabindex]");
}, []);
useBundle("focusable", {
'tabindex': isOpen ? undefined : '-1',
style: {
pointerEvents: isOpen ? 'auto' : 'none'
}
});
return isOpen ? (
<div className="modal">
<h2>Modal Title</h2>
<input placeholder="Name" />
<button>Submit</button>
<button>Cancel</button>
</div>
) : null;
}Advanced Patterns
Multiple Bundles
Apply different attributes to different element groups:
function AnimatedPage() {
const [trigger, setTrigger] = useState(false);
useEffect(() => {
registerBundle("fade-in", ".card, .panel");
registerBundle("slide-in", ".sidebar, .header");
registerBundle("bounce-in", "button.primary");
}, []);
useBundle("fade-in", {
class: trigger ? "animate-fade" : ""
});
useBundle("slide-in", {
class: trigger ? "animate-slide" : ""
});
useBundle("bounce-in", {
class: trigger ? "animate-bounce" : ""
});
return (
<div>
<button onClick={() => setTrigger(true)}>Animate All</button>
{/* Cards will fade, sidebar will slide, primary buttons will bounce */}
</div>
);
}Conditional Registration
Register/unregister bundles dynamically:
function FeatureToggle() {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
if (enabled) {
registerBundle("highlight", ".important");
} else {
unregisterBundle("highlight");
}
}, [enabled]);
if (enabled) {
useBundle("highlight", {
class: "highlighted",
style: { background: 'yellow' }
});
}
return (
<div>
<button onClick={() => setEnabled(!enabled)}>
Toggle Highlights
</button>
<p className="important">Important text 1</p>
<p className="important">Important text 2</p>
</div>
);
}Dynamic Selectors
Change selector based on state:
function FilteredView() {
const [filter, setFilter] = useState('all');
useEffect(() => {
const selector = filter === 'all'
? '.item'
: `.item[data-category="${filter}"]`;
registerBundle("filtered-items", selector);
}, [filter]);
useBundle("filtered-items", {
class: "visible",
style: { display: 'block' }
});
return (
<div>
<select onChange={e => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="tech">Tech</option>
<option value="design">Design</option>
</select>
<div className="item" data-category="tech">Tech Item</div>
<div className="item" data-category="design">Design Item</div>
<div className="item" data-category="tech">Another Tech</div>
</div>
);
}Function-Based Selection
Select elements programmatically:
function LazyLoadImages() {
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
registerBundle("visible-images", () => {
return Array.from(document.querySelectorAll('img.lazy'))
.filter(img => {
const rect = img.getBoundingClientRect();
return rect.top < window.innerHeight + 100; // 100px buffer
});
});
const handleScroll = () => setScrollY(window.scrollY);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useBundle("visible-images", {
class: "loaded",
'data-loaded': 'true'
});
return (
<div>
{[1, 2, 3, 4, 5].map(i => (
<img key={i} className="lazy" data-src={`image${i}.jpg`} />
))}
</div>
);
}Benefits
✅ No Wrapper Pollution - Keep markup clean and semantic ✅ Declarative Control - State-driven attribute application ✅ Reusable Bundles - Define once, use everywhere ✅ Dynamic Targeting - Function-based selectors for runtime logic ✅ Type-Safe - Full TypeScript support ✅ Server-Compatible - Integrates with Minimact's SSR ✅ Zero Output - Renders nothing to the DOM ✅ Pure Behavior - Behavioral anchor without visual presence
Performance
| Operation | Time |
|---|---|
| Bundle registration | < 1ms |
| Attribute application (10 elements) | < 2ms |
| Update attributes | < 1ms |
| Cleanup | < 1ms |
Optimization Tips:
// Cache selectors that don't change
useEffect(() => {
registerBundle("static", ".card");
}, []); // Empty deps - only register once
// Use specific selectors for better performance
registerBundle("items", ".list > .item"); // ✅ Specific
registerBundle("items", ".item"); // ❌ Too broad
// Batch updates
useBundle("multi", {
class: "a b c", // ✅ Single update
style: { x: 1, y: 2, z: 3 } // ✅ Single style update
});Integration with Minimact
minimact-bundle follows the standard Minimact extension pattern:
Client-Side
// Bundle applies attributes declaratively
useBundle("hero", {
class: "fade-in",
style: { opacity: 1 }
});
// Automatically syncs to server
context.signalR.updateBundleState(
componentId,
bundleId,
{ class: "fade-in", style: { opacity: 1 } }
);Server-Side
// Server receives bundle state
protected override VNode Render()
{
var bundleState = State["bundle_hero"];
// Can use bundle state in rendering logic
return new VNode("section",
bundleState.Attributes.ContainsKey("class") &&
bundleState.Attributes["class"].Contains("visible")
? new VNode("p", "Content is visible!")
: new VNode("p", "Content hidden")
);
}MES Certification
minimact-bundle has achieved MES Silver certification:
- ✅ Component context integration
- ✅ Index tracking for multiple instances
- ✅ Cleanup handling
- ✅ Server sync support
- ✅ TypeScript declarations
- ✅ Dual-mode architecture (standalone + integrated)
Philosophy
"The DOM is now your puppet." 🎯
"Control it declaratively. No wrappers needed."
Traditional frameworks force you to wrap elements to control them. minimact-bundle lets you target and control any DOM elements without changing your markup structure.
It's behavioral composition - pure behavior without visual presence. Define what you want to control, then apply attributes declaratively based on state.
Browser Support
- ✅ Modern browsers with
document.querySelectorAll - ✅ IE11+ (with polyfills)
- ✅ Server-side rendering compatible
Next Steps
- minimact-punch (DOM State)
- minimact-query (SQL for DOM)
- minimact-quantum (DOM Entanglement)
- Core Hooks API
Part of the Minimact Quantum Stack 🌵🎯✨
