@sc4rfurryx/proteusjs 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +331 -77
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/react.d.ts +140 -0
  5. package/dist/adapters/react.esm.js +849 -0
  6. package/dist/adapters/react.esm.js.map +1 -0
  7. package/dist/adapters/svelte.d.ts +181 -0
  8. package/dist/adapters/svelte.esm.js +909 -0
  9. package/dist/adapters/svelte.esm.js.map +1 -0
  10. package/dist/adapters/vue.d.ts +205 -0
  11. package/dist/adapters/vue.esm.js +873 -0
  12. package/dist/adapters/vue.esm.js.map +1 -0
  13. package/dist/modules/a11y-audit.d.ts +31 -0
  14. package/dist/modules/a11y-audit.esm.js +64 -0
  15. package/dist/modules/a11y-audit.esm.js.map +1 -0
  16. package/dist/modules/a11y-primitives.d.ts +36 -0
  17. package/dist/modules/a11y-primitives.esm.js +114 -0
  18. package/dist/modules/a11y-primitives.esm.js.map +1 -0
  19. package/dist/modules/anchor.d.ts +30 -0
  20. package/dist/modules/anchor.esm.js +219 -0
  21. package/dist/modules/anchor.esm.js.map +1 -0
  22. package/dist/modules/container.d.ts +60 -0
  23. package/dist/modules/container.esm.js +194 -0
  24. package/dist/modules/container.esm.js.map +1 -0
  25. package/dist/modules/perf.d.ts +82 -0
  26. package/dist/modules/perf.esm.js +257 -0
  27. package/dist/modules/perf.esm.js.map +1 -0
  28. package/dist/modules/popover.d.ts +33 -0
  29. package/dist/modules/popover.esm.js +191 -0
  30. package/dist/modules/popover.esm.js.map +1 -0
  31. package/dist/modules/scroll.d.ts +43 -0
  32. package/dist/modules/scroll.esm.js +195 -0
  33. package/dist/modules/scroll.esm.js.map +1 -0
  34. package/dist/modules/transitions.d.ts +35 -0
  35. package/dist/modules/transitions.esm.js +120 -0
  36. package/dist/modules/transitions.esm.js.map +1 -0
  37. package/dist/modules/typography.d.ts +72 -0
  38. package/dist/modules/typography.esm.js +168 -0
  39. package/dist/modules/typography.esm.js.map +1 -0
  40. package/dist/proteus.cjs.js +1554 -12
  41. package/dist/proteus.cjs.js.map +1 -1
  42. package/dist/proteus.d.ts +516 -12
  43. package/dist/proteus.esm.js +1545 -12
  44. package/dist/proteus.esm.js.map +1 -1
  45. package/dist/proteus.esm.min.js +3 -3
  46. package/dist/proteus.esm.min.js.map +1 -1
  47. package/dist/proteus.js +1554 -12
  48. package/dist/proteus.js.map +1 -1
  49. package/dist/proteus.min.js +3 -3
  50. package/dist/proteus.min.js.map +1 -1
  51. package/package.json +69 -7
  52. package/src/adapters/react.ts +264 -0
  53. package/src/adapters/svelte.ts +321 -0
  54. package/src/adapters/vue.ts +268 -0
  55. package/src/index.ts +33 -6
  56. package/src/modules/a11y-audit/index.ts +84 -0
  57. package/src/modules/a11y-primitives/index.ts +152 -0
  58. package/src/modules/anchor/index.ts +259 -0
  59. package/src/modules/container/index.ts +230 -0
  60. package/src/modules/perf/index.ts +291 -0
  61. package/src/modules/popover/index.ts +238 -0
  62. package/src/modules/scroll/index.ts +251 -0
  63. package/src/modules/transitions/index.ts +145 -0
  64. package/src/modules/typography/index.ts +239 -0
  65. package/src/utils/version.ts +1 -1
