Spec Registry
Help your agents use open-source better. Learn more.
Find usage specs for your project’s dependencies
- Author
- tessl
- Last updated
- Spec files
npm-svelte
Describes: npm/svelte
- Description
- Revolutionary JavaScript framework and compiler that builds web applications without runtime overhead by compiling components at build time.
- Author
- tessl
- Last updated
actions.md docs/
1# Actions23Element lifecycle and behavior enhancement system for reusable DOM interactions and element augmentation.45## Capabilities67### Action Functions89Create reusable functions that enhance DOM elements with custom behavior, event handling, or lifecycle management.1011```javascript { .api }12/**13* Action function that enhances a DOM element with custom behavior14* @param node - DOM element to enhance15* @param parameter - Optional parameter for configuring the action16* @returns Optional action return object with update and destroy methods17*/18interface Action<19Element = HTMLElement,20Parameter = undefined,21Attributes extends Record<string, any> = Record<never, any>22> {23(24node: Element,25parameter?: Parameter26): void | ActionReturn<Parameter, Attributes>;27}2829interface ActionReturn<30Parameter = undefined,31Attributes extends Record<string, any> = Record<never, any>32> {33/** Called when the action parameter changes */34update?: (parameter: Parameter) => void;35/** Called when the element is removed from the DOM */36destroy?: () => void;37}38```3940**Usage Examples:**4142```javascript43// Basic action without parameters44function ripple(node) {45function handleClick(event) {46const rect = node.getBoundingClientRect();47const ripple = document.createElement('div');48ripple.className = 'ripple';49ripple.style.left = (event.clientX - rect.left) + 'px';50ripple.style.top = (event.clientY - rect.top) + 'px';51node.appendChild(ripple);5253setTimeout(() => ripple.remove(), 600);54}5556node.addEventListener('click', handleClick);5758return {59destroy() {60node.removeEventListener('click', handleClick);61}62};63}6465// Usage in template66<button use:ripple>Click me</button>6768// Action with parameters69function tooltip(node, text) {70let tooltipElement;7172function showTooltip() {73tooltipElement = document.createElement('div');74tooltipElement.className = 'tooltip';75tooltipElement.textContent = text;76document.body.appendChild(tooltipElement);7778const rect = node.getBoundingClientRect();79tooltipElement.style.left = rect.left + 'px';80tooltipElement.style.top = (rect.top - tooltipElement.offsetHeight - 5) + 'px';81}8283function hideTooltip() {84if (tooltipElement) {85tooltipElement.remove();86tooltipElement = null;87}88}8990node.addEventListener('mouseenter', showTooltip);91node.addEventListener('mouseleave', hideTooltip);9293return {94update(newText) {95text = newText;96if (tooltipElement) {97tooltipElement.textContent = text;98}99},100destroy() {101hideTooltip();102node.removeEventListener('mouseenter', showTooltip);103node.removeEventListener('mouseleave', hideTooltip);104}105};106}107108// Usage with parameters109<div use:tooltip={'Hello World'}>Hover me</div>110<div use:tooltip={dynamicText}>Dynamic tooltip</div>111```112113### TypeScript Actions114115Create type-safe actions with proper parameter and attribute typing.116117**Usage Examples:**118119```typescript120interface TooltipOptions {121text: string;122position?: 'top' | 'bottom' | 'left' | 'right';123delay?: number;124}125126interface TooltipAttributes {127'data-tooltip'?: string;128'aria-describedby'?: string;129}130131const tooltip: Action<HTMLElement, TooltipOptions, TooltipAttributes> = (132node,133{ text, position = 'top', delay = 0 }134) => {135let timeoutId: number;136let tooltipElement: HTMLElement;137138function show() {139timeoutId = setTimeout(() => {140tooltipElement = document.createElement('div');141tooltipElement.className = `tooltip tooltip-${position}`;142tooltipElement.textContent = text;143tooltipElement.id = `tooltip-${Math.random().toString(36).substr(2, 9)}`;144145document.body.appendChild(tooltipElement);146node.setAttribute('aria-describedby', tooltipElement.id);147148positionTooltip(tooltipElement, node, position);149}, delay);150}151152function hide() {153clearTimeout(timeoutId);154if (tooltipElement) {155tooltipElement.remove();156node.removeAttribute('aria-describedby');157}158}159160node.addEventListener('mouseenter', show);161node.addEventListener('mouseleave', hide);162163return {164update({ text: newText, position: newPosition = 'top', delay: newDelay = 0 }) {165text = newText;166position = newPosition;167delay = newDelay;168169if (tooltipElement) {170tooltipElement.textContent = text;171tooltipElement.className = `tooltip tooltip-${position}`;172positionTooltip(tooltipElement, node, position);173}174},175176destroy() {177hide();178node.removeEventListener('mouseenter', show);179node.removeEventListener('mouseleave', hide);180}181};182};183184// Usage in TypeScript component185<button use:tooltip={{ text: 'Save changes', position: 'bottom', delay: 500 }}>186Save187</button>188```189190### Common Action Patterns191192Reusable patterns for building actions that handle common DOM interaction needs.193194**Click Outside:**195196```javascript197function clickOutside(node, callback) {198function handleClick(event) {199if (!node.contains(event.target)) {200callback();201}202}203204document.addEventListener('click', handleClick, true);205206return {207destroy() {208document.removeEventListener('click', handleClick, true);209},210update(newCallback) {211callback = newCallback;212}213};214}215216// Usage217let showModal = true;218219<div class="modal" use:clickOutside={() => showModal = false}>220Modal content221</div>222```223224**Auto-resize Textarea:**225226```javascript227function autoresize(node) {228function resize() {229node.style.height = 'auto';230node.style.height = node.scrollHeight + 'px';231}232233node.addEventListener('input', resize);234resize(); // Initial resize235236return {237destroy() {238node.removeEventListener('input', resize);239}240};241}242243// Usage244<textarea use:autoresize placeholder="Type here..."></textarea>245```246247**Long Press:**248249```javascript250function longpress(node, callback) {251let timeoutId;252const duration = 500; // milliseconds253254function handleMouseDown() {255timeoutId = setTimeout(callback, duration);256}257258function handleMouseUp() {259clearTimeout(timeoutId);260}261262node.addEventListener('mousedown', handleMouseDown);263node.addEventListener('mouseup', handleMouseUp);264node.addEventListener('mouseleave', handleMouseUp);265266return {267destroy() {268clearTimeout(timeoutId);269node.removeEventListener('mousedown', handleMouseDown);270node.removeEventListener('mouseup', handleMouseUp);271node.removeEventListener('mouseleave', handleMouseUp);272}273};274}275276// Usage277<button use:longpress={() => alert('Long pressed!')}>278Hold me279</button>280```281282**Focus Trap:**283284```javascript285function focusTrap(node) {286const focusableElements = node.querySelectorAll(287'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'288);289const firstElement = focusableElements[0];290const lastElement = focusableElements[focusableElements.length - 1];291292function handleKeyDown(event) {293if (event.key === 'Tab') {294if (event.shiftKey) {295if (document.activeElement === firstElement) {296lastElement.focus();297event.preventDefault();298}299} else {300if (document.activeElement === lastElement) {301firstElement.focus();302event.preventDefault();303}304}305}306307if (event.key === 'Escape') {308node.dispatchEvent(new CustomEvent('escape'));309}310}311312// Focus first element when trap is activated313firstElement?.focus();314315node.addEventListener('keydown', handleKeyDown);316317return {318destroy() {319node.removeEventListener('keydown', handleKeyDown);320}321};322}323324// Usage325<div class="modal" use:focusTrap on:escape={() => showModal = false}>326<input type="text" placeholder="First input" />327<input type="text" placeholder="Second input" />328<button>Close</button>329</div>330```331332### Action Libraries333334Actions work well as reusable libraries for common UI patterns:335336```javascript337// ui-actions.js338export function ripple(node, options = {}) {339const { color = 'rgba(255, 255, 255, 0.5)', duration = 600 } = options;340341function createRipple(event) {342const circle = document.createElement('span');343const rect = node.getBoundingClientRect();344const size = Math.max(rect.width, rect.height);345const x = event.clientX - rect.left - size / 2;346const y = event.clientY - rect.top - size / 2;347348circle.style.cssText = `349position: absolute;350width: ${size}px;351height: ${size}px;352left: ${x}px;353top: ${y}px;354background: ${color};355border-radius: 50%;356pointer-events: none;357transform: scale(0);358animation: ripple ${duration}ms ease-out;359`;360361node.appendChild(circle);362setTimeout(() => circle.remove(), duration);363}364365// Ensure node is positioned366if (getComputedStyle(node).position === 'static') {367node.style.position = 'relative';368}369370node.style.overflow = 'hidden';371node.addEventListener('click', createRipple);372373return {374update(newOptions) {375Object.assign(options, newOptions);376},377destroy() {378node.removeEventListener('click', createRipple);379}380};381}382383export function lazyload(node, src) {384if ('IntersectionObserver' in window) {385const observer = new IntersectionObserver(entries => {386if (entries[0].isIntersecting) {387node.src = src;388observer.disconnect();389}390});391392observer.observe(node);393394return {395update(newSrc) {396src = newSrc;397},398destroy() {399observer.disconnect();400}401};402} else {403// Fallback for browsers without IntersectionObserver404node.src = src;405}406}407408// Usage409import { ripple, lazyload } from './ui-actions.js';410411<button use:ripple={{ color: 'rgba(0, 100, 255, 0.3)' }}>412Ripple Button413</button>414415<img use:lazyload={'./large-image.jpg'} alt="Lazy loaded" />416```417418## Action Best Practices4194201. **Clean Up Resources**: Always remove event listeners and clear timers in the destroy method4212. **Handle Parameter Updates**: Implement update method when actions accept parameters4223. **Type Safety**: Use TypeScript interfaces for better development experience4234. **Performance**: Avoid creating actions in render loops or expensive operations4245. **Accessibility**: Consider ARIA attributes and keyboard navigation in your actions4256. **Error Handling**: Handle edge cases and provide fallbacks where appropriate