@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
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * ProteusJS v1.0.0
2
+ * ProteusJS v1.1.1
3
3
  * Shape-shifting responsive design that adapts like the sea god himself
4
- * (c) 2024 sc4rfurry
4
+ * (c) 2025 sc4rfurry
5
5
  * Released under the MIT License
6
6
  */
7
7
  /**
@@ -15158,7 +15158,7 @@ class BrowserPolyfills {
15158
15158
  /**
15159
15159
  * Check if the current environment supports ProteusJS features
15160
15160
  */
15161
- function isSupported() {
15161
+ function isSupported$1() {
15162
15162
  const support = getSupportInfo();
15163
15163
  return support.resizeObserver && support.intersectionObserver;
15164
15164
  }
@@ -15225,7 +15225,7 @@ function checkPassiveEventListenerSupport() {
15225
15225
  /**
15226
15226
  * Version utilities for ProteusJS
15227
15227
  */
15228
- const version = '1.0.0';
15228
+ const version = '1.1.0';
15229
15229
 
15230
15230
  /**
15231
15231
  * ProteusJS - Main library class
@@ -15357,7 +15357,7 @@ class ProteusJS {
15357
15357
  return this;
15358
15358
  }
15359
15359
  // Check browser support
15360
- if (!isSupported()) {
15360
+ if (!isSupported$1()) {
15361
15361
  logger.error('Browser not supported. Missing required APIs.');
15362
15362
  return this;
15363
15363
  }
@@ -15728,7 +15728,7 @@ class ProteusJS {
15728
15728
  * Check if browser is supported
15729
15729
  */
15730
15730
  static isSupported() {
15731
- return isSupported();
15731
+ return isSupported$1();
15732
15732
  }
15733
15733
  /**
15734
15734
  * Get or create global instance
@@ -15989,17 +15989,1550 @@ class ProteusJS {
15989
15989
  ProteusJS.instance = null;
15990
15990
 
15991
15991
  /**
15992
- * ProteusJS - Dynamic Responsive Design Library
15992
+ * @sc4rfurryx/proteusjs/transitions
15993
+ * View Transitions API wrapper with safe fallbacks
15994
+ *
15995
+ * @version 1.1.0
15996
+ * @author sc4rfurry
15997
+ * @license MIT
15998
+ */
15999
+ /**
16000
+ * One API for animating DOM state changes and cross-document navigations
16001
+ * using the View Transitions API with safe fallbacks.
16002
+ */
16003
+ async function transition(run, opts = {}) {
16004
+ const { name, duration = 300, onBefore, onAfter, allowInterrupt = true } = opts;
16005
+ // Check for View Transitions API support
16006
+ const hasViewTransitions = 'startViewTransition' in document;
16007
+ if (onBefore) {
16008
+ onBefore();
16009
+ }
16010
+ if (!hasViewTransitions) {
16011
+ // Fallback: run immediately without transitions
16012
+ try {
16013
+ await run();
16014
+ }
16015
+ finally {
16016
+ if (onAfter) {
16017
+ onAfter();
16018
+ }
16019
+ }
16020
+ return;
16021
+ }
16022
+ // Use native View Transitions API
16023
+ try {
16024
+ const viewTransition = document.startViewTransition(async () => {
16025
+ await run();
16026
+ });
16027
+ // Add CSS view-transition-name if name provided
16028
+ if (name) {
16029
+ const style = document.createElement('style');
16030
+ style.textContent = `
16031
+ ::view-transition-old(${name}),
16032
+ ::view-transition-new(${name}) {
16033
+ animation-duration: ${duration}ms;
16034
+ }
16035
+ `;
16036
+ document.head.appendChild(style);
16037
+ // Clean up style after transition
16038
+ viewTransition.finished.finally(() => {
16039
+ style.remove();
16040
+ });
16041
+ }
16042
+ await viewTransition.finished;
16043
+ }
16044
+ catch (error) {
16045
+ console.warn('View transition failed, falling back to immediate execution:', error);
16046
+ await run();
16047
+ }
16048
+ finally {
16049
+ if (onAfter) {
16050
+ onAfter();
16051
+ }
16052
+ }
16053
+ }
16054
+ /**
16055
+ * MPA-friendly navigation with view transitions when supported
16056
+ */
16057
+ async function navigate(url, opts = {}) {
16058
+ const { name, prerender = false } = opts;
16059
+ // Optional prerender hint (basic implementation)
16060
+ if (prerender && 'speculation' in HTMLScriptElement.prototype) {
16061
+ const script = document.createElement('script');
16062
+ script.type = 'speculationrules';
16063
+ script.textContent = JSON.stringify({
16064
+ prerender: [{ where: { href_matches: url } }]
16065
+ });
16066
+ document.head.appendChild(script);
16067
+ }
16068
+ // Check for View Transitions API support
16069
+ const hasViewTransitions = 'startViewTransition' in document;
16070
+ if (!hasViewTransitions) {
16071
+ // Fallback: normal navigation
16072
+ window.location.href = url;
16073
+ return;
16074
+ }
16075
+ try {
16076
+ // Use view transitions for navigation
16077
+ const viewTransition = document.startViewTransition(() => {
16078
+ window.location.href = url;
16079
+ });
16080
+ if (name) {
16081
+ const style = document.createElement('style');
16082
+ style.textContent = `
16083
+ ::view-transition-old(${name}),
16084
+ ::view-transition-new(${name}) {
16085
+ animation-duration: 300ms;
16086
+ }
16087
+ `;
16088
+ document.head.appendChild(style);
16089
+ }
16090
+ await viewTransition.finished;
16091
+ }
16092
+ catch (error) {
16093
+ console.warn('View transition navigation failed, falling back to normal navigation:', error);
16094
+ window.location.href = url;
16095
+ }
16096
+ }
16097
+ // Export default object for convenience
16098
+ var index$g = {
16099
+ transition,
16100
+ navigate
16101
+ };
16102
+
16103
+ var index$h = /*#__PURE__*/Object.freeze({
16104
+ __proto__: null,
16105
+ default: index$g,
16106
+ navigate: navigate,
16107
+ transition: transition
16108
+ });
16109
+
16110
+ /**
16111
+ * @sc4rfurryx/proteusjs/scroll
16112
+ * Scroll-driven animations with CSS Scroll-Linked Animations
16113
+ *
16114
+ * @version 1.1.0
16115
+ * @author sc4rfurry
16116
+ * @license MIT
16117
+ */
16118
+ /**
16119
+ * Zero-boilerplate setup for CSS Scroll-Linked Animations with fallbacks
16120
+ */
16121
+ function scrollAnimate(target, opts) {
16122
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16123
+ if (!targetEl) {
16124
+ throw new Error('Target element not found');
16125
+ }
16126
+ const { keyframes, range = ['0%', '100%'], timeline = {}, fallback = 'io' } = opts;
16127
+ const { axis = 'block', start = '0%', end = '100%' } = timeline;
16128
+ // Check for CSS Scroll-Linked Animations support
16129
+ const hasScrollTimeline = 'CSS' in window && CSS.supports('animation-timeline', 'scroll()');
16130
+ // Check for reduced motion preference
16131
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16132
+ if (prefersReducedMotion) {
16133
+ // Respect user preference - either disable or reduce animation
16134
+ if (fallback === false)
16135
+ return;
16136
+ // Apply only the end state for reduced motion
16137
+ const endKeyframe = keyframes[keyframes.length - 1];
16138
+ Object.assign(targetEl.style, endKeyframe);
16139
+ return;
16140
+ }
16141
+ if (hasScrollTimeline) {
16142
+ // Use native CSS Scroll-Linked Animations
16143
+ const timelineName = `scroll-timeline-${Math.random().toString(36).substr(2, 9)}`;
16144
+ // Create scroll timeline
16145
+ const style = document.createElement('style');
16146
+ style.textContent = `
16147
+ @scroll-timeline ${timelineName} {
16148
+ source: nearest;
16149
+ orientation: ${axis};
16150
+ scroll-offsets: ${start}, ${end};
16151
+ }
16152
+
16153
+ .scroll-animate-${timelineName} {
16154
+ animation-timeline: ${timelineName};
16155
+ animation-duration: 1ms; /* Required but ignored */
16156
+ animation-fill-mode: both;
16157
+ }
16158
+ `;
16159
+ document.head.appendChild(style);
16160
+ // Apply animation class
16161
+ targetEl.classList.add(`scroll-animate-${timelineName}`);
16162
+ // Create Web Animations API animation
16163
+ const animation = targetEl.animate(keyframes, {
16164
+ duration: 1, // Required but ignored with scroll timeline
16165
+ fill: 'both'
16166
+ });
16167
+ // Set scroll timeline (when supported)
16168
+ if ('timeline' in animation) {
16169
+ animation.timeline = new window.ScrollTimeline({
16170
+ source: document.scrollingElement,
16171
+ orientation: axis,
16172
+ scrollOffsets: [
16173
+ { target: targetEl, edge: 'start', threshold: parseFloat(start) / 100 },
16174
+ { target: targetEl, edge: 'end', threshold: parseFloat(end) / 100 }
16175
+ ]
16176
+ });
16177
+ }
16178
+ }
16179
+ else if (fallback === 'io') {
16180
+ // Fallback using Intersection Observer
16181
+ let animation = null;
16182
+ const observer = new IntersectionObserver((entries) => {
16183
+ entries.forEach(entry => {
16184
+ const progress = Math.max(0, Math.min(1, entry.intersectionRatio));
16185
+ if (!animation) {
16186
+ animation = targetEl.animate(keyframes, {
16187
+ duration: 1000,
16188
+ fill: 'both'
16189
+ });
16190
+ animation.pause();
16191
+ }
16192
+ // Update animation progress based on intersection
16193
+ animation.currentTime = progress * 1000;
16194
+ });
16195
+ }, {
16196
+ threshold: Array.from({ length: 101 }, (_, i) => i / 100) // 0 to 1 in 0.01 steps
16197
+ });
16198
+ observer.observe(targetEl);
16199
+ // Store cleanup function
16200
+ targetEl._scrollAnimateCleanup = () => {
16201
+ observer.disconnect();
16202
+ if (animation) {
16203
+ animation.cancel();
16204
+ }
16205
+ };
16206
+ }
16207
+ }
16208
+ /**
16209
+ * Create a scroll-triggered animation that plays once when element enters viewport
16210
+ */
16211
+ function scrollTrigger(target, keyframes, options = {}) {
16212
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16213
+ if (!targetEl) {
16214
+ throw new Error('Target element not found');
16215
+ }
16216
+ // Check for reduced motion preference
16217
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16218
+ if (prefersReducedMotion) {
16219
+ // Apply end state immediately
16220
+ const endKeyframe = keyframes[keyframes.length - 1];
16221
+ Object.assign(targetEl.style, endKeyframe);
16222
+ return;
16223
+ }
16224
+ const observer = new IntersectionObserver((entries) => {
16225
+ entries.forEach(entry => {
16226
+ if (entry.isIntersecting) {
16227
+ // Play animation
16228
+ targetEl.animate(keyframes, {
16229
+ duration: 600,
16230
+ easing: 'ease-out',
16231
+ fill: 'forwards',
16232
+ ...options
16233
+ });
16234
+ // Disconnect observer after first trigger
16235
+ observer.disconnect();
16236
+ }
16237
+ });
16238
+ }, {
16239
+ threshold: 0.1,
16240
+ rootMargin: '0px 0px -10% 0px'
16241
+ });
16242
+ observer.observe(targetEl);
16243
+ }
16244
+ /**
16245
+ * Parallax effect using scroll-driven animations
16246
+ */
16247
+ function parallax(target, speed = 0.5) {
16248
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16249
+ if (!targetEl) {
16250
+ throw new Error('Target element not found');
16251
+ }
16252
+ // Check for reduced motion preference
16253
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16254
+ if (prefersReducedMotion)
16255
+ return;
16256
+ const keyframes = [
16257
+ { transform: `translateY(${ -100 * speed}px)` },
16258
+ { transform: `translateY(${100 * speed}px)` }
16259
+ ];
16260
+ scrollAnimate(targetEl, {
16261
+ keyframes,
16262
+ range: ['0%', '100%'],
16263
+ timeline: { axis: 'block' },
16264
+ fallback: 'io'
16265
+ });
16266
+ }
16267
+ /**
16268
+ * Cleanup function to remove scroll animations
16269
+ */
16270
+ function cleanup$2(target) {
16271
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16272
+ if (!targetEl)
16273
+ return;
16274
+ // Call stored cleanup function if it exists
16275
+ if (targetEl._scrollAnimateCleanup) {
16276
+ targetEl._scrollAnimateCleanup();
16277
+ delete targetEl._scrollAnimateCleanup;
16278
+ }
16279
+ // Remove animation classes
16280
+ targetEl.classList.forEach(className => {
16281
+ if (className.startsWith('scroll-animate-')) {
16282
+ targetEl.classList.remove(className);
16283
+ }
16284
+ });
16285
+ // Cancel any running animations
16286
+ const animations = targetEl.getAnimations();
16287
+ animations.forEach(animation => animation.cancel());
16288
+ }
16289
+ // Export default object for convenience
16290
+ var index$e = {
16291
+ scrollAnimate,
16292
+ scrollTrigger,
16293
+ parallax,
16294
+ cleanup: cleanup$2
16295
+ };
16296
+
16297
+ var index$f = /*#__PURE__*/Object.freeze({
16298
+ __proto__: null,
16299
+ cleanup: cleanup$2,
16300
+ default: index$e,
16301
+ parallax: parallax,
16302
+ scrollAnimate: scrollAnimate,
16303
+ scrollTrigger: scrollTrigger
16304
+ });
16305
+
16306
+ /**
16307
+ * @sc4rfurryx/proteusjs/anchor
16308
+ * CSS Anchor Positioning utilities with robust JS fallback
16309
+ *
16310
+ * @version 1.1.0
16311
+ * @author sc4rfurry
16312
+ * @license MIT
16313
+ */
16314
+ /**
16315
+ * Declarative tethers (tooltips, callouts) via CSS Anchor Positioning when available;
16316
+ * robust JS fallback with flip/collision detection
16317
+ */
16318
+ function tether(floating, opts) {
16319
+ const floatingEl = typeof floating === 'string' ? document.querySelector(floating) : floating;
16320
+ const anchorEl = typeof opts.anchor === 'string' ? document.querySelector(opts.anchor) : opts.anchor;
16321
+ if (!floatingEl || !anchorEl) {
16322
+ throw new Error('Both floating and anchor elements must exist');
16323
+ }
16324
+ const { placement = 'bottom', align = 'center', offset = 8, strategy = 'absolute' } = opts;
16325
+ // Check for CSS Anchor Positioning support
16326
+ const hasAnchorPositioning = CSS.supports('anchor-name', 'test');
16327
+ let isDestroyed = false;
16328
+ let resizeObserver = null;
16329
+ const setupCSSAnchorPositioning = () => {
16330
+ if (!hasAnchorPositioning)
16331
+ return false;
16332
+ // Generate unique anchor name
16333
+ const anchorName = `anchor-${Math.random().toString(36).substring(2, 11)}`;
16334
+ // Set anchor name on anchor element
16335
+ anchorEl.style.setProperty('anchor-name', anchorName);
16336
+ // Position floating element using CSS anchor positioning
16337
+ const floatingStyle = floatingEl;
16338
+ floatingStyle.style.position = strategy;
16339
+ floatingStyle.style.setProperty('position-anchor', anchorName);
16340
+ // Set position based on placement
16341
+ switch (placement) {
16342
+ case 'top':
16343
+ floatingStyle.style.bottom = `anchor(bottom, ${offset}px)`;
16344
+ break;
16345
+ case 'bottom':
16346
+ floatingStyle.style.top = `anchor(bottom, ${offset}px)`;
16347
+ break;
16348
+ case 'left':
16349
+ floatingStyle.style.right = `anchor(left, ${offset}px)`;
16350
+ break;
16351
+ case 'right':
16352
+ floatingStyle.style.left = `anchor(right, ${offset}px)`;
16353
+ break;
16354
+ }
16355
+ // Set alignment
16356
+ if (placement === 'top' || placement === 'bottom') {
16357
+ switch (align) {
16358
+ case 'start':
16359
+ floatingStyle.style.left = 'anchor(left)';
16360
+ break;
16361
+ case 'center':
16362
+ floatingStyle.style.left = 'anchor(center)';
16363
+ floatingStyle.style.transform = 'translateX(-50%)';
16364
+ break;
16365
+ case 'end':
16366
+ floatingStyle.style.right = 'anchor(right)';
16367
+ break;
16368
+ }
16369
+ }
16370
+ else {
16371
+ switch (align) {
16372
+ case 'start':
16373
+ floatingStyle.style.top = 'anchor(top)';
16374
+ break;
16375
+ case 'center':
16376
+ floatingStyle.style.top = 'anchor(center)';
16377
+ floatingStyle.style.transform = 'translateY(-50%)';
16378
+ break;
16379
+ case 'end':
16380
+ floatingStyle.style.bottom = 'anchor(bottom)';
16381
+ break;
16382
+ }
16383
+ }
16384
+ return true;
16385
+ };
16386
+ const calculatePosition = () => {
16387
+ const anchorRect = anchorEl.getBoundingClientRect();
16388
+ const floatingRect = floatingEl.getBoundingClientRect();
16389
+ const viewport = {
16390
+ width: window.innerWidth,
16391
+ height: window.innerHeight
16392
+ };
16393
+ let finalPlacement = placement;
16394
+ let x = 0;
16395
+ let y = 0;
16396
+ // Calculate base position
16397
+ switch (finalPlacement) {
16398
+ case 'top':
16399
+ x = anchorRect.left;
16400
+ y = anchorRect.top - floatingRect.height - offset;
16401
+ break;
16402
+ case 'bottom':
16403
+ x = anchorRect.left;
16404
+ y = anchorRect.bottom + offset;
16405
+ break;
16406
+ case 'left':
16407
+ x = anchorRect.left - floatingRect.width - offset;
16408
+ y = anchorRect.top;
16409
+ break;
16410
+ case 'right':
16411
+ x = anchorRect.right + offset;
16412
+ y = anchorRect.top;
16413
+ break;
16414
+ case 'auto': {
16415
+ // Choose best placement based on available space
16416
+ const spaces = {
16417
+ top: anchorRect.top,
16418
+ bottom: viewport.height - anchorRect.bottom,
16419
+ left: anchorRect.left,
16420
+ right: viewport.width - anchorRect.right
16421
+ };
16422
+ const bestPlacement = Object.entries(spaces).reduce((a, b) => spaces[a[0]] > spaces[b[0]] ? a : b)[0];
16423
+ finalPlacement = bestPlacement;
16424
+ return calculatePosition(); // Recursive call with determined placement
16425
+ }
16426
+ }
16427
+ // Apply alignment
16428
+ if (finalPlacement === 'top' || finalPlacement === 'bottom') {
16429
+ switch (align) {
16430
+ case 'start':
16431
+ // x already set correctly
16432
+ break;
16433
+ case 'center':
16434
+ x = anchorRect.left + (anchorRect.width - floatingRect.width) / 2;
16435
+ break;
16436
+ case 'end':
16437
+ x = anchorRect.right - floatingRect.width;
16438
+ break;
16439
+ }
16440
+ }
16441
+ else {
16442
+ switch (align) {
16443
+ case 'start':
16444
+ // y already set correctly
16445
+ break;
16446
+ case 'center':
16447
+ y = anchorRect.top + (anchorRect.height - floatingRect.height) / 2;
16448
+ break;
16449
+ case 'end':
16450
+ y = anchorRect.bottom - floatingRect.height;
16451
+ break;
16452
+ }
16453
+ }
16454
+ // Collision detection and adjustment
16455
+ if (x < 0)
16456
+ x = 8;
16457
+ if (y < 0)
16458
+ y = 8;
16459
+ if (x + floatingRect.width > viewport.width) {
16460
+ x = viewport.width - floatingRect.width - 8;
16461
+ }
16462
+ if (y + floatingRect.height > viewport.height) {
16463
+ y = viewport.height - floatingRect.height - 8;
16464
+ }
16465
+ return { x, y };
16466
+ };
16467
+ const updatePosition = () => {
16468
+ if (isDestroyed)
16469
+ return;
16470
+ if (!hasAnchorPositioning) {
16471
+ const { x, y } = calculatePosition();
16472
+ const floatingStyle = floatingEl;
16473
+ floatingStyle.style.position = strategy;
16474
+ floatingStyle.style.left = `${x}px`;
16475
+ floatingStyle.style.top = `${y}px`;
16476
+ }
16477
+ };
16478
+ const setupJSFallback = () => {
16479
+ updatePosition();
16480
+ // Set up observers for position updates
16481
+ resizeObserver = new ResizeObserver(updatePosition);
16482
+ resizeObserver.observe(anchorEl);
16483
+ resizeObserver.observe(floatingEl);
16484
+ window.addEventListener('scroll', updatePosition, { passive: true });
16485
+ window.addEventListener('resize', updatePosition, { passive: true });
16486
+ };
16487
+ const destroy = () => {
16488
+ isDestroyed = true;
16489
+ if (resizeObserver) {
16490
+ resizeObserver.disconnect();
16491
+ resizeObserver = null;
16492
+ }
16493
+ window.removeEventListener('scroll', updatePosition);
16494
+ window.removeEventListener('resize', updatePosition);
16495
+ // Clean up CSS anchor positioning
16496
+ if (hasAnchorPositioning) {
16497
+ anchorEl.style.removeProperty('anchor-name');
16498
+ const floatingStyle = floatingEl;
16499
+ floatingStyle.style.removeProperty('position-anchor');
16500
+ floatingStyle.style.position = '';
16501
+ }
16502
+ };
16503
+ // Initialize
16504
+ if (!setupCSSAnchorPositioning()) {
16505
+ setupJSFallback();
16506
+ }
16507
+ return {
16508
+ update: updatePosition,
16509
+ destroy
16510
+ };
16511
+ }
16512
+ // Export default object for convenience
16513
+ var index$c = {
16514
+ tether
16515
+ };
16516
+
16517
+ var index$d = /*#__PURE__*/Object.freeze({
16518
+ __proto__: null,
16519
+ default: index$c,
16520
+ tether: tether
16521
+ });
16522
+
16523
+ /**
16524
+ * @sc4rfurryx/proteusjs/popover
16525
+ * HTML Popover API wrapper with robust focus/inert handling
16526
+ *
16527
+ * @version 1.1.0
16528
+ * @author sc4rfurry
16529
+ * @license MIT
16530
+ */
16531
+ /**
16532
+ * Unified API for menus, tooltips, and dialogs using the native Popover API
16533
+ * with robust focus/inert handling
16534
+ */
16535
+ function attach(trigger, panel, opts = {}) {
16536
+ const triggerEl = typeof trigger === 'string' ? document.querySelector(trigger) : trigger;
16537
+ const panelEl = typeof panel === 'string' ? document.querySelector(panel) : panel;
16538
+ if (!triggerEl || !panelEl) {
16539
+ throw new Error('Both trigger and panel elements must exist');
16540
+ }
16541
+ const { type = 'menu', trapFocus = type === 'dialog', restoreFocus = true, closeOnEscape = true, onOpen, onClose } = opts;
16542
+ let isOpen = false;
16543
+ let previousFocus = null;
16544
+ let focusTrap = null;
16545
+ // Check for native Popover API support
16546
+ const hasPopoverAPI = 'popover' in HTMLElement.prototype;
16547
+ // Set up ARIA attributes
16548
+ const setupAria = () => {
16549
+ const panelId = panelEl.id || `popover-${Math.random().toString(36).substr(2, 9)}`;
16550
+ panelEl.id = panelId;
16551
+ triggerEl.setAttribute('aria-expanded', 'false');
16552
+ triggerEl.setAttribute('aria-controls', panelId);
16553
+ if (type === 'menu') {
16554
+ triggerEl.setAttribute('aria-haspopup', 'menu');
16555
+ panelEl.setAttribute('role', 'menu');
16556
+ }
16557
+ else if (type === 'dialog') {
16558
+ triggerEl.setAttribute('aria-haspopup', 'dialog');
16559
+ panelEl.setAttribute('role', 'dialog');
16560
+ panelEl.setAttribute('aria-modal', 'true');
16561
+ }
16562
+ else if (type === 'tooltip') {
16563
+ triggerEl.setAttribute('aria-describedby', panelId);
16564
+ panelEl.setAttribute('role', 'tooltip');
16565
+ }
16566
+ };
16567
+ // Set up native popover if supported
16568
+ const setupNativePopover = () => {
16569
+ if (hasPopoverAPI) {
16570
+ panelEl.popover = type === 'dialog' ? 'manual' : 'auto';
16571
+ triggerEl.setAttribute('popovertarget', panelEl.id);
16572
+ }
16573
+ };
16574
+ // Focus trap implementation
16575
+ class FocusTrap {
16576
+ constructor(container) {
16577
+ this.container = container;
16578
+ this.focusableElements = [];
16579
+ this.handleKeyDown = (e) => {
16580
+ if (e.key !== 'Tab')
16581
+ return;
16582
+ const firstElement = this.focusableElements[0];
16583
+ const lastElement = this.focusableElements[this.focusableElements.length - 1];
16584
+ if (e.shiftKey) {
16585
+ if (document.activeElement === firstElement) {
16586
+ e.preventDefault();
16587
+ lastElement.focus();
16588
+ }
16589
+ }
16590
+ else {
16591
+ if (document.activeElement === lastElement) {
16592
+ e.preventDefault();
16593
+ firstElement.focus();
16594
+ }
16595
+ }
16596
+ };
16597
+ this.updateFocusableElements();
16598
+ }
16599
+ updateFocusableElements() {
16600
+ const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
16601
+ this.focusableElements = Array.from(this.container.querySelectorAll(selector));
16602
+ }
16603
+ activate() {
16604
+ this.updateFocusableElements();
16605
+ if (this.focusableElements.length > 0) {
16606
+ this.focusableElements[0].focus();
16607
+ }
16608
+ document.addEventListener('keydown', this.handleKeyDown);
16609
+ }
16610
+ deactivate() {
16611
+ document.removeEventListener('keydown', this.handleKeyDown);
16612
+ }
16613
+ }
16614
+ const open = () => {
16615
+ if (isOpen)
16616
+ return;
16617
+ if (restoreFocus) {
16618
+ previousFocus = document.activeElement;
16619
+ }
16620
+ if (hasPopoverAPI) {
16621
+ panelEl.showPopover();
16622
+ }
16623
+ else {
16624
+ panelEl.style.display = 'block';
16625
+ panelEl.setAttribute('data-popover-open', 'true');
16626
+ }
16627
+ triggerEl.setAttribute('aria-expanded', 'true');
16628
+ isOpen = true;
16629
+ if (trapFocus) {
16630
+ focusTrap = new FocusTrap(panelEl);
16631
+ focusTrap.activate();
16632
+ }
16633
+ if (onOpen) {
16634
+ onOpen();
16635
+ }
16636
+ };
16637
+ const close = () => {
16638
+ if (!isOpen)
16639
+ return;
16640
+ if (hasPopoverAPI) {
16641
+ panelEl.hidePopover();
16642
+ }
16643
+ else {
16644
+ panelEl.style.display = 'none';
16645
+ panelEl.removeAttribute('data-popover-open');
16646
+ }
16647
+ triggerEl.setAttribute('aria-expanded', 'false');
16648
+ isOpen = false;
16649
+ if (focusTrap) {
16650
+ focusTrap.deactivate();
16651
+ focusTrap = null;
16652
+ }
16653
+ if (restoreFocus && previousFocus) {
16654
+ previousFocus.focus();
16655
+ previousFocus = null;
16656
+ }
16657
+ if (onClose) {
16658
+ onClose();
16659
+ }
16660
+ };
16661
+ const toggle = () => {
16662
+ if (isOpen) {
16663
+ close();
16664
+ }
16665
+ else {
16666
+ open();
16667
+ }
16668
+ };
16669
+ const handleKeyDown = (e) => {
16670
+ if (closeOnEscape && e.key === 'Escape' && isOpen) {
16671
+ e.preventDefault();
16672
+ close();
16673
+ }
16674
+ };
16675
+ const handleClick = (e) => {
16676
+ e.preventDefault();
16677
+ toggle();
16678
+ };
16679
+ const destroy = () => {
16680
+ triggerEl.removeEventListener('click', handleClick);
16681
+ document.removeEventListener('keydown', handleKeyDown);
16682
+ if (focusTrap) {
16683
+ focusTrap.deactivate();
16684
+ }
16685
+ if (isOpen) {
16686
+ close();
16687
+ }
16688
+ };
16689
+ // Initialize
16690
+ setupAria();
16691
+ setupNativePopover();
16692
+ triggerEl.addEventListener('click', handleClick);
16693
+ document.addEventListener('keydown', handleKeyDown);
16694
+ return {
16695
+ open,
16696
+ close,
16697
+ toggle,
16698
+ destroy
16699
+ };
16700
+ }
16701
+ // Export default object for convenience
16702
+ var index$a = {
16703
+ attach
16704
+ };
16705
+
16706
+ var index$b = /*#__PURE__*/Object.freeze({
16707
+ __proto__: null,
16708
+ attach: attach,
16709
+ default: index$a
16710
+ });
16711
+
16712
+ /**
16713
+ * @sc4rfurryx/proteusjs/container
16714
+ * Container/Style Query helpers with visualization devtools
16715
+ *
16716
+ * @version 1.1.0
16717
+ * @author sc4rfurry
16718
+ * @license MIT
16719
+ */
16720
+ /**
16721
+ * Sugar on native container queries with dev visualization
16722
+ */
16723
+ function defineContainer(target, name, opts = {}) {
16724
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16725
+ if (!targetEl) {
16726
+ throw new Error('Target element not found');
16727
+ }
16728
+ const { type = 'size', inlineSize: _inlineSize = true } = opts;
16729
+ const containerName = name || `container-${Math.random().toString(36).substring(2, 11)}`;
16730
+ // Apply container properties
16731
+ const element = targetEl;
16732
+ element.style.containerName = containerName;
16733
+ element.style.containerType = type;
16734
+ // Warn if containment settings are missing
16735
+ const computedStyle = getComputedStyle(element);
16736
+ if (!computedStyle.contain || computedStyle.contain === 'none') {
16737
+ console.warn(`Container "${containerName}" may need explicit containment settings for optimal performance`);
16738
+ }
16739
+ // Dev overlay (only in development)
16740
+ if (process.env['NODE_ENV'] === 'development' || window.__PROTEUS_DEV__) {
16741
+ createDevOverlay(element, containerName);
16742
+ }
16743
+ }
16744
+ /**
16745
+ * Create development overlay showing container bounds and breakpoints
16746
+ */
16747
+ function createDevOverlay(element, name) {
16748
+ const overlay = document.createElement('div');
16749
+ overlay.className = 'proteus-container-overlay';
16750
+ overlay.style.cssText = `
16751
+ position: absolute;
16752
+ top: 0;
16753
+ left: 0;
16754
+ right: 0;
16755
+ bottom: 0;
16756
+ pointer-events: none;
16757
+ border: 2px dashed rgba(255, 0, 255, 0.5);
16758
+ background: rgba(255, 0, 255, 0.05);
16759
+ z-index: 9999;
16760
+ font-family: monospace;
16761
+ font-size: 12px;
16762
+ color: #ff00ff;
16763
+ `;
16764
+ const label = document.createElement('div');
16765
+ label.style.cssText = `
16766
+ position: absolute;
16767
+ top: -20px;
16768
+ left: 0;
16769
+ background: rgba(255, 0, 255, 0.9);
16770
+ color: white;
16771
+ padding: 2px 6px;
16772
+ border-radius: 3px;
16773
+ font-size: 10px;
16774
+ white-space: nowrap;
16775
+ `;
16776
+ label.textContent = `Container: ${name}`;
16777
+ const sizeInfo = document.createElement('div');
16778
+ sizeInfo.style.cssText = `
16779
+ position: absolute;
16780
+ bottom: 2px;
16781
+ right: 2px;
16782
+ background: rgba(0, 0, 0, 0.7);
16783
+ color: white;
16784
+ padding: 2px 4px;
16785
+ border-radius: 2px;
16786
+ font-size: 10px;
16787
+ `;
16788
+ overlay.appendChild(label);
16789
+ overlay.appendChild(sizeInfo);
16790
+ // Position overlay relative to container
16791
+ if (getComputedStyle(element).position === 'static') {
16792
+ element.style.position = 'relative';
16793
+ }
16794
+ element.appendChild(overlay);
16795
+ // Update size info
16796
+ const updateSizeInfo = () => {
16797
+ const rect = element.getBoundingClientRect();
16798
+ sizeInfo.textContent = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
16799
+ };
16800
+ updateSizeInfo();
16801
+ // Update on resize
16802
+ if ('ResizeObserver' in window) {
16803
+ const resizeObserver = new ResizeObserver(updateSizeInfo);
16804
+ resizeObserver.observe(element);
16805
+ }
16806
+ // Store cleanup function
16807
+ element._proteusContainerCleanup = () => {
16808
+ overlay.remove();
16809
+ };
16810
+ }
16811
+ /**
16812
+ * Helper to create container query CSS rules
16813
+ */
16814
+ function createContainerQuery(containerName, condition, styles) {
16815
+ const cssRules = Object.entries(styles)
16816
+ .map(([property, value]) => ` ${property}: ${value};`)
16817
+ .join('\n');
16818
+ return `@container ${containerName} (${condition}) {\n${cssRules}\n}`;
16819
+ }
16820
+ /**
16821
+ * Apply container query styles dynamically
16822
+ */
16823
+ function applyContainerQuery(containerName, condition, styles) {
16824
+ const css = createContainerQuery(containerName, condition, styles);
16825
+ const styleElement = document.createElement('style');
16826
+ styleElement.textContent = css;
16827
+ styleElement.setAttribute('data-proteus-container', containerName);
16828
+ document.head.appendChild(styleElement);
16829
+ }
16830
+ /**
16831
+ * Remove container query styles
16832
+ */
16833
+ function removeContainerQuery(containerName) {
16834
+ const styleElements = document.querySelectorAll(`style[data-proteus-container="${containerName}"]`);
16835
+ styleElements.forEach(element => element.remove());
16836
+ }
16837
+ /**
16838
+ * Get container size information
16839
+ */
16840
+ function getContainerSize(target) {
16841
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16842
+ if (!targetEl) {
16843
+ throw new Error('Target element not found');
16844
+ }
16845
+ const rect = targetEl.getBoundingClientRect();
16846
+ return {
16847
+ width: rect.width,
16848
+ height: rect.height
16849
+ };
16850
+ }
16851
+ /**
16852
+ * Check if container queries are supported
16853
+ */
16854
+ function isSupported() {
16855
+ return CSS.supports('container-type', 'size');
16856
+ }
16857
+ /**
16858
+ * Cleanup container overlays and observers
16859
+ */
16860
+ function cleanup$1(target) {
16861
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16862
+ if (!targetEl)
16863
+ return;
16864
+ // Call stored cleanup function if it exists
16865
+ const elementWithCleanup = targetEl;
16866
+ if (elementWithCleanup._proteusContainerCleanup) {
16867
+ elementWithCleanup._proteusContainerCleanup();
16868
+ delete elementWithCleanup._proteusContainerCleanup;
16869
+ }
16870
+ }
16871
+ /**
16872
+ * Toggle dev overlay visibility
16873
+ */
16874
+ function toggleDevOverlay(visible) {
16875
+ const overlays = document.querySelectorAll('.proteus-container-overlay');
16876
+ overlays.forEach(overlay => {
16877
+ const element = overlay;
16878
+ if (visible !== undefined) {
16879
+ element.style.display = visible ? 'block' : 'none';
16880
+ }
16881
+ else {
16882
+ element.style.display = element.style.display === 'none' ? 'block' : 'none';
16883
+ }
16884
+ });
16885
+ }
16886
+ // Export default object for convenience
16887
+ var index$8 = {
16888
+ defineContainer,
16889
+ createContainerQuery,
16890
+ applyContainerQuery,
16891
+ removeContainerQuery,
16892
+ getContainerSize,
16893
+ isSupported,
16894
+ cleanup: cleanup$1,
16895
+ toggleDevOverlay
16896
+ };
16897
+
16898
+ var index$9 = /*#__PURE__*/Object.freeze({
16899
+ __proto__: null,
16900
+ applyContainerQuery: applyContainerQuery,
16901
+ cleanup: cleanup$1,
16902
+ createContainerQuery: createContainerQuery,
16903
+ default: index$8,
16904
+ defineContainer: defineContainer,
16905
+ getContainerSize: getContainerSize,
16906
+ isSupported: isSupported,
16907
+ removeContainerQuery: removeContainerQuery,
16908
+ toggleDevOverlay: toggleDevOverlay
16909
+ });
16910
+
16911
+ /**
16912
+ * @sc4rfurryx/proteusjs/typography
16913
+ * Fluid typography with CSS-first approach
16914
+ *
16915
+ * @version 1.1.0
16916
+ * @author sc4rfurry
16917
+ * @license MIT
16918
+ */
16919
+ /**
16920
+ * Generate pure-CSS clamp() rules for fluid typography
16921
+ */
16922
+ function fluidType(minRem, maxRem, options = {}) {
16923
+ const { minViewportPx = 320, maxViewportPx = 1200, lineHeight, containerUnits = false } = options;
16924
+ // Convert rem to px for calculations (assuming 16px base)
16925
+ const minPx = minRem * 16;
16926
+ const maxPx = maxRem * 16;
16927
+ // Calculate slope and y-intercept for linear interpolation
16928
+ const slope = (maxPx - minPx) / (maxViewportPx - minViewportPx);
16929
+ const yIntercept = minPx - slope * minViewportPx;
16930
+ // Generate clamp() function
16931
+ const viewportUnit = containerUnits ? 'cqw' : 'vw';
16932
+ const clampValue = `clamp(${minRem}rem, ${yIntercept / 16}rem + ${slope * 100}${viewportUnit}, ${maxRem}rem)`;
16933
+ let css = `font-size: ${clampValue};`;
16934
+ // Add line-height if specified
16935
+ if (lineHeight) {
16936
+ css += `\nline-height: ${lineHeight};`;
16937
+ }
16938
+ return { css };
16939
+ }
16940
+ /**
16941
+ * Apply fluid typography to elements
16942
+ */
16943
+ function applyFluidType(selector, minRem, maxRem, options = {}) {
16944
+ const { css } = fluidType(minRem, maxRem, options);
16945
+ const styleElement = document.createElement('style');
16946
+ styleElement.textContent = `${selector} {\n ${css.replace(/\n/g, '\n ')}\n}`;
16947
+ styleElement.setAttribute('data-proteus-typography', selector);
16948
+ document.head.appendChild(styleElement);
16949
+ }
16950
+ /**
16951
+ * Create a complete typographic scale
16952
+ */
16953
+ function createTypographicScale(baseSize = 1, ratio = 1.25, steps = 6, options = {}) {
16954
+ const scale = {};
16955
+ for (let i = -2; i <= steps - 3; i++) {
16956
+ const size = baseSize * Math.pow(ratio, i);
16957
+ const minSize = size * 0.8; // 20% smaller at min viewport
16958
+ const maxSize = size * 1.2; // 20% larger at max viewport
16959
+ const stepName = i <= 0 ? `small${Math.abs(i)}` : `large${i}`;
16960
+ scale[stepName] = fluidType(minSize, maxSize, options);
16961
+ }
16962
+ return scale;
16963
+ }
16964
+ /**
16965
+ * Generate CSS custom properties for a typographic scale
16966
+ */
16967
+ function generateScaleCSS(scale, prefix = '--font-size') {
16968
+ const cssVars = Object.entries(scale)
16969
+ .map(([name, result]) => ` ${prefix}-${name}: ${result.css.replace('font-size: ', '').replace(';', '')};`)
16970
+ .join('\n');
16971
+ return `:root {\n${cssVars}\n}`;
16972
+ }
16973
+ /**
16974
+ * Optimize line height for readability
16975
+ */
16976
+ function optimizeLineHeight(fontSize, measure = 65) {
16977
+ // Optimal line height based on font size and measure (characters per line)
16978
+ // Smaller fonts need more line height, larger fonts need less
16979
+ const baseLineHeight = 1.4;
16980
+ const sizeAdjustment = Math.max(0.1, Math.min(0.3, (1 - fontSize) * 0.5));
16981
+ const measureAdjustment = Math.max(-0.1, Math.min(0.1, (65 - measure) * 0.002));
16982
+ return baseLineHeight + sizeAdjustment + measureAdjustment;
16983
+ }
16984
+ /**
16985
+ * Calculate optimal font size for container width
16986
+ */
16987
+ function calculateOptimalSize(containerWidth, targetCharacters = 65, baseCharWidth = 0.5) {
16988
+ // Calculate font size to achieve target characters per line
16989
+ const optimalFontSize = containerWidth / (targetCharacters * baseCharWidth);
16990
+ // Clamp to reasonable bounds (12px to 24px)
16991
+ return Math.max(0.75, Math.min(1.5, optimalFontSize));
16992
+ }
16993
+ /**
16994
+ * Apply responsive typography to an element
16995
+ */
16996
+ function makeResponsive(target, options = {}) {
16997
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16998
+ if (!targetEl) {
16999
+ throw new Error('Target element not found');
17000
+ }
17001
+ const { minSize = 0.875, maxSize = 1.25, targetCharacters = 65, autoLineHeight = true } = options;
17002
+ // Apply fluid typography
17003
+ const { css } = fluidType(minSize, maxSize);
17004
+ const element = targetEl;
17005
+ // Parse and apply CSS
17006
+ const styles = css.split(';').filter(Boolean);
17007
+ styles.forEach(style => {
17008
+ const [property, value] = style.split(':').map(s => s.trim());
17009
+ if (property && value) {
17010
+ element.style.setProperty(property, value);
17011
+ }
17012
+ });
17013
+ // Auto line height if enabled
17014
+ if (autoLineHeight) {
17015
+ const updateLineHeight = () => {
17016
+ const computedStyle = getComputedStyle(element);
17017
+ const fontSize = parseFloat(computedStyle.fontSize);
17018
+ const containerWidth = element.getBoundingClientRect().width;
17019
+ const charactersPerLine = containerWidth / (fontSize * 0.5);
17020
+ const optimalLineHeight = optimizeLineHeight(fontSize / 16, charactersPerLine);
17021
+ element.style.lineHeight = optimalLineHeight.toString();
17022
+ };
17023
+ updateLineHeight();
17024
+ // Update on resize
17025
+ if ('ResizeObserver' in window) {
17026
+ const resizeObserver = new ResizeObserver(updateLineHeight);
17027
+ resizeObserver.observe(element);
17028
+ // Store cleanup function
17029
+ element._proteusTypographyCleanup = () => {
17030
+ resizeObserver.disconnect();
17031
+ };
17032
+ }
17033
+ }
17034
+ }
17035
+ /**
17036
+ * Remove applied typography styles
17037
+ */
17038
+ function cleanup(target) {
17039
+ if (target) {
17040
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
17041
+ if (targetEl && targetEl._proteusTypographyCleanup) {
17042
+ targetEl._proteusTypographyCleanup();
17043
+ delete targetEl._proteusTypographyCleanup;
17044
+ }
17045
+ }
17046
+ else {
17047
+ // Remove all typography style elements
17048
+ const styleElements = document.querySelectorAll('style[data-proteus-typography]');
17049
+ styleElements.forEach(element => element.remove());
17050
+ }
17051
+ }
17052
+ /**
17053
+ * Check if container query units are supported
17054
+ */
17055
+ function supportsContainerUnits() {
17056
+ return CSS.supports('width', '1cqw');
17057
+ }
17058
+ // Export default object for convenience
17059
+ var index$6 = {
17060
+ fluidType,
17061
+ applyFluidType,
17062
+ createTypographicScale,
17063
+ generateScaleCSS,
17064
+ optimizeLineHeight,
17065
+ calculateOptimalSize,
17066
+ makeResponsive,
17067
+ cleanup,
17068
+ supportsContainerUnits
17069
+ };
17070
+
17071
+ var index$7 = /*#__PURE__*/Object.freeze({
17072
+ __proto__: null,
17073
+ applyFluidType: applyFluidType,
17074
+ calculateOptimalSize: calculateOptimalSize,
17075
+ cleanup: cleanup,
17076
+ createTypographicScale: createTypographicScale,
17077
+ default: index$6,
17078
+ fluidType: fluidType,
17079
+ generateScaleCSS: generateScaleCSS,
17080
+ makeResponsive: makeResponsive,
17081
+ optimizeLineHeight: optimizeLineHeight,
17082
+ supportsContainerUnits: supportsContainerUnits
17083
+ });
17084
+
17085
+ /**
17086
+ * @sc4rfurryx/proteusjs/a11y-audit
17087
+ * Lightweight accessibility audits for development
17088
+ *
17089
+ * @version 1.1.0
17090
+ * @author sc4rfurry
17091
+ * @license MIT
17092
+ */
17093
+ async function audit(target = document, options = {}) {
17094
+ if (typeof window === 'undefined' || process.env['NODE_ENV'] === 'production') {
17095
+ return { violations: [], passes: 0, timestamp: Date.now(), url: '' };
17096
+ }
17097
+ const { rules = ['images', 'headings', 'forms'], format = 'console' } = options;
17098
+ const violations = [];
17099
+ let passes = 0;
17100
+ if (rules.includes('images')) {
17101
+ const imgs = target.querySelectorAll('img:not([alt])');
17102
+ if (imgs.length > 0) {
17103
+ violations.push({
17104
+ id: 'image-alt', impact: 'critical', nodes: imgs.length, help: 'Images need alt text'
17105
+ });
17106
+ }
17107
+ passes += target.querySelectorAll('img[alt]').length;
17108
+ }
17109
+ if (rules.includes('headings')) {
17110
+ const h1s = target.querySelectorAll('h1');
17111
+ if (h1s.length !== 1) {
17112
+ violations.push({
17113
+ id: 'heading-structure', impact: 'moderate', nodes: h1s.length, help: 'Page should have exactly one h1'
17114
+ });
17115
+ }
17116
+ else
17117
+ passes++;
17118
+ }
17119
+ if (rules.includes('forms')) {
17120
+ const unlabeled = target.querySelectorAll('input:not([aria-label]):not([aria-labelledby])');
17121
+ if (unlabeled.length > 0) {
17122
+ violations.push({
17123
+ id: 'form-labels', impact: 'critical', nodes: unlabeled.length, help: 'Form inputs need labels'
17124
+ });
17125
+ }
17126
+ passes += target.querySelectorAll('input[aria-label], input[aria-labelledby]').length;
17127
+ }
17128
+ const report = {
17129
+ violations, passes, timestamp: Date.now(),
17130
+ url: typeof window !== 'undefined' ? window.location.href : ''
17131
+ };
17132
+ if (format === 'console' && violations.length > 0) {
17133
+ console.group('🔍 A11y Audit Results');
17134
+ violations.forEach(v => console.warn(`${v.impact}: ${v.help}`));
17135
+ console.groupEnd();
17136
+ }
17137
+ return report;
17138
+ }
17139
+ var index$4 = { audit };
17140
+
17141
+ var index$5 = /*#__PURE__*/Object.freeze({
17142
+ __proto__: null,
17143
+ audit: audit,
17144
+ default: index$4
17145
+ });
17146
+
17147
+ /**
17148
+ * @sc4rfurryx/proteusjs/a11y-primitives
17149
+ * Lightweight accessibility patterns
17150
+ *
17151
+ * @version 1.1.0
17152
+ * @author sc4rfurry
17153
+ * @license MIT
17154
+ */
17155
+ function dialog(root, opts = {}) {
17156
+ const el = typeof root === 'string' ? document.querySelector(root) : root;
17157
+ if (!el)
17158
+ throw new Error('Dialog element not found');
17159
+ const { modal = true, restoreFocus = true } = opts;
17160
+ const close = () => {
17161
+ };
17162
+ return { destroy: () => close() };
17163
+ }
17164
+ function tooltip(trigger, content, opts = {}) {
17165
+ const { delay = 300 } = opts;
17166
+ let timeout;
17167
+ const show = () => {
17168
+ clearTimeout(timeout);
17169
+ timeout = window.setTimeout(() => {
17170
+ content.setAttribute('role', 'tooltip');
17171
+ trigger.setAttribute('aria-describedby', content.id || 'tooltip');
17172
+ content.style.display = 'block';
17173
+ }, delay);
17174
+ };
17175
+ const hide = () => {
17176
+ clearTimeout(timeout);
17177
+ content.style.display = 'none';
17178
+ trigger.removeAttribute('aria-describedby');
17179
+ };
17180
+ trigger.addEventListener('mouseenter', show);
17181
+ trigger.addEventListener('mouseleave', hide);
17182
+ trigger.addEventListener('focus', show);
17183
+ trigger.addEventListener('blur', hide);
17184
+ return {
17185
+ destroy: () => {
17186
+ clearTimeout(timeout);
17187
+ trigger.removeEventListener('mouseenter', show);
17188
+ trigger.removeEventListener('mouseleave', hide);
17189
+ trigger.removeEventListener('focus', show);
17190
+ trigger.removeEventListener('blur', hide);
17191
+ }
17192
+ };
17193
+ }
17194
+ function focusTrap(container) {
17195
+ const focusable = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
17196
+ const activate = () => {
17197
+ const elements = container.querySelectorAll(focusable);
17198
+ if (elements.length === 0)
17199
+ return;
17200
+ const first = elements[0];
17201
+ const last = elements[elements.length - 1];
17202
+ const handleTab = (e) => {
17203
+ if (e.key !== 'Tab')
17204
+ return;
17205
+ if (e.shiftKey && document.activeElement === first) {
17206
+ e.preventDefault();
17207
+ last.focus();
17208
+ }
17209
+ else if (!e.shiftKey && document.activeElement === last) {
17210
+ e.preventDefault();
17211
+ first.focus();
17212
+ }
17213
+ };
17214
+ container.addEventListener('keydown', handleTab);
17215
+ first.focus();
17216
+ return () => container.removeEventListener('keydown', handleTab);
17217
+ };
17218
+ let deactivate = () => { };
17219
+ return {
17220
+ activate: () => { deactivate = activate() || (() => { }); },
17221
+ deactivate: () => deactivate()
17222
+ };
17223
+ }
17224
+ function menu(container) {
17225
+ const items = container.querySelectorAll('[role="menuitem"]');
17226
+ let currentIndex = 0;
17227
+ const navigate = (e) => {
17228
+ switch (e.key) {
17229
+ case 'ArrowDown':
17230
+ e.preventDefault();
17231
+ currentIndex = (currentIndex + 1) % items.length;
17232
+ items[currentIndex].focus();
17233
+ break;
17234
+ case 'ArrowUp':
17235
+ e.preventDefault();
17236
+ currentIndex = currentIndex === 0 ? items.length - 1 : currentIndex - 1;
17237
+ items[currentIndex].focus();
17238
+ break;
17239
+ case 'Escape':
17240
+ e.preventDefault();
17241
+ container.dispatchEvent(new CustomEvent('menu:close'));
17242
+ break;
17243
+ }
17244
+ };
17245
+ container.setAttribute('role', 'menu');
17246
+ container.addEventListener('keydown', navigate);
17247
+ return {
17248
+ destroy: () => container.removeEventListener('keydown', navigate)
17249
+ };
17250
+ }
17251
+ var index$2 = { dialog, tooltip, focusTrap, menu };
17252
+
17253
+ var index$3 = /*#__PURE__*/Object.freeze({
17254
+ __proto__: null,
17255
+ default: index$2,
17256
+ dialog: dialog,
17257
+ focusTrap: focusTrap,
17258
+ menu: menu,
17259
+ tooltip: tooltip
17260
+ });
17261
+
17262
+ /**
17263
+ * @sc4rfurryx/proteusjs/perf
17264
+ * Performance guardrails and CWV-friendly patterns
17265
+ *
17266
+ * @version 1.1.0
17267
+ * @author sc4rfurry
17268
+ * @license MIT
17269
+ */
17270
+ /**
17271
+ * Apply content-visibility for performance optimization
17272
+ */
17273
+ function contentVisibility(selector, mode = 'auto', opts = {}) {
17274
+ const elements = typeof selector === 'string'
17275
+ ? document.querySelectorAll(selector)
17276
+ : [selector];
17277
+ const { containIntrinsicSize = '1000px 400px' } = opts;
17278
+ elements.forEach(element => {
17279
+ const el = element;
17280
+ el.style.contentVisibility = mode;
17281
+ if (mode === 'auto') {
17282
+ el.style.containIntrinsicSize = containIntrinsicSize;
17283
+ }
17284
+ });
17285
+ }
17286
+ /**
17287
+ * Set fetch priority for resources
17288
+ */
17289
+ function fetchPriority(selector, priority) {
17290
+ const elements = typeof selector === 'string'
17291
+ ? document.querySelectorAll(selector)
17292
+ : [selector];
17293
+ elements.forEach(element => {
17294
+ if (element instanceof HTMLImageElement ||
17295
+ element instanceof HTMLLinkElement ||
17296
+ element instanceof HTMLScriptElement) {
17297
+ element.fetchPriority = priority;
17298
+ }
17299
+ });
17300
+ }
17301
+ /**
17302
+ * Set up speculation rules for prerendering and prefetching
17303
+ */
17304
+ function speculate(opts) {
17305
+ const { prerender = [], prefetch = [], sameOriginOnly = true } = opts;
17306
+ // Check for Speculation Rules API support
17307
+ if (!('supports' in HTMLScriptElement && HTMLScriptElement.supports('speculationrules'))) {
17308
+ console.warn('Speculation Rules API not supported');
17309
+ return;
17310
+ }
17311
+ const rules = {};
17312
+ if (prerender.length > 0) {
17313
+ rules.prerender = prerender.map(url => {
17314
+ const rule = { where: { href_matches: url } };
17315
+ if (sameOriginOnly) {
17316
+ rule.where.href_matches = new URL(url, window.location.origin).href;
17317
+ }
17318
+ return rule;
17319
+ });
17320
+ }
17321
+ if (prefetch.length > 0) {
17322
+ rules.prefetch = prefetch.map(url => {
17323
+ const rule = { where: { href_matches: url } };
17324
+ if (sameOriginOnly) {
17325
+ rule.where.href_matches = new URL(url, window.location.origin).href;
17326
+ }
17327
+ return rule;
17328
+ });
17329
+ }
17330
+ if (Object.keys(rules).length === 0)
17331
+ return;
17332
+ // Create and inject speculation rules script
17333
+ const script = document.createElement('script');
17334
+ script.type = 'speculationrules';
17335
+ script.textContent = JSON.stringify(rules);
17336
+ document.head.appendChild(script);
17337
+ }
17338
+ /**
17339
+ * Yield to browser using scheduler.yield or postTask when available
17340
+ */
17341
+ async function yieldToBrowser() {
17342
+ // Use scheduler.yield if available (Chrome 115+)
17343
+ if ('scheduler' in window && 'yield' in window.scheduler) {
17344
+ return window.scheduler.yield();
17345
+ }
17346
+ // Use scheduler.postTask if available
17347
+ if ('scheduler' in window && 'postTask' in window.scheduler) {
17348
+ return new Promise(resolve => {
17349
+ window.scheduler.postTask(resolve, { priority: 'user-blocking' });
17350
+ });
17351
+ }
17352
+ // Fallback to setTimeout
17353
+ return new Promise(resolve => {
17354
+ setTimeout(resolve, 0);
17355
+ });
17356
+ }
17357
+ /**
17358
+ * Optimize images with loading and decoding hints
17359
+ */
17360
+ function optimizeImages(selector = 'img') {
17361
+ const images = typeof selector === 'string'
17362
+ ? document.querySelectorAll(selector)
17363
+ : [selector];
17364
+ images.forEach(img => {
17365
+ if (!(img instanceof HTMLImageElement))
17366
+ return;
17367
+ // Set loading attribute if not already set
17368
+ if (!img.hasAttribute('loading')) {
17369
+ const rect = img.getBoundingClientRect();
17370
+ const isAboveFold = rect.top < window.innerHeight;
17371
+ img.loading = isAboveFold ? 'eager' : 'lazy';
17372
+ }
17373
+ // Set decoding hint
17374
+ if (!img.hasAttribute('decoding')) {
17375
+ img.decoding = 'async';
17376
+ }
17377
+ // Set fetch priority for above-fold images
17378
+ if (!img.hasAttribute('fetchpriority')) {
17379
+ const rect = img.getBoundingClientRect();
17380
+ const isAboveFold = rect.top < window.innerHeight;
17381
+ if (isAboveFold) {
17382
+ img.fetchPriority = 'high';
17383
+ }
17384
+ }
17385
+ });
17386
+ }
17387
+ /**
17388
+ * Preload critical resources
17389
+ */
17390
+ function preloadCritical(resources) {
17391
+ resources.forEach(({ href, as, type }) => {
17392
+ // Check if already preloaded
17393
+ const existing = document.querySelector(`link[rel="preload"][href="${href}"]`);
17394
+ if (existing)
17395
+ return;
17396
+ const link = document.createElement('link');
17397
+ link.rel = 'preload';
17398
+ link.href = href;
17399
+ link.as = as;
17400
+ if (type) {
17401
+ link.type = type;
17402
+ }
17403
+ document.head.appendChild(link);
17404
+ });
17405
+ }
17406
+ /**
17407
+ * Measure and report Core Web Vitals
17408
+ */
17409
+ function measureCWV() {
17410
+ return new Promise(resolve => {
17411
+ const metrics = {};
17412
+ let metricsCount = 0;
17413
+ const totalMetrics = 3;
17414
+ const checkComplete = () => {
17415
+ metricsCount++;
17416
+ if (metricsCount >= totalMetrics) {
17417
+ resolve(metrics);
17418
+ }
17419
+ };
17420
+ // LCP (Largest Contentful Paint)
17421
+ if ('PerformanceObserver' in window) {
17422
+ try {
17423
+ const lcpObserver = new PerformanceObserver(list => {
17424
+ const entries = list.getEntries();
17425
+ const lastEntry = entries[entries.length - 1];
17426
+ metrics.lcp = lastEntry.startTime;
17427
+ });
17428
+ lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
17429
+ // Stop observing after 10 seconds
17430
+ setTimeout(() => {
17431
+ lcpObserver.disconnect();
17432
+ checkComplete();
17433
+ }, 10000);
17434
+ }
17435
+ catch {
17436
+ checkComplete();
17437
+ }
17438
+ // FID (First Input Delay)
17439
+ try {
17440
+ const fidObserver = new PerformanceObserver(list => {
17441
+ const entries = list.getEntries();
17442
+ entries.forEach((entry) => {
17443
+ metrics.fid = entry.processingStart - entry.startTime;
17444
+ });
17445
+ fidObserver.disconnect();
17446
+ checkComplete();
17447
+ });
17448
+ fidObserver.observe({ entryTypes: ['first-input'] });
17449
+ // If no input after 10 seconds, consider FID as 0
17450
+ setTimeout(() => {
17451
+ if (metrics.fid === undefined) {
17452
+ metrics.fid = 0;
17453
+ fidObserver.disconnect();
17454
+ checkComplete();
17455
+ }
17456
+ }, 10000);
17457
+ }
17458
+ catch {
17459
+ checkComplete();
17460
+ }
17461
+ // CLS (Cumulative Layout Shift)
17462
+ try {
17463
+ let clsValue = 0;
17464
+ const clsObserver = new PerformanceObserver(list => {
17465
+ list.getEntries().forEach((entry) => {
17466
+ if (!entry.hadRecentInput) {
17467
+ clsValue += entry.value;
17468
+ }
17469
+ });
17470
+ metrics.cls = clsValue;
17471
+ });
17472
+ clsObserver.observe({ entryTypes: ['layout-shift'] });
17473
+ // Stop observing after 10 seconds
17474
+ setTimeout(() => {
17475
+ clsObserver.disconnect();
17476
+ checkComplete();
17477
+ }, 10000);
17478
+ }
17479
+ catch {
17480
+ checkComplete();
17481
+ }
17482
+ }
17483
+ else {
17484
+ // Fallback if PerformanceObserver is not supported
17485
+ setTimeout(() => resolve(metrics), 100);
17486
+ }
17487
+ });
17488
+ }
17489
+ // Export boost object to match usage examples in upgrade spec
17490
+ const boost = {
17491
+ contentVisibility,
17492
+ fetchPriority,
17493
+ speculate,
17494
+ yieldToBrowser,
17495
+ optimizeImages,
17496
+ preloadCritical,
17497
+ measureCWV
17498
+ };
17499
+ // Export all functions as named exports and default object
17500
+ var index = {
17501
+ contentVisibility,
17502
+ fetchPriority,
17503
+ speculate,
17504
+ yieldToBrowser,
17505
+ optimizeImages,
17506
+ preloadCritical,
17507
+ measureCWV,
17508
+ boost
17509
+ };
17510
+
17511
+ var index$1 = /*#__PURE__*/Object.freeze({
17512
+ __proto__: null,
17513
+ boost: boost,
17514
+ contentVisibility: contentVisibility,
17515
+ default: index,
17516
+ fetchPriority: fetchPriority,
17517
+ measureCWV: measureCWV,
17518
+ optimizeImages: optimizeImages,
17519
+ preloadCritical: preloadCritical,
17520
+ speculate: speculate,
17521
+ yieldToBrowser: yieldToBrowser
17522
+ });
17523
+
17524
+ /**
17525
+ * ProteusJS - Native-first Web Development Primitives
15993
17526
  * Shape-shifting responsive design that adapts like the sea god himself
15994
17527
  *
15995
- * @version 1.0.0
15996
- * @author ProteusJS Team
17528
+ * @version 1.1.0
17529
+ * @author sc4rfurry
15997
17530
  * @license MIT
15998
17531
  */
15999
- // Core exports
17532
+ // Core exports (legacy compatibility)
16000
17533
  // Constants
16001
- const VERSION = '1.0.0';
17534
+ const VERSION = '1.1.0';
16002
17535
  const LIBRARY_NAME = 'ProteusJS';
16003
17536
 
16004
- export { LIBRARY_NAME, ProteusJS, VERSION, ProteusJS as default, isSupported, version };
17537
+ export { LIBRARY_NAME, ProteusJS, VERSION, index$5 as a11yAudit, index$3 as a11yPrimitives, index$d as anchor, index$9 as container, ProteusJS as default, isSupported$1 as isSupported, index$1 as perf, index$b as popover, index$f as scroll, index$h as transitions, index$7 as typography, version };
16005
17538
  //# sourceMappingURL=proteus.esm.js.map