@@ -0,0 +1,909 @@
1
+ /*!
2
+ * ProteusJS v1.1.1
3
+ * Shape-shifting responsive design that adapts like the sea god himself
4
+ * (c) 2025 sc4rfurry
5
+ * Released under the MIT License
6
+ */
7
+ import { writable } from 'svelte/store';
8
+
9
+ /**
10
+ * @sc4rfurryx/proteusjs/transitions
11
+ * View Transitions API wrapper with safe fallbacks
12
+ *
13
+ * @version 1.1.0
14
+ * @author sc4rfurry
15
+ * @license MIT
16
+ */
17
+ /**
18
+ * One API for animating DOM state changes and cross-document navigations
19
+ * using the View Transitions API with safe fallbacks.
20
+ */
21
+ async function transition(run, opts = {}) {
22
+ const { name, duration = 300, onBefore, onAfter, allowInterrupt = true } = opts;
23
+ // Check for View Transitions API support
24
+ const hasViewTransitions = 'startViewTransition' in document;
25
+ if (onBefore) {
26
+ onBefore();
27
+ }
28
+ if (!hasViewTransitions) {
29
+ // Fallback: run immediately without transitions
30
+ try {
31
+ await run();
32
+ }
33
+ finally {
34
+ if (onAfter) {
35
+ onAfter();
36
+ }
37
+ }
38
+ return;
39
+ }
40
+ // Use native View Transitions API
41
+ try {
42
+ const viewTransition = document.startViewTransition(async () => {
43
+ await run();
44
+ });
45
+ // Add CSS view-transition-name if name provided
46
+ if (name) {
47
+ const style = document.createElement('style');
48
+ style.textContent = `
49
+ ::view-transition-old(${name}),
50
+ ::view-transition-new(${name}) {
51
+ animation-duration: ${duration}ms;
52
+ }
53
+ `;
54
+ document.head.appendChild(style);
55
+ // Clean up style after transition
56
+ viewTransition.finished.finally(() => {
57
+ style.remove();
58
+ });
59
+ }
60
+ await viewTransition.finished;
61
+ }
62
+ catch (error) {
63
+ console.warn('View transition failed, falling back to immediate execution:', error);
64
+ await run();
65
+ }
66
+ finally {
67
+ if (onAfter) {
68
+ onAfter();
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * @sc4rfurryx/proteusjs/scroll
75
+ * Scroll-driven animations with CSS Scroll-Linked Animations
76
+ *
77
+ * @version 1.1.0
78
+ * @author sc4rfurry
79
+ * @license MIT
80
+ */
81
+ /**
82
+ * Zero-boilerplate setup for CSS Scroll-Linked Animations with fallbacks
83
+ */
84
+ function scrollAnimate(target, opts) {
85
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
86
+ if (!targetEl) {
87
+ throw new Error('Target element not found');
88
+ }
89
+ const { keyframes, range = ['0%', '100%'], timeline = {}, fallback = 'io' } = opts;
90
+ const { axis = 'block', start = '0%', end = '100%' } = timeline;
91
+ // Check for CSS Scroll-Linked Animations support
92
+ const hasScrollTimeline = 'CSS' in window && CSS.supports('animation-timeline', 'scroll()');
93
+ // Check for reduced motion preference
94
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
95
+ if (prefersReducedMotion) {
96
+ // Respect user preference - either disable or reduce animation
97
+ if (fallback === false)
98
+ return;
99
+ // Apply only the end state for reduced motion
100
+ const endKeyframe = keyframes[keyframes.length - 1];
101
+ Object.assign(targetEl.style, endKeyframe);
102
+ return;
103
+ }
104
+ if (hasScrollTimeline) {
105
+ // Use native CSS Scroll-Linked Animations
106
+ const timelineName = `scroll-timeline-${Math.random().toString(36).substr(2, 9)}`;
107
+ // Create scroll timeline
108
+ const style = document.createElement('style');
109
+ style.textContent = `
110
+ @scroll-timeline ${timelineName} {
111
+ source: nearest;
112
+ orientation: ${axis};
113
+ scroll-offsets: ${start}, ${end};
114
+ }
115
+
116
+ .scroll-animate-${timelineName} {
117
+ animation-timeline: ${timelineName};
118
+ animation-duration: 1ms; /* Required but ignored */
119
+ animation-fill-mode: both;
120
+ }
121
+ `;
122
+ document.head.appendChild(style);
123
+ // Apply animation class
124
+ targetEl.classList.add(`scroll-animate-${timelineName}`);
125
+ // Create Web Animations API animation
126
+ const animation = targetEl.animate(keyframes, {
127
+ duration: 1, // Required but ignored with scroll timeline
128
+ fill: 'both'
129
+ });
130
+ // Set scroll timeline (when supported)
131
+ if ('timeline' in animation) {
132
+ animation.timeline = new window.ScrollTimeline({
133
+ source: document.scrollingElement,
134
+ orientation: axis,
135
+ scrollOffsets: [
136
+ { target: targetEl, edge: 'start', threshold: parseFloat(start) / 100 },
137
+ { target: targetEl, edge: 'end', threshold: parseFloat(end) / 100 }
138
+ ]
139
+ });
140
+ }
141
+ }
142
+ else if (fallback === 'io') {
143
+ // Fallback using Intersection Observer
144
+ let animation = null;
145
+ const observer = new IntersectionObserver((entries) => {
146
+ entries.forEach(entry => {
147
+ const progress = Math.max(0, Math.min(1, entry.intersectionRatio));
148
+ if (!animation) {
149
+ animation = targetEl.animate(keyframes, {
150
+ duration: 1000,
151
+ fill: 'both'
152
+ });
153
+ animation.pause();
154
+ }
155
+ // Update animation progress based on intersection
156
+ animation.currentTime = progress * 1000;
157
+ });
158
+ }, {
159
+ threshold: Array.from({ length: 101 }, (_, i) => i / 100) // 0 to 1 in 0.01 steps
160
+ });
161
+ observer.observe(targetEl);
162
+ // Store cleanup function
163
+ targetEl._scrollAnimateCleanup = () => {
164
+ observer.disconnect();
165
+ if (animation) {
166
+ animation.cancel();
167
+ }
168
+ };
169
+ }
170
+ }
171
+
172
+ /**
173
+ * @sc4rfurryx/proteusjs/popover
174
+ * HTML Popover API wrapper with robust focus/inert handling
175
+ *
176
+ * @version 1.1.0
177
+ * @author sc4rfurry
178
+ * @license MIT
179
+ */
180
+ /**
181
+ * Unified API for menus, tooltips, and dialogs using the native Popover API
182
+ * with robust focus/inert handling
183
+ */
184
+ function attach(trigger, panel, opts = {}) {
185
+ const triggerEl = typeof trigger === 'string' ? document.querySelector(trigger) : trigger;
186
+ const panelEl = typeof panel === 'string' ? document.querySelector(panel) : panel;
187
+ if (!triggerEl || !panelEl) {
188
+ throw new Error('Both trigger and panel elements must exist');
189
+ }
190
+ const { type = 'menu', trapFocus = type === 'dialog', restoreFocus = true, closeOnEscape = true, onOpen, onClose } = opts;
191
+ let isOpen = false;
192
+ let previousFocus = null;
193
+ let focusTrap = null;
194
+ // Check for native Popover API support
195
+ const hasPopoverAPI = 'popover' in HTMLElement.prototype;
196
+ // Set up ARIA attributes
197
+ const setupAria = () => {
198
+ const panelId = panelEl.id || `popover-${Math.random().toString(36).substr(2, 9)}`;
199
+ panelEl.id = panelId;
200
+ triggerEl.setAttribute('aria-expanded', 'false');
201
+ triggerEl.setAttribute('aria-controls', panelId);
202
+ if (type === 'menu') {
203
+ triggerEl.setAttribute('aria-haspopup', 'menu');
204
+ panelEl.setAttribute('role', 'menu');
205
+ }
206
+ else if (type === 'dialog') {
207
+ triggerEl.setAttribute('aria-haspopup', 'dialog');
208
+ panelEl.setAttribute('role', 'dialog');
209
+ panelEl.setAttribute('aria-modal', 'true');
210
+ }
211
+ else if (type === 'tooltip') {
212
+ triggerEl.setAttribute('aria-describedby', panelId);
213
+ panelEl.setAttribute('role', 'tooltip');
214
+ }
215
+ };
216
+ // Set up native popover if supported
217
+ const setupNativePopover = () => {
218
+ if (hasPopoverAPI) {
219
+ panelEl.popover = type === 'dialog' ? 'manual' : 'auto';
220
+ triggerEl.setAttribute('popovertarget', panelEl.id);
221
+ }
222
+ };
223
+ // Focus trap implementation
224
+ class FocusTrap {
225
+ constructor(container) {
226
+ this.container = container;
227
+ this.focusableElements = [];
228
+ this.handleKeyDown = (e) => {
229
+ if (e.key !== 'Tab')
230
+ return;
231
+ const firstElement = this.focusableElements[0];
232
+ const lastElement = this.focusableElements[this.focusableElements.length - 1];
233
+ if (e.shiftKey) {
234
+ if (document.activeElement === firstElement) {
235
+ e.preventDefault();
236
+ lastElement.focus();
237
+ }
238
+ }
239
+ else {
240
+ if (document.activeElement === lastElement) {
241
+ e.preventDefault();
242
+ firstElement.focus();
243
+ }
244
+ }
245
+ };
246
+ this.updateFocusableElements();
247
+ }
248
+ updateFocusableElements() {
249
+ const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
250
+ this.focusableElements = Array.from(this.container.querySelectorAll(selector));
251
+ }
252
+ activate() {
253
+ this.updateFocusableElements();
254
+ if (this.focusableElements.length > 0) {
255
+ this.focusableElements[0].focus();
256
+ }
257
+ document.addEventListener('keydown', this.handleKeyDown);
258
+ }
259
+ deactivate() {
260
+ document.removeEventListener('keydown', this.handleKeyDown);
261
+ }
262
+ }
263
+ const open = () => {
264
+ if (isOpen)
265
+ return;
266
+ if (restoreFocus) {
267
+ previousFocus = document.activeElement;
268
+ }
269
+ if (hasPopoverAPI) {
270
+ panelEl.showPopover();
271
+ }
272
+ else {
273
+ panelEl.style.display = 'block';
274
+ panelEl.setAttribute('data-popover-open', 'true');
275
+ }
276
+ triggerEl.setAttribute('aria-expanded', 'true');
277
+ isOpen = true;
278
+ if (trapFocus) {
279
+ focusTrap = new FocusTrap(panelEl);
280
+ focusTrap.activate();
281
+ }
282
+ if (onOpen) {
283
+ onOpen();
284
+ }
285
+ };
286
+ const close = () => {
287
+ if (!isOpen)
288
+ return;
289
+ if (hasPopoverAPI) {
290
+ panelEl.hidePopover();
291
+ }
292
+ else {
293
+ panelEl.style.display = 'none';
294
+ panelEl.removeAttribute('data-popover-open');
295
+ }
296
+ triggerEl.setAttribute('aria-expanded', 'false');
297
+ isOpen = false;
298
+ if (focusTrap) {
299
+ focusTrap.deactivate();
300
+ focusTrap = null;
301
+ }
302
+ if (restoreFocus && previousFocus) {
303
+ previousFocus.focus();
304
+ previousFocus = null;
305
+ }
306
+ if (onClose) {
307
+ onClose();
308
+ }
309
+ };
310
+ const toggle = () => {
311
+ if (isOpen) {
312
+ close();
313
+ }
314
+ else {
315
+ open();
316
+ }
317
+ };
318
+ const handleKeyDown = (e) => {
319
+ if (closeOnEscape && e.key === 'Escape' && isOpen) {
320
+ e.preventDefault();
321
+ close();
322
+ }
323
+ };
324
+ const handleClick = (e) => {
325
+ e.preventDefault();
326
+ toggle();
327
+ };
328
+ const destroy = () => {
329
+ triggerEl.removeEventListener('click', handleClick);
330
+ document.removeEventListener('keydown', handleKeyDown);
331
+ if (focusTrap) {
332
+ focusTrap.deactivate();
333
+ }
334
+ if (isOpen) {
335
+ close();
336
+ }
337
+ };
338
+ // Initialize
339
+ setupAria();
340
+ setupNativePopover();
341
+ triggerEl.addEventListener('click', handleClick);
342
+ document.addEventListener('keydown', handleKeyDown);
343
+ return {
344
+ open,
345
+ close,
346
+ toggle,
347
+ destroy
348
+ };
349
+ }
350
+
351
+ /**
352
+ * @sc4rfurryx/proteusjs/anchor
353
+ * CSS Anchor Positioning utilities with robust JS fallback
354
+ *
355
+ * @version 1.1.0
356
+ * @author sc4rfurry
357
+ * @license MIT
358
+ */
359
+ /**
360
+ * Declarative tethers (tooltips, callouts) via CSS Anchor Positioning when available;
361
+ * robust JS fallback with flip/collision detection
362
+ */
363
+ function tether(floating, opts) {
364
+ const floatingEl = typeof floating === 'string' ? document.querySelector(floating) : floating;
365
+ const anchorEl = typeof opts.anchor === 'string' ? document.querySelector(opts.anchor) : opts.anchor;
366
+ if (!floatingEl || !anchorEl) {
367
+ throw new Error('Both floating and anchor elements must exist');
368
+ }
369
+ const { placement = 'bottom', align = 'center', offset = 8, strategy = 'absolute' } = opts;
370
+ // Check for CSS Anchor Positioning support
371
+ const hasAnchorPositioning = CSS.supports('anchor-name', 'test');
372
+ let isDestroyed = false;
373
+ let resizeObserver = null;
374
+ const setupCSSAnchorPositioning = () => {
375
+ if (!hasAnchorPositioning)
376
+ return false;
377
+ // Generate unique anchor name
378
+ const anchorName = `anchor-${Math.random().toString(36).substring(2, 11)}`;
379
+ // Set anchor name on anchor element
380
+ anchorEl.style.setProperty('anchor-name', anchorName);
381
+ // Position floating element using CSS anchor positioning
382
+ const floatingStyle = floatingEl;
383
+ floatingStyle.style.position = strategy;
384
+ floatingStyle.style.setProperty('position-anchor', anchorName);
385
+ // Set position based on placement
386
+ switch (placement) {
387
+ case 'top':
388
+ floatingStyle.style.bottom = `anchor(bottom, ${offset}px)`;
389
+ break;
390
+ case 'bottom':
391
+ floatingStyle.style.top = `anchor(bottom, ${offset}px)`;
392
+ break;
393
+ case 'left':
394
+ floatingStyle.style.right = `anchor(left, ${offset}px)`;
395
+ break;
396
+ case 'right':
397
+ floatingStyle.style.left = `anchor(right, ${offset}px)`;
398
+ break;
399
+ }
400
+ // Set alignment
401
+ if (placement === 'top' || placement === 'bottom') {
402
+ switch (align) {
403
+ case 'start':
404
+ floatingStyle.style.left = 'anchor(left)';
405
+ break;
406
+ case 'center':
407
+ floatingStyle.style.left = 'anchor(center)';
408
+ floatingStyle.style.transform = 'translateX(-50%)';
409
+ break;
410
+ case 'end':
411
+ floatingStyle.style.right = 'anchor(right)';
412
+ break;
413
+ }
414
+ }
415
+ else {
416
+ switch (align) {
417
+ case 'start':
418
+ floatingStyle.style.top = 'anchor(top)';
419
+ break;
420
+ case 'center':
421
+ floatingStyle.style.top = 'anchor(center)';
422
+ floatingStyle.style.transform = 'translateY(-50%)';
423
+ break;
424
+ case 'end':
425
+ floatingStyle.style.bottom = 'anchor(bottom)';
426
+ break;
427
+ }
428
+ }
429
+ return true;
430
+ };
431
+ const calculatePosition = () => {
432
+ const anchorRect = anchorEl.getBoundingClientRect();
433
+ const floatingRect = floatingEl.getBoundingClientRect();
434
+ const viewport = {
435
+ width: window.innerWidth,
436
+ height: window.innerHeight
437
+ };
438
+ let finalPlacement = placement;
439
+ let x = 0;
440
+ let y = 0;
441
+ // Calculate base position
442
+ switch (finalPlacement) {
443
+ case 'top':
444
+ x = anchorRect.left;
445
+ y = anchorRect.top - floatingRect.height - offset;
446
+ break;
447
+ case 'bottom':
448
+ x = anchorRect.left;
449
+ y = anchorRect.bottom + offset;
450
+ break;
451
+ case 'left':
452
+ x = anchorRect.left - floatingRect.width - offset;
453
+ y = anchorRect.top;
454
+ break;
455
+ case 'right':
456
+ x = anchorRect.right + offset;
457
+ y = anchorRect.top;
458
+ break;
459
+ case 'auto': {
460
+ // Choose best placement based on available space
461
+ const spaces = {
462
+ top: anchorRect.top,
463
+ bottom: viewport.height - anchorRect.bottom,
464
+ left: anchorRect.left,
465
+ right: viewport.width - anchorRect.right
466
+ };
467
+ const bestPlacement = Object.entries(spaces).reduce((a, b) => spaces[a[0]] > spaces[b[0]] ? a : b)[0];
468
+ finalPlacement = bestPlacement;
469
+ return calculatePosition(); // Recursive call with determined placement
470
+ }
471
+ }
472
+ // Apply alignment
473
+ if (finalPlacement === 'top' || finalPlacement === 'bottom') {
474
+ switch (align) {
475
+ case 'start':
476
+ // x already set correctly
477
+ break;
478
+ case 'center':
479
+ x = anchorRect.left + (anchorRect.width - floatingRect.width) / 2;
480
+ break;
481
+ case 'end':
482
+ x = anchorRect.right - floatingRect.width;
483
+ break;
484
+ }
485
+ }
486
+ else {
487
+ switch (align) {
488
+ case 'start':
489
+ // y already set correctly
490
+ break;
491
+ case 'center':
492
+ y = anchorRect.top + (anchorRect.height - floatingRect.height) / 2;
493
+ break;
494
+ case 'end':
495
+ y = anchorRect.bottom - floatingRect.height;
496
+ break;
497
+ }
498
+ }
499
+ // Collision detection and adjustment
500
+ if (x < 0)
501
+ x = 8;
502
+ if (y < 0)
503
+ y = 8;
504
+ if (x + floatingRect.width > viewport.width) {
505
+ x = viewport.width - floatingRect.width - 8;
506
+ }
507
+ if (y + floatingRect.height > viewport.height) {
508
+ y = viewport.height - floatingRect.height - 8;
509
+ }
510
+ return { x, y };
511
+ };
512
+ const updatePosition = () => {
513
+ if (isDestroyed)
514
+ return;
515
+ if (!hasAnchorPositioning) {
516
+ const { x, y } = calculatePosition();
517
+ const floatingStyle = floatingEl;
518
+ floatingStyle.style.position = strategy;
519
+ floatingStyle.style.left = `${x}px`;
520
+ floatingStyle.style.top = `${y}px`;
521
+ }
522
+ };
523
+ const setupJSFallback = () => {
524
+ updatePosition();
525
+ // Set up observers for position updates
526
+ resizeObserver = new ResizeObserver(updatePosition);
527
+ resizeObserver.observe(anchorEl);
528
+ resizeObserver.observe(floatingEl);
529
+ window.addEventListener('scroll', updatePosition, { passive: true });
530
+ window.addEventListener('resize', updatePosition, { passive: true });
531
+ };
532
+ const destroy = () => {
533
+ isDestroyed = true;
534
+ if (resizeObserver) {
535
+ resizeObserver.disconnect();
536
+ resizeObserver = null;
537
+ }
538
+ window.removeEventListener('scroll', updatePosition);
539
+ window.removeEventListener('resize', updatePosition);
540
+ // Clean up CSS anchor positioning
541
+ if (hasAnchorPositioning) {
542
+ anchorEl.style.removeProperty('anchor-name');
543
+ const floatingStyle = floatingEl;
544
+ floatingStyle.style.removeProperty('position-anchor');
545
+ floatingStyle.style.position = '';
546
+ }
547
+ };
548
+ // Initialize
549
+ if (!setupCSSAnchorPositioning()) {
550
+ setupJSFallback();
551
+ }
552
+ return {
553
+ update: updatePosition,
554
+ destroy
555
+ };
556
+ }
557
+
558
+ /**
559
+ * @sc4rfurryx/proteusjs/container
560
+ * Container/Style Query helpers with visualization devtools
561
+ *
562
+ * @version 1.1.0
563
+ * @author sc4rfurry
564
+ * @license MIT
565
+ */
566
+ /**
567
+ * Sugar on native container queries with dev visualization
568
+ */
569
+ function defineContainer(target, name, opts = {}) {
570
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
571
+ if (!targetEl) {
572
+ throw new Error('Target element not found');
573
+ }
574
+ const { type = 'size', inlineSize: _inlineSize = true } = opts;
575
+ const containerName = name || `container-${Math.random().toString(36).substring(2, 11)}`;
576
+ // Apply container properties
577
+ const element = targetEl;
578
+ element.style.containerName = containerName;
579
+ element.style.containerType = type;
580
+ // Warn if containment settings are missing
581
+ const computedStyle = getComputedStyle(element);
582
+ if (!computedStyle.contain || computedStyle.contain === 'none') {
583
+ console.warn(`Container "${containerName}" may need explicit containment settings for optimal performance`);
584
+ }
585
+ // Dev overlay (only in development)
586
+ if (process.env['NODE_ENV'] === 'development' || window.__PROTEUS_DEV__) {
587
+ createDevOverlay(element, containerName);
588
+ }
589
+ }
590
+ /**
591
+ * Create development overlay showing container bounds and breakpoints
592
+ */
593
+ function createDevOverlay(element, name) {
594
+ const overlay = document.createElement('div');
595
+ overlay.className = 'proteus-container-overlay';
596
+ overlay.style.cssText = `
597
+ position: absolute;
598
+ top: 0;
599
+ left: 0;
600
+ right: 0;
601
+ bottom: 0;
602
+ pointer-events: none;
603
+ border: 2px dashed rgba(255, 0, 255, 0.5);
604
+ background: rgba(255, 0, 255, 0.05);
605
+ z-index: 9999;
606
+ font-family: monospace;
607
+ font-size: 12px;
608
+ color: #ff00ff;
609
+ `;
610
+ const label = document.createElement('div');
611
+ label.style.cssText = `
612
+ position: absolute;
613
+ top: -20px;
614
+ left: 0;
615
+ background: rgba(255, 0, 255, 0.9);
616
+ color: white;
617
+ padding: 2px 6px;
618
+ border-radius: 3px;
619
+ font-size: 10px;
620
+ white-space: nowrap;
621
+ `;
622
+ label.textContent = `Container: ${name}`;
623
+ const sizeInfo = document.createElement('div');
624
+ sizeInfo.style.cssText = `
625
+ position: absolute;
626
+ bottom: 2px;
627
+ right: 2px;
628
+ background: rgba(0, 0, 0, 0.7);
629
+ color: white;
630
+ padding: 2px 4px;
631
+ border-radius: 2px;
632
+ font-size: 10px;
633
+ `;
634
+ overlay.appendChild(label);
635
+ overlay.appendChild(sizeInfo);
636
+ // Position overlay relative to container
637
+ if (getComputedStyle(element).position === 'static') {
638
+ element.style.position = 'relative';
639
+ }
640
+ element.appendChild(overlay);
641
+ // Update size info
642
+ const updateSizeInfo = () => {
643
+ const rect = element.getBoundingClientRect();
644
+ sizeInfo.textContent = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
645
+ };
646
+ updateSizeInfo();
647
+ // Update on resize
648
+ if ('ResizeObserver' in window) {
649
+ const resizeObserver = new ResizeObserver(updateSizeInfo);
650
+ resizeObserver.observe(element);
651
+ }
652
+ // Store cleanup function
653
+ element._proteusContainerCleanup = () => {
654
+ overlay.remove();
655
+ };
656
+ }
657
+
658
+ /**
659
+ * @sc4rfurryx/proteusjs/adapters/svelte
660
+ * Svelte actions and stores for ProteusJS
661
+ *
662
+ * @version 1.1.0
663
+ * @author sc4rfurry
664
+ * @license MIT
665
+ */
666
+ /**
667
+ * Svelte action for scroll animations
668
+ */
669
+ function proteusScroll(node, options) {
670
+ scrollAnimate(node, options);
671
+ return {
672
+ update(newOptions) {
673
+ // Re-apply with new options
674
+ scrollAnimate(node, newOptions);
675
+ },
676
+ destroy() {
677
+ // Cleanup handled by scroll module
678
+ }
679
+ };
680
+ }
681
+ /**
682
+ * Svelte action for container queries
683
+ */
684
+ function proteusContainer(node, options = {}) {
685
+ const { name, containerOptions } = options;
686
+ defineContainer(node, name, containerOptions);
687
+ return {
688
+ update(newOptions) {
689
+ const { name: newName, containerOptions: newContainerOptions } = newOptions;
690
+ defineContainer(node, newName, newContainerOptions);
691
+ }
692
+ };
693
+ }
694
+ /**
695
+ * Svelte action for popover functionality
696
+ */
697
+ function proteusPopover(node, options) {
698
+ let controller = null;
699
+ const { panel, popoverOptions } = options;
700
+ controller = attach(node, panel, popoverOptions);
701
+ return {
702
+ update(newOptions) {
703
+ if (controller) {
704
+ controller.destroy();
705
+ }
706
+ controller = attach(node, newOptions.panel, newOptions.popoverOptions);
707
+ },
708
+ destroy() {
709
+ if (controller) {
710
+ controller.destroy();
711
+ }
712
+ }
713
+ };
714
+ }
715
+ /**
716
+ * Svelte action for anchor positioning
717
+ */
718
+ function proteusAnchor(node, options) {
719
+ let controller = null;
720
+ controller = tether(node, options);
721
+ return {
722
+ update(newOptions) {
723
+ if (controller) {
724
+ controller.destroy();
725
+ }
726
+ controller = tether(node, newOptions);
727
+ },
728
+ destroy() {
729
+ if (controller) {
730
+ controller.destroy();
731
+ }
732
+ }
733
+ };
734
+ }
735
+ /**
736
+ * Svelte action for performance optimizations
737
+ */
738
+ function proteusPerf(node) {
739
+ const observer = new IntersectionObserver((entries) => {
740
+ entries.forEach(entry => {
741
+ if (entry.isIntersecting) {
742
+ node.style.contentVisibility = 'visible';
743
+ }
744
+ else {
745
+ node.style.contentVisibility = 'auto';
746
+ }
747
+ });
748
+ }, { rootMargin: '50px' });
749
+ observer.observe(node);
750
+ return {
751
+ destroy() {
752
+ observer.disconnect();
753
+ }
754
+ };
755
+ }
756
+ /**
757
+ * Svelte action for accessibility enhancements
758
+ */
759
+ function proteusA11y(node, options = {}) {
760
+ const { announceChanges = false } = options;
761
+ // Enhance focus indicators
762
+ const focusableElements = node.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
763
+ const focusHandlers = new Map();
764
+ focusableElements.forEach(element => {
765
+ const htmlEl = element;
766
+ const focusHandler = () => {
767
+ htmlEl.style.outline = '2px solid #0066cc';
768
+ htmlEl.style.outlineOffset = '2px';
769
+ };
770
+ const blurHandler = () => {
771
+ htmlEl.style.outline = 'none';
772
+ };
773
+ htmlEl.addEventListener('focus', focusHandler);
774
+ htmlEl.addEventListener('blur', blurHandler);
775
+ focusHandlers.set(htmlEl, { focusHandler, blurHandler });
776
+ });
777
+ let mutationObserver = null;
778
+ if (announceChanges) {
779
+ mutationObserver = new MutationObserver(() => {
780
+ const announcement = document.createElement('div');
781
+ announcement.setAttribute('aria-live', 'polite');
782
+ announcement.style.position = 'absolute';
783
+ announcement.style.left = '-10000px';
784
+ announcement.textContent = 'Content updated';
785
+ document.body.appendChild(announcement);
786
+ setTimeout(() => {
787
+ document.body.removeChild(announcement);
788
+ }, 1000);
789
+ });
790
+ mutationObserver.observe(node, { childList: true, subtree: true });
791
+ }
792
+ return {
793
+ destroy() {
794
+ // Clean up focus handlers
795
+ focusHandlers.forEach(({ focusHandler, blurHandler }, element) => {
796
+ element.removeEventListener('focus', focusHandler);
797
+ element.removeEventListener('blur', blurHandler);
798
+ });
799
+ if (mutationObserver) {
800
+ mutationObserver.disconnect();
801
+ }
802
+ }
803
+ };
804
+ }
805
+ /**
806
+ * Store for managing popover state
807
+ */
808
+ function createPopover(triggerSelector, panelSelector, options) {
809
+ const isOpen = writable(false);
810
+ let controller = null;
811
+ const initialize = () => {
812
+ const trigger = document.querySelector(triggerSelector);
813
+ const panel = document.querySelector(panelSelector);
814
+ if (trigger && panel) {
815
+ controller = attach(trigger, panel, {
816
+ ...options,
817
+ onOpen: () => {
818
+ isOpen.set(true);
819
+ options?.onOpen?.();
820
+ },
821
+ onClose: () => {
822
+ isOpen.set(false);
823
+ options?.onClose?.();
824
+ }
825
+ });
826
+ }
827
+ };
828
+ const open = () => controller?.open();
829
+ const close = () => controller?.close();
830
+ const toggle = () => controller?.toggle();
831
+ const destroy = () => {
832
+ if (controller) {
833
+ controller.destroy();
834
+ controller = null;
835
+ }
836
+ };
837
+ return {
838
+ isOpen,
839
+ initialize,
840
+ open,
841
+ close,
842
+ toggle,
843
+ destroy
844
+ };
845
+ }
846
+ /**
847
+ * Store for managing transition state
848
+ */
849
+ function createTransition() {
850
+ const isTransitioning = writable(false);
851
+ const runTransition = async (run, opts) => {
852
+ isTransitioning.set(true);
853
+ try {
854
+ await transition(run, opts);
855
+ }
856
+ finally {
857
+ isTransitioning.set(false);
858
+ }
859
+ };
860
+ return {
861
+ isTransitioning,
862
+ runTransition
863
+ };
864
+ }
865
+ /**
866
+ * Utility function to create reactive container size store
867
+ */
868
+ function createContainerSize(element) {
869
+ const size = writable({ width: 0, height: 0 });
870
+ if ('ResizeObserver' in window) {
871
+ const resizeObserver = new ResizeObserver((entries) => {
872
+ for (const entry of entries) {
873
+ const { width, height } = entry.contentRect;
874
+ size.set({ width, height });
875
+ }
876
+ });
877
+ resizeObserver.observe(element);
878
+ return {
879
+ size,
880
+ destroy: () => resizeObserver.disconnect()
881
+ };
882
+ }
883
+ // Fallback for browsers without ResizeObserver
884
+ const updateSize = () => {
885
+ const rect = element.getBoundingClientRect();
886
+ size.set({ width: rect.width, height: rect.height });
887
+ };
888
+ updateSize();
889
+ window.addEventListener('resize', updateSize);
890
+ return {
891
+ size,
892
+ destroy: () => window.removeEventListener('resize', updateSize)
893
+ };
894
+ }
895
+ // Export all actions and utilities
896
+ var svelte = {
897
+ proteusScroll,
898
+ proteusContainer,
899
+ proteusPopover,
900
+ proteusAnchor,
901
+ proteusPerf,
902
+ proteusA11y,
903
+ createPopover,
904
+ createTransition,
905
+ createContainerSize
906
+ };
907
+
908
+ export { createContainerSize, createPopover, createTransition, svelte as default, proteusA11y, proteusAnchor, proteusContainer, proteusPerf, proteusPopover, proteusScroll };
909
+ //# sourceMappingURL=svelte.esm.js.map