Spec RegistrySpec Registry

Help your agents use open-source better. Learn more.

Find usage specs for your project’s dependencies

>

npm-svelte

Describes: npmnpm/svelte

Description
Revolutionary JavaScript framework and compiler that builds web applications without runtime overhead by compiling components at build time.
Author
tessl
Last updated

How to use

npx @tessl/cli registry install tessl/npm-svelte@4.2.0

actions.md docs/

1
# Actions
2
3
Element lifecycle and behavior enhancement system for reusable DOM interactions and element augmentation.
4
5
## Capabilities
6
7
### Action Functions
8
9
Create reusable functions that enhance DOM elements with custom behavior, event handling, or lifecycle management.
10
11
```javascript { .api }
12
/**
13
* Action function that enhances a DOM element with custom behavior
14
* @param node - DOM element to enhance
15
* @param parameter - Optional parameter for configuring the action
16
* @returns Optional action return object with update and destroy methods
17
*/
18
interface Action<
19
Element = HTMLElement,
20
Parameter = undefined,
21
Attributes extends Record<string, any> = Record<never, any>
22
> {
23
(
24
node: Element,
25
parameter?: Parameter
26
): void | ActionReturn<Parameter, Attributes>;
27
}
28
29
interface ActionReturn<
30
Parameter = undefined,
31
Attributes extends Record<string, any> = Record<never, any>
32
> {
33
/** Called when the action parameter changes */
34
update?: (parameter: Parameter) => void;
35
/** Called when the element is removed from the DOM */
36
destroy?: () => void;
37
}
38
```
39
40
**Usage Examples:**
41
42
```javascript
43
// Basic action without parameters
44
function ripple(node) {
45
function handleClick(event) {
46
const rect = node.getBoundingClientRect();
47
const ripple = document.createElement('div');
48
ripple.className = 'ripple';
49
ripple.style.left = (event.clientX - rect.left) + 'px';
50
ripple.style.top = (event.clientY - rect.top) + 'px';
51
node.appendChild(ripple);
52
53
setTimeout(() => ripple.remove(), 600);
54
}
55
56
node.addEventListener('click', handleClick);
57
58
return {
59
destroy() {
60
node.removeEventListener('click', handleClick);
61
}
62
};
63
}
64
65
// Usage in template
66
<button use:ripple>Click me</button>
67
68
// Action with parameters
69
function tooltip(node, text) {
70
let tooltipElement;
71
72
function showTooltip() {
73
tooltipElement = document.createElement('div');
74
tooltipElement.className = 'tooltip';
75
tooltipElement.textContent = text;
76
document.body.appendChild(tooltipElement);
77
78
const rect = node.getBoundingClientRect();
79
tooltipElement.style.left = rect.left + 'px';
80
tooltipElement.style.top = (rect.top - tooltipElement.offsetHeight - 5) + 'px';
81
}
82
83
function hideTooltip() {
84
if (tooltipElement) {
85
tooltipElement.remove();
86
tooltipElement = null;
87
}
88
}
89
90
node.addEventListener('mouseenter', showTooltip);
91
node.addEventListener('mouseleave', hideTooltip);
92
93
return {
94
update(newText) {
95
text = newText;
96
if (tooltipElement) {
97
tooltipElement.textContent = text;
98
}
99
},
100
destroy() {
101
hideTooltip();
102
node.removeEventListener('mouseenter', showTooltip);
103
node.removeEventListener('mouseleave', hideTooltip);
104
}
105
};
106
}
107
108
// Usage with parameters
109
<div use:tooltip={'Hello World'}>Hover me</div>
110
<div use:tooltip={dynamicText}>Dynamic tooltip</div>
111
```
112
113
### TypeScript Actions
114
115
Create type-safe actions with proper parameter and attribute typing.
116
117
**Usage Examples:**
118
119
```typescript
120
interface TooltipOptions {
121
text: string;
122
position?: 'top' | 'bottom' | 'left' | 'right';
123
delay?: number;
124
}
125
126
interface TooltipAttributes {
127
'data-tooltip'?: string;
128
'aria-describedby'?: string;
129
}
130
131
const tooltip: Action<HTMLElement, TooltipOptions, TooltipAttributes> = (
132
node,
133
{ text, position = 'top', delay = 0 }
134
) => {
135
let timeoutId: number;
136
let tooltipElement: HTMLElement;
137
138
function show() {
139
timeoutId = setTimeout(() => {
140
tooltipElement = document.createElement('div');
141
tooltipElement.className = `tooltip tooltip-${position}`;
142
tooltipElement.textContent = text;
143
tooltipElement.id = `tooltip-${Math.random().toString(36).substr(2, 9)}`;
144
145
document.body.appendChild(tooltipElement);
146
node.setAttribute('aria-describedby', tooltipElement.id);
147
148
positionTooltip(tooltipElement, node, position);
149
}, delay);
150
}
151
152
function hide() {
153
clearTimeout(timeoutId);
154
if (tooltipElement) {
155
tooltipElement.remove();
156
node.removeAttribute('aria-describedby');
157
}
158
}
159
160
node.addEventListener('mouseenter', show);
161
node.addEventListener('mouseleave', hide);
162
163
return {
164
update({ text: newText, position: newPosition = 'top', delay: newDelay = 0 }) {
165
text = newText;
166
position = newPosition;
167
delay = newDelay;
168
169
if (tooltipElement) {
170
tooltipElement.textContent = text;
171
tooltipElement.className = `tooltip tooltip-${position}`;
172
positionTooltip(tooltipElement, node, position);
173
}
174
},
175
176
destroy() {
177
hide();
178
node.removeEventListener('mouseenter', show);
179
node.removeEventListener('mouseleave', hide);
180
}
181
};
182
};
183
184
// Usage in TypeScript component
185
<button use:tooltip={{ text: 'Save changes', position: 'bottom', delay: 500 }}>
186
Save
187
</button>
188
```
189
190
### Common Action Patterns
191
192
Reusable patterns for building actions that handle common DOM interaction needs.
193
194
**Click Outside:**
195
196
```javascript
197
function clickOutside(node, callback) {
198
function handleClick(event) {
199
if (!node.contains(event.target)) {
200
callback();
201
}
202
}
203
204
document.addEventListener('click', handleClick, true);
205
206
return {
207
destroy() {
208
document.removeEventListener('click', handleClick, true);
209
},
210
update(newCallback) {
211
callback = newCallback;
212
}
213
};
214
}
215
216
// Usage
217
let showModal = true;
218
219
<div class="modal" use:clickOutside={() => showModal = false}>
220
Modal content
221
</div>
222
```
223
224
**Auto-resize Textarea:**
225
226
```javascript
227
function autoresize(node) {
228
function resize() {
229
node.style.height = 'auto';
230
node.style.height = node.scrollHeight + 'px';
231
}
232
233
node.addEventListener('input', resize);
234
resize(); // Initial resize
235
236
return {
237
destroy() {
238
node.removeEventListener('input', resize);
239
}
240
};
241
}
242
243
// Usage
244
<textarea use:autoresize placeholder="Type here..."></textarea>
245
```
246
247
**Long Press:**
248
249
```javascript
250
function longpress(node, callback) {
251
let timeoutId;
252
const duration = 500; // milliseconds
253
254
function handleMouseDown() {
255
timeoutId = setTimeout(callback, duration);
256
}
257
258
function handleMouseUp() {
259
clearTimeout(timeoutId);
260
}
261
262
node.addEventListener('mousedown', handleMouseDown);
263
node.addEventListener('mouseup', handleMouseUp);
264
node.addEventListener('mouseleave', handleMouseUp);
265
266
return {
267
destroy() {
268
clearTimeout(timeoutId);
269
node.removeEventListener('mousedown', handleMouseDown);
270
node.removeEventListener('mouseup', handleMouseUp);
271
node.removeEventListener('mouseleave', handleMouseUp);
272
}
273
};
274
}
275
276
// Usage
277
<button use:longpress={() => alert('Long pressed!')}>
278
Hold me
279
</button>
280
```
281
282
**Focus Trap:**
283
284
```javascript
285
function focusTrap(node) {
286
const focusableElements = node.querySelectorAll(
287
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
288
);
289
const firstElement = focusableElements[0];
290
const lastElement = focusableElements[focusableElements.length - 1];
291
292
function handleKeyDown(event) {
293
if (event.key === 'Tab') {
294
if (event.shiftKey) {
295
if (document.activeElement === firstElement) {
296
lastElement.focus();
297
event.preventDefault();
298
}
299
} else {
300
if (document.activeElement === lastElement) {
301
firstElement.focus();
302
event.preventDefault();
303
}
304
}
305
}
306
307
if (event.key === 'Escape') {
308
node.dispatchEvent(new CustomEvent('escape'));
309
}
310
}
311
312
// Focus first element when trap is activated
313
firstElement?.focus();
314
315
node.addEventListener('keydown', handleKeyDown);
316
317
return {
318
destroy() {
319
node.removeEventListener('keydown', handleKeyDown);
320
}
321
};
322
}
323
324
// Usage
325
<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
```
331
332
### Action Libraries
333
334
Actions work well as reusable libraries for common UI patterns:
335
336
```javascript
337
// ui-actions.js
338
export function ripple(node, options = {}) {
339
const { color = 'rgba(255, 255, 255, 0.5)', duration = 600 } = options;
340
341
function createRipple(event) {
342
const circle = document.createElement('span');
343
const rect = node.getBoundingClientRect();
344
const size = Math.max(rect.width, rect.height);
345
const x = event.clientX - rect.left - size / 2;
346
const y = event.clientY - rect.top - size / 2;
347
348
circle.style.cssText = `
349
position: absolute;
350
width: ${size}px;
351
height: ${size}px;
352
left: ${x}px;
353
top: ${y}px;
354
background: ${color};
355
border-radius: 50%;
356
pointer-events: none;
357
transform: scale(0);
358
animation: ripple ${duration}ms ease-out;
359
`;
360
361
node.appendChild(circle);
362
setTimeout(() => circle.remove(), duration);
363
}
364
365
// Ensure node is positioned
366
if (getComputedStyle(node).position === 'static') {
367
node.style.position = 'relative';
368
}
369
370
node.style.overflow = 'hidden';
371
node.addEventListener('click', createRipple);
372
373
return {
374
update(newOptions) {
375
Object.assign(options, newOptions);
376
},
377
destroy() {
378
node.removeEventListener('click', createRipple);
379
}
380
};
381
}
382
383
export function lazyload(node, src) {
384
if ('IntersectionObserver' in window) {
385
const observer = new IntersectionObserver(entries => {
386
if (entries[0].isIntersecting) {
387
node.src = src;
388
observer.disconnect();
389
}
390
});
391
392
observer.observe(node);
393
394
return {
395
update(newSrc) {
396
src = newSrc;
397
},
398
destroy() {
399
observer.disconnect();
400
}
401
};
402
} else {
403
// Fallback for browsers without IntersectionObserver
404
node.src = src;
405
}
406
}
407
408
// Usage
409
import { ripple, lazyload } from './ui-actions.js';
410
411
<button use:ripple={{ color: 'rgba(0, 100, 255, 0.3)' }}>
412
Ripple Button
413
</button>
414
415
<img use:lazyload={'./large-image.jpg'} alt="Lazy loaded" />
416
```
417
418
## Action Best Practices
419
420
1. **Clean Up Resources**: Always remove event listeners and clear timers in the destroy method
421
2. **Handle Parameter Updates**: Implement update method when actions accept parameters
422
3. **Type Safety**: Use TypeScript interfaces for better development experience
423
4. **Performance**: Avoid creating actions in render loops or expensive operations
424
5. **Accessibility**: Consider ARIA attributes and keyboard navigation in your actions
425
6. **Error Handling**: Handle edge cases and provide fallbacks where appropriate