@sc4rfurryx/proteusjs 1.0.0 → 1.1.0

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 +139 -0
  5. package/dist/adapters/react.esm.js +848 -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 +908 -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 +872 -0
  12. package/dist/adapters/vue.esm.js.map +1 -0
  13. package/dist/modules/a11y-audit.d.ts +39 -0
  14. package/dist/modules/a11y-audit.esm.js +509 -0
  15. package/dist/modules/a11y-audit.esm.js.map +1 -0
  16. package/dist/modules/a11y-primitives.d.ts +69 -0
  17. package/dist/modules/a11y-primitives.esm.js +445 -0
  18. package/dist/modules/a11y-primitives.esm.js.map +1 -0
  19. package/dist/modules/anchor.d.ts +29 -0
  20. package/dist/modules/anchor.esm.js +218 -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 +2332 -12
  41. package/dist/proteus.cjs.js.map +1 -1
  42. package/dist/proteus.d.ts +561 -12
  43. package/dist/proteus.esm.js +2323 -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 +2332 -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 +61 -4
  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 +608 -0
  57. package/src/modules/a11y-primitives/index.ts +554 -0
  58. package/src/modules/anchor/index.ts +257 -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
package/dist/proteus.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * ProteusJS v1.0.0
2
+ * ProteusJS v1.1.0
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
  (function (global, factory) {
@@ -15164,7 +15164,7 @@
15164
15164
  /**
15165
15165
  * Check if the current environment supports ProteusJS features
15166
15166
  */
15167
- function isSupported() {
15167
+ function isSupported$1() {
15168
15168
  const support = getSupportInfo();
15169
15169
  return support.resizeObserver && support.intersectionObserver;
15170
15170
  }
@@ -15231,7 +15231,7 @@
15231
15231
  /**
15232
15232
  * Version utilities for ProteusJS
15233
15233
  */
15234
- const version = '1.0.0';
15234
+ const version = '1.1.0';
15235
15235
 
15236
15236
  /**
15237
15237
  * ProteusJS - Main library class
@@ -15363,7 +15363,7 @@
15363
15363
  return this;
15364
15364
  }
15365
15365
  // Check browser support
15366
- if (!isSupported()) {
15366
+ if (!isSupported$1()) {
15367
15367
  logger.error('Browser not supported. Missing required APIs.');
15368
15368
  return this;
15369
15369
  }
@@ -15734,7 +15734,7 @@
15734
15734
  * Check if browser is supported
15735
15735
  */
15736
15736
  static isSupported() {
15737
- return isSupported();
15737
+ return isSupported$1();
15738
15738
  }
15739
15739
  /**
15740
15740
  * Get or create global instance
@@ -15995,23 +15995,2343 @@
15995
15995
  ProteusJS.instance = null;
15996
15996
 
15997
15997
  /**
15998
- * ProteusJS - Dynamic Responsive Design Library
15998
+ * @sc4rfurryx/proteusjs/transitions
15999
+ * View Transitions API wrapper with safe fallbacks
16000
+ *
16001
+ * @version 1.1.0
16002
+ * @author sc4rfurry
16003
+ * @license MIT
16004
+ */
16005
+ /**
16006
+ * One API for animating DOM state changes and cross-document navigations
16007
+ * using the View Transitions API with safe fallbacks.
16008
+ */
16009
+ async function transition(run, opts = {}) {
16010
+ const { name, duration = 300, onBefore, onAfter, allowInterrupt = true } = opts;
16011
+ // Check for View Transitions API support
16012
+ const hasViewTransitions = 'startViewTransition' in document;
16013
+ if (onBefore) {
16014
+ onBefore();
16015
+ }
16016
+ if (!hasViewTransitions) {
16017
+ // Fallback: run immediately without transitions
16018
+ try {
16019
+ await run();
16020
+ }
16021
+ finally {
16022
+ if (onAfter) {
16023
+ onAfter();
16024
+ }
16025
+ }
16026
+ return;
16027
+ }
16028
+ // Use native View Transitions API
16029
+ try {
16030
+ const viewTransition = document.startViewTransition(async () => {
16031
+ await run();
16032
+ });
16033
+ // Add CSS view-transition-name if name provided
16034
+ if (name) {
16035
+ const style = document.createElement('style');
16036
+ style.textContent = `
16037
+ ::view-transition-old(${name}),
16038
+ ::view-transition-new(${name}) {
16039
+ animation-duration: ${duration}ms;
16040
+ }
16041
+ `;
16042
+ document.head.appendChild(style);
16043
+ // Clean up style after transition
16044
+ viewTransition.finished.finally(() => {
16045
+ style.remove();
16046
+ });
16047
+ }
16048
+ await viewTransition.finished;
16049
+ }
16050
+ catch (error) {
16051
+ console.warn('View transition failed, falling back to immediate execution:', error);
16052
+ await run();
16053
+ }
16054
+ finally {
16055
+ if (onAfter) {
16056
+ onAfter();
16057
+ }
16058
+ }
16059
+ }
16060
+ /**
16061
+ * MPA-friendly navigation with view transitions when supported
16062
+ */
16063
+ async function navigate(url, opts = {}) {
16064
+ const { name, prerender = false } = opts;
16065
+ // Optional prerender hint (basic implementation)
16066
+ if (prerender && 'speculation' in HTMLScriptElement.prototype) {
16067
+ const script = document.createElement('script');
16068
+ script.type = 'speculationrules';
16069
+ script.textContent = JSON.stringify({
16070
+ prerender: [{ where: { href_matches: url } }]
16071
+ });
16072
+ document.head.appendChild(script);
16073
+ }
16074
+ // Check for View Transitions API support
16075
+ const hasViewTransitions = 'startViewTransition' in document;
16076
+ if (!hasViewTransitions) {
16077
+ // Fallback: normal navigation
16078
+ window.location.href = url;
16079
+ return;
16080
+ }
16081
+ try {
16082
+ // Use view transitions for navigation
16083
+ const viewTransition = document.startViewTransition(() => {
16084
+ window.location.href = url;
16085
+ });
16086
+ if (name) {
16087
+ const style = document.createElement('style');
16088
+ style.textContent = `
16089
+ ::view-transition-old(${name}),
16090
+ ::view-transition-new(${name}) {
16091
+ animation-duration: 300ms;
16092
+ }
16093
+ `;
16094
+ document.head.appendChild(style);
16095
+ }
16096
+ await viewTransition.finished;
16097
+ }
16098
+ catch (error) {
16099
+ console.warn('View transition navigation failed, falling back to normal navigation:', error);
16100
+ window.location.href = url;
16101
+ }
16102
+ }
16103
+ // Export default object for convenience
16104
+ var index$g = {
16105
+ transition,
16106
+ navigate
16107
+ };
16108
+
16109
+ var index$h = /*#__PURE__*/Object.freeze({
16110
+ __proto__: null,
16111
+ default: index$g,
16112
+ navigate: navigate,
16113
+ transition: transition
16114
+ });
16115
+
16116
+ /**
16117
+ * @sc4rfurryx/proteusjs/scroll
16118
+ * Scroll-driven animations with CSS Scroll-Linked Animations
16119
+ *
16120
+ * @version 1.1.0
16121
+ * @author sc4rfurry
16122
+ * @license MIT
16123
+ */
16124
+ /**
16125
+ * Zero-boilerplate setup for CSS Scroll-Linked Animations with fallbacks
16126
+ */
16127
+ function scrollAnimate(target, opts) {
16128
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16129
+ if (!targetEl) {
16130
+ throw new Error('Target element not found');
16131
+ }
16132
+ const { keyframes, range = ['0%', '100%'], timeline = {}, fallback = 'io' } = opts;
16133
+ const { axis = 'block', start = '0%', end = '100%' } = timeline;
16134
+ // Check for CSS Scroll-Linked Animations support
16135
+ const hasScrollTimeline = 'CSS' in window && CSS.supports('animation-timeline', 'scroll()');
16136
+ // Check for reduced motion preference
16137
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16138
+ if (prefersReducedMotion) {
16139
+ // Respect user preference - either disable or reduce animation
16140
+ if (fallback === false)
16141
+ return;
16142
+ // Apply only the end state for reduced motion
16143
+ const endKeyframe = keyframes[keyframes.length - 1];
16144
+ Object.assign(targetEl.style, endKeyframe);
16145
+ return;
16146
+ }
16147
+ if (hasScrollTimeline) {
16148
+ // Use native CSS Scroll-Linked Animations
16149
+ const timelineName = `scroll-timeline-${Math.random().toString(36).substr(2, 9)}`;
16150
+ // Create scroll timeline
16151
+ const style = document.createElement('style');
16152
+ style.textContent = `
16153
+ @scroll-timeline ${timelineName} {
16154
+ source: nearest;
16155
+ orientation: ${axis};
16156
+ scroll-offsets: ${start}, ${end};
16157
+ }
16158
+
16159
+ .scroll-animate-${timelineName} {
16160
+ animation-timeline: ${timelineName};
16161
+ animation-duration: 1ms; /* Required but ignored */
16162
+ animation-fill-mode: both;
16163
+ }
16164
+ `;
16165
+ document.head.appendChild(style);
16166
+ // Apply animation class
16167
+ targetEl.classList.add(`scroll-animate-${timelineName}`);
16168
+ // Create Web Animations API animation
16169
+ const animation = targetEl.animate(keyframes, {
16170
+ duration: 1, // Required but ignored with scroll timeline
16171
+ fill: 'both'
16172
+ });
16173
+ // Set scroll timeline (when supported)
16174
+ if ('timeline' in animation) {
16175
+ animation.timeline = new window.ScrollTimeline({
16176
+ source: document.scrollingElement,
16177
+ orientation: axis,
16178
+ scrollOffsets: [
16179
+ { target: targetEl, edge: 'start', threshold: parseFloat(start) / 100 },
16180
+ { target: targetEl, edge: 'end', threshold: parseFloat(end) / 100 }
16181
+ ]
16182
+ });
16183
+ }
16184
+ }
16185
+ else if (fallback === 'io') {
16186
+ // Fallback using Intersection Observer
16187
+ let animation = null;
16188
+ const observer = new IntersectionObserver((entries) => {
16189
+ entries.forEach(entry => {
16190
+ const progress = Math.max(0, Math.min(1, entry.intersectionRatio));
16191
+ if (!animation) {
16192
+ animation = targetEl.animate(keyframes, {
16193
+ duration: 1000,
16194
+ fill: 'both'
16195
+ });
16196
+ animation.pause();
16197
+ }
16198
+ // Update animation progress based on intersection
16199
+ animation.currentTime = progress * 1000;
16200
+ });
16201
+ }, {
16202
+ threshold: Array.from({ length: 101 }, (_, i) => i / 100) // 0 to 1 in 0.01 steps
16203
+ });
16204
+ observer.observe(targetEl);
16205
+ // Store cleanup function
16206
+ targetEl._scrollAnimateCleanup = () => {
16207
+ observer.disconnect();
16208
+ if (animation) {
16209
+ animation.cancel();
16210
+ }
16211
+ };
16212
+ }
16213
+ }
16214
+ /**
16215
+ * Create a scroll-triggered animation that plays once when element enters viewport
16216
+ */
16217
+ function scrollTrigger(target, keyframes, options = {}) {
16218
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16219
+ if (!targetEl) {
16220
+ throw new Error('Target element not found');
16221
+ }
16222
+ // Check for reduced motion preference
16223
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16224
+ if (prefersReducedMotion) {
16225
+ // Apply end state immediately
16226
+ const endKeyframe = keyframes[keyframes.length - 1];
16227
+ Object.assign(targetEl.style, endKeyframe);
16228
+ return;
16229
+ }
16230
+ const observer = new IntersectionObserver((entries) => {
16231
+ entries.forEach(entry => {
16232
+ if (entry.isIntersecting) {
16233
+ // Play animation
16234
+ targetEl.animate(keyframes, {
16235
+ duration: 600,
16236
+ easing: 'ease-out',
16237
+ fill: 'forwards',
16238
+ ...options
16239
+ });
16240
+ // Disconnect observer after first trigger
16241
+ observer.disconnect();
16242
+ }
16243
+ });
16244
+ }, {
16245
+ threshold: 0.1,
16246
+ rootMargin: '0px 0px -10% 0px'
16247
+ });
16248
+ observer.observe(targetEl);
16249
+ }
16250
+ /**
16251
+ * Parallax effect using scroll-driven animations
16252
+ */
16253
+ function parallax(target, speed = 0.5) {
16254
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16255
+ if (!targetEl) {
16256
+ throw new Error('Target element not found');
16257
+ }
16258
+ // Check for reduced motion preference
16259
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16260
+ if (prefersReducedMotion)
16261
+ return;
16262
+ const keyframes = [
16263
+ { transform: `translateY(${ -100 * speed}px)` },
16264
+ { transform: `translateY(${100 * speed}px)` }
16265
+ ];
16266
+ scrollAnimate(targetEl, {
16267
+ keyframes,
16268
+ range: ['0%', '100%'],
16269
+ timeline: { axis: 'block' },
16270
+ fallback: 'io'
16271
+ });
16272
+ }
16273
+ /**
16274
+ * Cleanup function to remove scroll animations
16275
+ */
16276
+ function cleanup$2(target) {
16277
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16278
+ if (!targetEl)
16279
+ return;
16280
+ // Call stored cleanup function if it exists
16281
+ if (targetEl._scrollAnimateCleanup) {
16282
+ targetEl._scrollAnimateCleanup();
16283
+ delete targetEl._scrollAnimateCleanup;
16284
+ }
16285
+ // Remove animation classes
16286
+ targetEl.classList.forEach(className => {
16287
+ if (className.startsWith('scroll-animate-')) {
16288
+ targetEl.classList.remove(className);
16289
+ }
16290
+ });
16291
+ // Cancel any running animations
16292
+ const animations = targetEl.getAnimations();
16293
+ animations.forEach(animation => animation.cancel());
16294
+ }
16295
+ // Export default object for convenience
16296
+ var index$e = {
16297
+ scrollAnimate,
16298
+ scrollTrigger,
16299
+ parallax,
16300
+ cleanup: cleanup$2
16301
+ };
16302
+
16303
+ var index$f = /*#__PURE__*/Object.freeze({
16304
+ __proto__: null,
16305
+ cleanup: cleanup$2,
16306
+ default: index$e,
16307
+ parallax: parallax,
16308
+ scrollAnimate: scrollAnimate,
16309
+ scrollTrigger: scrollTrigger
16310
+ });
16311
+
16312
+ /**
16313
+ * @sc4rfurryx/proteusjs/anchor
16314
+ * CSS Anchor Positioning utilities with robust JS fallback
16315
+ *
16316
+ * @version 1.1.0
16317
+ * @author sc4rfurry
16318
+ * @license MIT
16319
+ */
16320
+ /**
16321
+ * Declarative tethers (tooltips, callouts) via CSS Anchor Positioning when available;
16322
+ * robust JS fallback with flip/collision detection
16323
+ */
16324
+ function tether(floating, opts) {
16325
+ const floatingEl = typeof floating === 'string' ? document.querySelector(floating) : floating;
16326
+ const anchorEl = typeof opts.anchor === 'string' ? document.querySelector(opts.anchor) : opts.anchor;
16327
+ if (!floatingEl || !anchorEl) {
16328
+ throw new Error('Both floating and anchor elements must exist');
16329
+ }
16330
+ const { placement = 'bottom', align = 'center', offset = 8, strategy = 'absolute' } = opts;
16331
+ // Check for CSS Anchor Positioning support
16332
+ const hasAnchorPositioning = CSS.supports('anchor-name', 'test');
16333
+ let isDestroyed = false;
16334
+ let resizeObserver = null;
16335
+ const setupCSSAnchorPositioning = () => {
16336
+ if (!hasAnchorPositioning)
16337
+ return false;
16338
+ // Generate unique anchor name
16339
+ const anchorName = `anchor-${Math.random().toString(36).substring(2, 11)}`;
16340
+ // Set anchor name on anchor element
16341
+ anchorEl.style.setProperty('anchor-name', anchorName);
16342
+ // Position floating element using CSS anchor positioning
16343
+ const floatingStyle = floatingEl;
16344
+ floatingStyle.style.position = strategy;
16345
+ floatingStyle.style.setProperty('position-anchor', anchorName);
16346
+ // Set position based on placement
16347
+ switch (placement) {
16348
+ case 'top':
16349
+ floatingStyle.style.bottom = `anchor(bottom, ${offset}px)`;
16350
+ break;
16351
+ case 'bottom':
16352
+ floatingStyle.style.top = `anchor(bottom, ${offset}px)`;
16353
+ break;
16354
+ case 'left':
16355
+ floatingStyle.style.right = `anchor(left, ${offset}px)`;
16356
+ break;
16357
+ case 'right':
16358
+ floatingStyle.style.left = `anchor(right, ${offset}px)`;
16359
+ break;
16360
+ }
16361
+ // Set alignment
16362
+ if (placement === 'top' || placement === 'bottom') {
16363
+ switch (align) {
16364
+ case 'start':
16365
+ floatingStyle.style.left = 'anchor(left)';
16366
+ break;
16367
+ case 'center':
16368
+ floatingStyle.style.left = 'anchor(center)';
16369
+ floatingStyle.style.transform = 'translateX(-50%)';
16370
+ break;
16371
+ case 'end':
16372
+ floatingStyle.style.right = 'anchor(right)';
16373
+ break;
16374
+ }
16375
+ }
16376
+ else {
16377
+ switch (align) {
16378
+ case 'start':
16379
+ floatingStyle.style.top = 'anchor(top)';
16380
+ break;
16381
+ case 'center':
16382
+ floatingStyle.style.top = 'anchor(center)';
16383
+ floatingStyle.style.transform = 'translateY(-50%)';
16384
+ break;
16385
+ case 'end':
16386
+ floatingStyle.style.bottom = 'anchor(bottom)';
16387
+ break;
16388
+ }
16389
+ }
16390
+ return true;
16391
+ };
16392
+ const calculatePosition = () => {
16393
+ const anchorRect = anchorEl.getBoundingClientRect();
16394
+ const floatingRect = floatingEl.getBoundingClientRect();
16395
+ const viewport = {
16396
+ width: window.innerWidth,
16397
+ height: window.innerHeight
16398
+ };
16399
+ let finalPlacement = placement;
16400
+ let x = 0;
16401
+ let y = 0;
16402
+ // Calculate base position
16403
+ switch (finalPlacement) {
16404
+ case 'top':
16405
+ x = anchorRect.left;
16406
+ y = anchorRect.top - floatingRect.height - offset;
16407
+ break;
16408
+ case 'bottom':
16409
+ x = anchorRect.left;
16410
+ y = anchorRect.bottom + offset;
16411
+ break;
16412
+ case 'left':
16413
+ x = anchorRect.left - floatingRect.width - offset;
16414
+ y = anchorRect.top;
16415
+ break;
16416
+ case 'right':
16417
+ x = anchorRect.right + offset;
16418
+ y = anchorRect.top;
16419
+ break;
16420
+ case 'auto': {
16421
+ // Choose best placement based on available space
16422
+ const spaces = {
16423
+ top: anchorRect.top,
16424
+ bottom: viewport.height - anchorRect.bottom,
16425
+ left: anchorRect.left,
16426
+ right: viewport.width - anchorRect.right
16427
+ };
16428
+ const bestPlacement = Object.entries(spaces).reduce((a, b) => spaces[a[0]] > spaces[b[0]] ? a : b)[0];
16429
+ finalPlacement = bestPlacement;
16430
+ return calculatePosition(); // Recursive call with determined placement
16431
+ }
16432
+ }
16433
+ // Apply alignment
16434
+ if (finalPlacement === 'top' || finalPlacement === 'bottom') {
16435
+ switch (align) {
16436
+ case 'start':
16437
+ // x already set correctly
16438
+ break;
16439
+ case 'center':
16440
+ x = anchorRect.left + (anchorRect.width - floatingRect.width) / 2;
16441
+ break;
16442
+ case 'end':
16443
+ x = anchorRect.right - floatingRect.width;
16444
+ break;
16445
+ }
16446
+ }
16447
+ else {
16448
+ switch (align) {
16449
+ case 'start':
16450
+ // y already set correctly
16451
+ break;
16452
+ case 'center':
16453
+ y = anchorRect.top + (anchorRect.height - floatingRect.height) / 2;
16454
+ break;
16455
+ case 'end':
16456
+ y = anchorRect.bottom - floatingRect.height;
16457
+ break;
16458
+ }
16459
+ }
16460
+ // Collision detection and adjustment
16461
+ if (x < 0)
16462
+ x = 8;
16463
+ if (y < 0)
16464
+ y = 8;
16465
+ if (x + floatingRect.width > viewport.width) {
16466
+ x = viewport.width - floatingRect.width - 8;
16467
+ }
16468
+ if (y + floatingRect.height > viewport.height) {
16469
+ y = viewport.height - floatingRect.height - 8;
16470
+ }
16471
+ return { x, y };
16472
+ };
16473
+ const updatePosition = () => {
16474
+ if (isDestroyed)
16475
+ return;
16476
+ if (!hasAnchorPositioning) {
16477
+ const { x, y } = calculatePosition();
16478
+ const floatingStyle = floatingEl;
16479
+ floatingStyle.style.position = strategy;
16480
+ floatingStyle.style.left = `${x}px`;
16481
+ floatingStyle.style.top = `${y}px`;
16482
+ }
16483
+ };
16484
+ const setupJSFallback = () => {
16485
+ updatePosition();
16486
+ // Set up observers for position updates
16487
+ resizeObserver = new ResizeObserver(updatePosition);
16488
+ resizeObserver.observe(anchorEl);
16489
+ resizeObserver.observe(floatingEl);
16490
+ window.addEventListener('scroll', updatePosition, { passive: true });
16491
+ window.addEventListener('resize', updatePosition, { passive: true });
16492
+ };
16493
+ const destroy = () => {
16494
+ isDestroyed = true;
16495
+ if (resizeObserver) {
16496
+ resizeObserver.disconnect();
16497
+ resizeObserver = null;
16498
+ }
16499
+ window.removeEventListener('scroll', updatePosition);
16500
+ window.removeEventListener('resize', updatePosition);
16501
+ // Clean up CSS anchor positioning
16502
+ if (hasAnchorPositioning) {
16503
+ anchorEl.style.removeProperty('anchor-name');
16504
+ const floatingStyle = floatingEl;
16505
+ floatingStyle.style.removeProperty('position-anchor');
16506
+ floatingStyle.style.position = '';
16507
+ }
16508
+ };
16509
+ // Initialize
16510
+ if (!setupCSSAnchorPositioning()) {
16511
+ setupJSFallback();
16512
+ }
16513
+ return {
16514
+ destroy
16515
+ };
16516
+ }
16517
+ // Export default object for convenience
16518
+ var index$c = {
16519
+ tether
16520
+ };
16521
+
16522
+ var index$d = /*#__PURE__*/Object.freeze({
16523
+ __proto__: null,
16524
+ default: index$c,
16525
+ tether: tether
16526
+ });
16527
+
16528
+ /**
16529
+ * @sc4rfurryx/proteusjs/popover
16530
+ * HTML Popover API wrapper with robust focus/inert handling
16531
+ *
16532
+ * @version 1.1.0
16533
+ * @author sc4rfurry
16534
+ * @license MIT
16535
+ */
16536
+ /**
16537
+ * Unified API for menus, tooltips, and dialogs using the native Popover API
16538
+ * with robust focus/inert handling
16539
+ */
16540
+ function attach(trigger, panel, opts = {}) {
16541
+ const triggerEl = typeof trigger === 'string' ? document.querySelector(trigger) : trigger;
16542
+ const panelEl = typeof panel === 'string' ? document.querySelector(panel) : panel;
16543
+ if (!triggerEl || !panelEl) {
16544
+ throw new Error('Both trigger and panel elements must exist');
16545
+ }
16546
+ const { type = 'menu', trapFocus = type === 'dialog', restoreFocus = true, closeOnEscape = true, onOpen, onClose } = opts;
16547
+ let isOpen = false;
16548
+ let previousFocus = null;
16549
+ let focusTrap = null;
16550
+ // Check for native Popover API support
16551
+ const hasPopoverAPI = 'popover' in HTMLElement.prototype;
16552
+ // Set up ARIA attributes
16553
+ const setupAria = () => {
16554
+ const panelId = panelEl.id || `popover-${Math.random().toString(36).substr(2, 9)}`;
16555
+ panelEl.id = panelId;
16556
+ triggerEl.setAttribute('aria-expanded', 'false');
16557
+ triggerEl.setAttribute('aria-controls', panelId);
16558
+ if (type === 'menu') {
16559
+ triggerEl.setAttribute('aria-haspopup', 'menu');
16560
+ panelEl.setAttribute('role', 'menu');
16561
+ }
16562
+ else if (type === 'dialog') {
16563
+ triggerEl.setAttribute('aria-haspopup', 'dialog');
16564
+ panelEl.setAttribute('role', 'dialog');
16565
+ panelEl.setAttribute('aria-modal', 'true');
16566
+ }
16567
+ else if (type === 'tooltip') {
16568
+ triggerEl.setAttribute('aria-describedby', panelId);
16569
+ panelEl.setAttribute('role', 'tooltip');
16570
+ }
16571
+ };
16572
+ // Set up native popover if supported
16573
+ const setupNativePopover = () => {
16574
+ if (hasPopoverAPI) {
16575
+ panelEl.popover = type === 'dialog' ? 'manual' : 'auto';
16576
+ triggerEl.setAttribute('popovertarget', panelEl.id);
16577
+ }
16578
+ };
16579
+ // Focus trap implementation
16580
+ class FocusTrap {
16581
+ constructor(container) {
16582
+ this.container = container;
16583
+ this.focusableElements = [];
16584
+ this.handleKeyDown = (e) => {
16585
+ if (e.key !== 'Tab')
16586
+ return;
16587
+ const firstElement = this.focusableElements[0];
16588
+ const lastElement = this.focusableElements[this.focusableElements.length - 1];
16589
+ if (e.shiftKey) {
16590
+ if (document.activeElement === firstElement) {
16591
+ e.preventDefault();
16592
+ lastElement.focus();
16593
+ }
16594
+ }
16595
+ else {
16596
+ if (document.activeElement === lastElement) {
16597
+ e.preventDefault();
16598
+ firstElement.focus();
16599
+ }
16600
+ }
16601
+ };
16602
+ this.updateFocusableElements();
16603
+ }
16604
+ updateFocusableElements() {
16605
+ const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
16606
+ this.focusableElements = Array.from(this.container.querySelectorAll(selector));
16607
+ }
16608
+ activate() {
16609
+ this.updateFocusableElements();
16610
+ if (this.focusableElements.length > 0) {
16611
+ this.focusableElements[0].focus();
16612
+ }
16613
+ document.addEventListener('keydown', this.handleKeyDown);
16614
+ }
16615
+ deactivate() {
16616
+ document.removeEventListener('keydown', this.handleKeyDown);
16617
+ }
16618
+ }
16619
+ const open = () => {
16620
+ if (isOpen)
16621
+ return;
16622
+ if (restoreFocus) {
16623
+ previousFocus = document.activeElement;
16624
+ }
16625
+ if (hasPopoverAPI) {
16626
+ panelEl.showPopover();
16627
+ }
16628
+ else {
16629
+ panelEl.style.display = 'block';
16630
+ panelEl.setAttribute('data-popover-open', 'true');
16631
+ }
16632
+ triggerEl.setAttribute('aria-expanded', 'true');
16633
+ isOpen = true;
16634
+ if (trapFocus) {
16635
+ focusTrap = new FocusTrap(panelEl);
16636
+ focusTrap.activate();
16637
+ }
16638
+ if (onOpen) {
16639
+ onOpen();
16640
+ }
16641
+ };
16642
+ const close = () => {
16643
+ if (!isOpen)
16644
+ return;
16645
+ if (hasPopoverAPI) {
16646
+ panelEl.hidePopover();
16647
+ }
16648
+ else {
16649
+ panelEl.style.display = 'none';
16650
+ panelEl.removeAttribute('data-popover-open');
16651
+ }
16652
+ triggerEl.setAttribute('aria-expanded', 'false');
16653
+ isOpen = false;
16654
+ if (focusTrap) {
16655
+ focusTrap.deactivate();
16656
+ focusTrap = null;
16657
+ }
16658
+ if (restoreFocus && previousFocus) {
16659
+ previousFocus.focus();
16660
+ previousFocus = null;
16661
+ }
16662
+ if (onClose) {
16663
+ onClose();
16664
+ }
16665
+ };
16666
+ const toggle = () => {
16667
+ if (isOpen) {
16668
+ close();
16669
+ }
16670
+ else {
16671
+ open();
16672
+ }
16673
+ };
16674
+ const handleKeyDown = (e) => {
16675
+ if (closeOnEscape && e.key === 'Escape' && isOpen) {
16676
+ e.preventDefault();
16677
+ close();
16678
+ }
16679
+ };
16680
+ const handleClick = (e) => {
16681
+ e.preventDefault();
16682
+ toggle();
16683
+ };
16684
+ const destroy = () => {
16685
+ triggerEl.removeEventListener('click', handleClick);
16686
+ document.removeEventListener('keydown', handleKeyDown);
16687
+ if (focusTrap) {
16688
+ focusTrap.deactivate();
16689
+ }
16690
+ if (isOpen) {
16691
+ close();
16692
+ }
16693
+ };
16694
+ // Initialize
16695
+ setupAria();
16696
+ setupNativePopover();
16697
+ triggerEl.addEventListener('click', handleClick);
16698
+ document.addEventListener('keydown', handleKeyDown);
16699
+ return {
16700
+ open,
16701
+ close,
16702
+ toggle,
16703
+ destroy
16704
+ };
16705
+ }
16706
+ // Export default object for convenience
16707
+ var index$a = {
16708
+ attach
16709
+ };
16710
+
16711
+ var index$b = /*#__PURE__*/Object.freeze({
16712
+ __proto__: null,
16713
+ attach: attach,
16714
+ default: index$a
16715
+ });
16716
+
16717
+ /**
16718
+ * @sc4rfurryx/proteusjs/container
16719
+ * Container/Style Query helpers with visualization devtools
16720
+ *
16721
+ * @version 1.1.0
16722
+ * @author sc4rfurry
16723
+ * @license MIT
16724
+ */
16725
+ /**
16726
+ * Sugar on native container queries with dev visualization
16727
+ */
16728
+ function defineContainer(target, name, opts = {}) {
16729
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16730
+ if (!targetEl) {
16731
+ throw new Error('Target element not found');
16732
+ }
16733
+ const { type = 'size', inlineSize: _inlineSize = true } = opts;
16734
+ const containerName = name || `container-${Math.random().toString(36).substring(2, 11)}`;
16735
+ // Apply container properties
16736
+ const element = targetEl;
16737
+ element.style.containerName = containerName;
16738
+ element.style.containerType = type;
16739
+ // Warn if containment settings are missing
16740
+ const computedStyle = getComputedStyle(element);
16741
+ if (!computedStyle.contain || computedStyle.contain === 'none') {
16742
+ console.warn(`Container "${containerName}" may need explicit containment settings for optimal performance`);
16743
+ }
16744
+ // Dev overlay (only in development)
16745
+ if (process.env['NODE_ENV'] === 'development' || window.__PROTEUS_DEV__) {
16746
+ createDevOverlay(element, containerName);
16747
+ }
16748
+ }
16749
+ /**
16750
+ * Create development overlay showing container bounds and breakpoints
16751
+ */
16752
+ function createDevOverlay(element, name) {
16753
+ const overlay = document.createElement('div');
16754
+ overlay.className = 'proteus-container-overlay';
16755
+ overlay.style.cssText = `
16756
+ position: absolute;
16757
+ top: 0;
16758
+ left: 0;
16759
+ right: 0;
16760
+ bottom: 0;
16761
+ pointer-events: none;
16762
+ border: 2px dashed rgba(255, 0, 255, 0.5);
16763
+ background: rgba(255, 0, 255, 0.05);
16764
+ z-index: 9999;
16765
+ font-family: monospace;
16766
+ font-size: 12px;
16767
+ color: #ff00ff;
16768
+ `;
16769
+ const label = document.createElement('div');
16770
+ label.style.cssText = `
16771
+ position: absolute;
16772
+ top: -20px;
16773
+ left: 0;
16774
+ background: rgba(255, 0, 255, 0.9);
16775
+ color: white;
16776
+ padding: 2px 6px;
16777
+ border-radius: 3px;
16778
+ font-size: 10px;
16779
+ white-space: nowrap;
16780
+ `;
16781
+ label.textContent = `Container: ${name}`;
16782
+ const sizeInfo = document.createElement('div');
16783
+ sizeInfo.style.cssText = `
16784
+ position: absolute;
16785
+ bottom: 2px;
16786
+ right: 2px;
16787
+ background: rgba(0, 0, 0, 0.7);
16788
+ color: white;
16789
+ padding: 2px 4px;
16790
+ border-radius: 2px;
16791
+ font-size: 10px;
16792
+ `;
16793
+ overlay.appendChild(label);
16794
+ overlay.appendChild(sizeInfo);
16795
+ // Position overlay relative to container
16796
+ if (getComputedStyle(element).position === 'static') {
16797
+ element.style.position = 'relative';
16798
+ }
16799
+ element.appendChild(overlay);
16800
+ // Update size info
16801
+ const updateSizeInfo = () => {
16802
+ const rect = element.getBoundingClientRect();
16803
+ sizeInfo.textContent = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
16804
+ };
16805
+ updateSizeInfo();
16806
+ // Update on resize
16807
+ if ('ResizeObserver' in window) {
16808
+ const resizeObserver = new ResizeObserver(updateSizeInfo);
16809
+ resizeObserver.observe(element);
16810
+ }
16811
+ // Store cleanup function
16812
+ element._proteusContainerCleanup = () => {
16813
+ overlay.remove();
16814
+ };
16815
+ }
16816
+ /**
16817
+ * Helper to create container query CSS rules
16818
+ */
16819
+ function createContainerQuery(containerName, condition, styles) {
16820
+ const cssRules = Object.entries(styles)
16821
+ .map(([property, value]) => ` ${property}: ${value};`)
16822
+ .join('\n');
16823
+ return `@container ${containerName} (${condition}) {\n${cssRules}\n}`;
16824
+ }
16825
+ /**
16826
+ * Apply container query styles dynamically
16827
+ */
16828
+ function applyContainerQuery(containerName, condition, styles) {
16829
+ const css = createContainerQuery(containerName, condition, styles);
16830
+ const styleElement = document.createElement('style');
16831
+ styleElement.textContent = css;
16832
+ styleElement.setAttribute('data-proteus-container', containerName);
16833
+ document.head.appendChild(styleElement);
16834
+ }
16835
+ /**
16836
+ * Remove container query styles
16837
+ */
16838
+ function removeContainerQuery(containerName) {
16839
+ const styleElements = document.querySelectorAll(`style[data-proteus-container="${containerName}"]`);
16840
+ styleElements.forEach(element => element.remove());
16841
+ }
16842
+ /**
16843
+ * Get container size information
16844
+ */
16845
+ function getContainerSize(target) {
16846
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16847
+ if (!targetEl) {
16848
+ throw new Error('Target element not found');
16849
+ }
16850
+ const rect = targetEl.getBoundingClientRect();
16851
+ return {
16852
+ width: rect.width,
16853
+ height: rect.height
16854
+ };
16855
+ }
16856
+ /**
16857
+ * Check if container queries are supported
16858
+ */
16859
+ function isSupported() {
16860
+ return CSS.supports('container-type', 'size');
16861
+ }
16862
+ /**
16863
+ * Cleanup container overlays and observers
16864
+ */
16865
+ function cleanup$1(target) {
16866
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16867
+ if (!targetEl)
16868
+ return;
16869
+ // Call stored cleanup function if it exists
16870
+ const elementWithCleanup = targetEl;
16871
+ if (elementWithCleanup._proteusContainerCleanup) {
16872
+ elementWithCleanup._proteusContainerCleanup();
16873
+ delete elementWithCleanup._proteusContainerCleanup;
16874
+ }
16875
+ }
16876
+ /**
16877
+ * Toggle dev overlay visibility
16878
+ */
16879
+ function toggleDevOverlay(visible) {
16880
+ const overlays = document.querySelectorAll('.proteus-container-overlay');
16881
+ overlays.forEach(overlay => {
16882
+ const element = overlay;
16883
+ if (visible !== undefined) {
16884
+ element.style.display = visible ? 'block' : 'none';
16885
+ }
16886
+ else {
16887
+ element.style.display = element.style.display === 'none' ? 'block' : 'none';
16888
+ }
16889
+ });
16890
+ }
16891
+ // Export default object for convenience
16892
+ var index$8 = {
16893
+ defineContainer,
16894
+ createContainerQuery,
16895
+ applyContainerQuery,
16896
+ removeContainerQuery,
16897
+ getContainerSize,
16898
+ isSupported,
16899
+ cleanup: cleanup$1,
16900
+ toggleDevOverlay
16901
+ };
16902
+
16903
+ var index$9 = /*#__PURE__*/Object.freeze({
16904
+ __proto__: null,
16905
+ applyContainerQuery: applyContainerQuery,
16906
+ cleanup: cleanup$1,
16907
+ createContainerQuery: createContainerQuery,
16908
+ default: index$8,
16909
+ defineContainer: defineContainer,
16910
+ getContainerSize: getContainerSize,
16911
+ isSupported: isSupported,
16912
+ removeContainerQuery: removeContainerQuery,
16913
+ toggleDevOverlay: toggleDevOverlay
16914
+ });
16915
+
16916
+ /**
16917
+ * @sc4rfurryx/proteusjs/typography
16918
+ * Fluid typography with CSS-first approach
16919
+ *
16920
+ * @version 1.1.0
16921
+ * @author sc4rfurry
16922
+ * @license MIT
16923
+ */
16924
+ /**
16925
+ * Generate pure-CSS clamp() rules for fluid typography
16926
+ */
16927
+ function fluidType(minRem, maxRem, options = {}) {
16928
+ const { minViewportPx = 320, maxViewportPx = 1200, lineHeight, containerUnits = false } = options;
16929
+ // Convert rem to px for calculations (assuming 16px base)
16930
+ const minPx = minRem * 16;
16931
+ const maxPx = maxRem * 16;
16932
+ // Calculate slope and y-intercept for linear interpolation
16933
+ const slope = (maxPx - minPx) / (maxViewportPx - minViewportPx);
16934
+ const yIntercept = minPx - slope * minViewportPx;
16935
+ // Generate clamp() function
16936
+ const viewportUnit = containerUnits ? 'cqw' : 'vw';
16937
+ const clampValue = `clamp(${minRem}rem, ${yIntercept / 16}rem + ${slope * 100}${viewportUnit}, ${maxRem}rem)`;
16938
+ let css = `font-size: ${clampValue};`;
16939
+ // Add line-height if specified
16940
+ if (lineHeight) {
16941
+ css += `\nline-height: ${lineHeight};`;
16942
+ }
16943
+ return { css };
16944
+ }
16945
+ /**
16946
+ * Apply fluid typography to elements
16947
+ */
16948
+ function applyFluidType(selector, minRem, maxRem, options = {}) {
16949
+ const { css } = fluidType(minRem, maxRem, options);
16950
+ const styleElement = document.createElement('style');
16951
+ styleElement.textContent = `${selector} {\n ${css.replace(/\n/g, '\n ')}\n}`;
16952
+ styleElement.setAttribute('data-proteus-typography', selector);
16953
+ document.head.appendChild(styleElement);
16954
+ }
16955
+ /**
16956
+ * Create a complete typographic scale
16957
+ */
16958
+ function createTypographicScale(baseSize = 1, ratio = 1.25, steps = 6, options = {}) {
16959
+ const scale = {};
16960
+ for (let i = -2; i <= steps - 3; i++) {
16961
+ const size = baseSize * Math.pow(ratio, i);
16962
+ const minSize = size * 0.8; // 20% smaller at min viewport
16963
+ const maxSize = size * 1.2; // 20% larger at max viewport
16964
+ const stepName = i <= 0 ? `small${Math.abs(i)}` : `large${i}`;
16965
+ scale[stepName] = fluidType(minSize, maxSize, options);
16966
+ }
16967
+ return scale;
16968
+ }
16969
+ /**
16970
+ * Generate CSS custom properties for a typographic scale
16971
+ */
16972
+ function generateScaleCSS(scale, prefix = '--font-size') {
16973
+ const cssVars = Object.entries(scale)
16974
+ .map(([name, result]) => ` ${prefix}-${name}: ${result.css.replace('font-size: ', '').replace(';', '')};`)
16975
+ .join('\n');
16976
+ return `:root {\n${cssVars}\n}`;
16977
+ }
16978
+ /**
16979
+ * Optimize line height for readability
16980
+ */
16981
+ function optimizeLineHeight(fontSize, measure = 65) {
16982
+ // Optimal line height based on font size and measure (characters per line)
16983
+ // Smaller fonts need more line height, larger fonts need less
16984
+ const baseLineHeight = 1.4;
16985
+ const sizeAdjustment = Math.max(0.1, Math.min(0.3, (1 - fontSize) * 0.5));
16986
+ const measureAdjustment = Math.max(-0.1, Math.min(0.1, (65 - measure) * 0.002));
16987
+ return baseLineHeight + sizeAdjustment + measureAdjustment;
16988
+ }
16989
+ /**
16990
+ * Calculate optimal font size for container width
16991
+ */
16992
+ function calculateOptimalSize(containerWidth, targetCharacters = 65, baseCharWidth = 0.5) {
16993
+ // Calculate font size to achieve target characters per line
16994
+ const optimalFontSize = containerWidth / (targetCharacters * baseCharWidth);
16995
+ // Clamp to reasonable bounds (12px to 24px)
16996
+ return Math.max(0.75, Math.min(1.5, optimalFontSize));
16997
+ }
16998
+ /**
16999
+ * Apply responsive typography to an element
17000
+ */
17001
+ function makeResponsive(target, options = {}) {
17002
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
17003
+ if (!targetEl) {
17004
+ throw new Error('Target element not found');
17005
+ }
17006
+ const { minSize = 0.875, maxSize = 1.25, targetCharacters = 65, autoLineHeight = true } = options;
17007
+ // Apply fluid typography
17008
+ const { css } = fluidType(minSize, maxSize);
17009
+ const element = targetEl;
17010
+ // Parse and apply CSS
17011
+ const styles = css.split(';').filter(Boolean);
17012
+ styles.forEach(style => {
17013
+ const [property, value] = style.split(':').map(s => s.trim());
17014
+ if (property && value) {
17015
+ element.style.setProperty(property, value);
17016
+ }
17017
+ });
17018
+ // Auto line height if enabled
17019
+ if (autoLineHeight) {
17020
+ const updateLineHeight = () => {
17021
+ const computedStyle = getComputedStyle(element);
17022
+ const fontSize = parseFloat(computedStyle.fontSize);
17023
+ const containerWidth = element.getBoundingClientRect().width;
17024
+ const charactersPerLine = containerWidth / (fontSize * 0.5);
17025
+ const optimalLineHeight = optimizeLineHeight(fontSize / 16, charactersPerLine);
17026
+ element.style.lineHeight = optimalLineHeight.toString();
17027
+ };
17028
+ updateLineHeight();
17029
+ // Update on resize
17030
+ if ('ResizeObserver' in window) {
17031
+ const resizeObserver = new ResizeObserver(updateLineHeight);
17032
+ resizeObserver.observe(element);
17033
+ // Store cleanup function
17034
+ element._proteusTypographyCleanup = () => {
17035
+ resizeObserver.disconnect();
17036
+ };
17037
+ }
17038
+ }
17039
+ }
17040
+ /**
17041
+ * Remove applied typography styles
17042
+ */
17043
+ function cleanup(target) {
17044
+ if (target) {
17045
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
17046
+ if (targetEl && targetEl._proteusTypographyCleanup) {
17047
+ targetEl._proteusTypographyCleanup();
17048
+ delete targetEl._proteusTypographyCleanup;
17049
+ }
17050
+ }
17051
+ else {
17052
+ // Remove all typography style elements
17053
+ const styleElements = document.querySelectorAll('style[data-proteus-typography]');
17054
+ styleElements.forEach(element => element.remove());
17055
+ }
17056
+ }
17057
+ /**
17058
+ * Check if container query units are supported
17059
+ */
17060
+ function supportsContainerUnits() {
17061
+ return CSS.supports('width', '1cqw');
17062
+ }
17063
+ // Export default object for convenience
17064
+ var index$6 = {
17065
+ fluidType,
17066
+ applyFluidType,
17067
+ createTypographicScale,
17068
+ generateScaleCSS,
17069
+ optimizeLineHeight,
17070
+ calculateOptimalSize,
17071
+ makeResponsive,
17072
+ cleanup,
17073
+ supportsContainerUnits
17074
+ };
17075
+
17076
+ var index$7 = /*#__PURE__*/Object.freeze({
17077
+ __proto__: null,
17078
+ applyFluidType: applyFluidType,
17079
+ calculateOptimalSize: calculateOptimalSize,
17080
+ cleanup: cleanup,
17081
+ createTypographicScale: createTypographicScale,
17082
+ default: index$6,
17083
+ fluidType: fluidType,
17084
+ generateScaleCSS: generateScaleCSS,
17085
+ makeResponsive: makeResponsive,
17086
+ optimizeLineHeight: optimizeLineHeight,
17087
+ supportsContainerUnits: supportsContainerUnits
17088
+ });
17089
+
17090
+ /**
17091
+ * @sc4rfurryx/proteusjs/a11y-audit
17092
+ * Accessibility audits for development (dev-only)
17093
+ *
17094
+ * @version 1.1.0
17095
+ * @author sc4rfurry
17096
+ * @license MIT
17097
+ */
17098
+ /**
17099
+ * Run accessibility audits with actionable output
17100
+ * DEV-ONLY: This module should be tree-shaken in production
17101
+ */
17102
+ async function audit(target = document, options = {}) {
17103
+ // Ensure this only runs in development
17104
+ if (process.env['NODE_ENV'] === 'production') {
17105
+ console.warn('a11y-audit should not be used in production');
17106
+ return {
17107
+ violations: [],
17108
+ passes: 0,
17109
+ incomplete: 0,
17110
+ timestamp: Date.now(),
17111
+ url: window.location.href
17112
+ };
17113
+ }
17114
+ const { rules = ['color-contrast', 'heading-order', 'image-alt', 'label', 'link-name', 'button-name'], format = 'console', openInBrowser = false } = options;
17115
+ const violations = [];
17116
+ let passes = 0;
17117
+ let incomplete = 0;
17118
+ // Basic accessibility checks
17119
+ const checks = {
17120
+ 'color-contrast': checkColorContrast,
17121
+ 'heading-order': checkHeadingOrder,
17122
+ 'image-alt': checkImageAlt,
17123
+ 'label': checkFormLabels,
17124
+ 'link-name': checkLinkNames,
17125
+ 'button-name': checkButtonNames,
17126
+ 'focus-visible': checkFocusVisible,
17127
+ 'aria-labels': checkAriaLabels,
17128
+ 'landmark-roles': checkLandmarkRoles,
17129
+ 'skip-links': checkSkipLinks
17130
+ };
17131
+ // Run selected checks
17132
+ for (const ruleId of rules) {
17133
+ if (checks[ruleId]) {
17134
+ try {
17135
+ const result = await checks[ruleId](target);
17136
+ if (result.violations.length > 0) {
17137
+ violations.push(...result.violations);
17138
+ }
17139
+ else {
17140
+ passes++;
17141
+ }
17142
+ }
17143
+ catch (error) {
17144
+ incomplete++;
17145
+ console.warn(`Failed to run accessibility check: ${ruleId}`, error);
17146
+ }
17147
+ }
17148
+ }
17149
+ const report = {
17150
+ violations,
17151
+ passes,
17152
+ incomplete,
17153
+ timestamp: Date.now(),
17154
+ url: window.location.href
17155
+ };
17156
+ // Output results
17157
+ if (format === 'console') {
17158
+ outputToConsole(report);
17159
+ }
17160
+ if (openInBrowser) {
17161
+ openReportInBrowser(report);
17162
+ }
17163
+ return report;
17164
+ }
17165
+ // Individual check functions
17166
+ async function checkColorContrast(target) {
17167
+ const violations = [];
17168
+ const elements = target.querySelectorAll('*');
17169
+ elements.forEach(element => {
17170
+ const style = getComputedStyle(element);
17171
+ const color = style.color;
17172
+ const backgroundColor = style.backgroundColor;
17173
+ // Simple contrast check (would need more sophisticated implementation)
17174
+ if (color && backgroundColor && color !== 'rgba(0, 0, 0, 0)' && backgroundColor !== 'rgba(0, 0, 0, 0)') {
17175
+ const contrast = calculateContrast(color, backgroundColor);
17176
+ if (contrast < 4.5) {
17177
+ violations.push({
17178
+ id: 'color-contrast',
17179
+ impact: 'serious',
17180
+ nodes: 1,
17181
+ help: 'Elements must have sufficient color contrast',
17182
+ fix: `Increase contrast ratio to at least 4.5:1. Current: ${contrast.toFixed(2)}:1`,
17183
+ elements: [element]
17184
+ });
17185
+ }
17186
+ }
17187
+ });
17188
+ return { violations };
17189
+ }
17190
+ async function checkHeadingOrder(target) {
17191
+ const violations = [];
17192
+ const headings = target.querySelectorAll('h1, h2, h3, h4, h5, h6');
17193
+ let lastLevel = 0;
17194
+ headings.forEach(heading => {
17195
+ const level = parseInt(heading.tagName.charAt(1));
17196
+ if (level > lastLevel + 1) {
17197
+ violations.push({
17198
+ id: 'heading-order',
17199
+ impact: 'moderate',
17200
+ nodes: 1,
17201
+ help: 'Heading levels should only increase by one',
17202
+ fix: `Change ${heading.tagName} to H${lastLevel + 1} or add intermediate headings`,
17203
+ elements: [heading]
17204
+ });
17205
+ }
17206
+ lastLevel = level;
17207
+ });
17208
+ return { violations };
17209
+ }
17210
+ async function checkImageAlt(target) {
17211
+ const violations = [];
17212
+ const images = target.querySelectorAll('img');
17213
+ images.forEach(img => {
17214
+ if (!img.hasAttribute('alt')) {
17215
+ violations.push({
17216
+ id: 'image-alt',
17217
+ impact: 'critical',
17218
+ nodes: 1,
17219
+ help: 'Images must have alternative text',
17220
+ fix: 'Add alt attribute with descriptive text or alt="" for decorative images',
17221
+ elements: [img]
17222
+ });
17223
+ }
17224
+ });
17225
+ return { violations };
17226
+ }
17227
+ async function checkFormLabels(target) {
17228
+ const violations = [];
17229
+ const inputs = target.querySelectorAll('input, select, textarea');
17230
+ inputs.forEach(input => {
17231
+ const hasLabel = input.hasAttribute('aria-label') ||
17232
+ input.hasAttribute('aria-labelledby') ||
17233
+ target.querySelector(`label[for="${input.id}"]`) ||
17234
+ input.closest('label');
17235
+ if (!hasLabel) {
17236
+ violations.push({
17237
+ id: 'label',
17238
+ impact: 'critical',
17239
+ nodes: 1,
17240
+ help: 'Form elements must have labels',
17241
+ fix: 'Add a label element, aria-label, or aria-labelledby attribute',
17242
+ elements: [input]
17243
+ });
17244
+ }
17245
+ });
17246
+ return { violations };
17247
+ }
17248
+ async function checkLinkNames(target) {
17249
+ const violations = [];
17250
+ const links = target.querySelectorAll('a[href]');
17251
+ links.forEach(link => {
17252
+ const text = link.textContent?.trim();
17253
+ const ariaLabel = link.getAttribute('aria-label');
17254
+ if (!text && !ariaLabel) {
17255
+ violations.push({
17256
+ id: 'link-name',
17257
+ impact: 'serious',
17258
+ nodes: 1,
17259
+ help: 'Links must have discernible text',
17260
+ fix: 'Add descriptive text content or aria-label attribute',
17261
+ elements: [link]
17262
+ });
17263
+ }
17264
+ });
17265
+ return { violations };
17266
+ }
17267
+ async function checkButtonNames(target) {
17268
+ const violations = [];
17269
+ const buttons = target.querySelectorAll('button, input[type="button"], input[type="submit"]');
17270
+ buttons.forEach(button => {
17271
+ const text = button.textContent?.trim();
17272
+ const ariaLabel = button.getAttribute('aria-label');
17273
+ const value = button.getAttribute('value');
17274
+ if (!text && !ariaLabel && !value) {
17275
+ violations.push({
17276
+ id: 'button-name',
17277
+ impact: 'serious',
17278
+ nodes: 1,
17279
+ help: 'Buttons must have discernible text',
17280
+ fix: 'Add text content, aria-label, or value attribute',
17281
+ elements: [button]
17282
+ });
17283
+ }
17284
+ });
17285
+ return { violations };
17286
+ }
17287
+ async function checkFocusVisible(target) {
17288
+ const violations = [];
17289
+ const focusableElements = target.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
17290
+ focusableElements.forEach(element => {
17291
+ const styles = getComputedStyle(element);
17292
+ const hasVisibleFocus = styles.outline !== 'none' && styles.outline !== '0px' &&
17293
+ styles.outline !== '0' && styles.outlineWidth !== '0px';
17294
+ // Check if element has custom focus styles
17295
+ const hasCustomFocus = styles.boxShadow.includes('inset') ||
17296
+ styles.border !== styles.borderColor ||
17297
+ element.hasAttribute('data-focus-visible');
17298
+ if (!hasVisibleFocus && !hasCustomFocus) {
17299
+ violations.push({
17300
+ id: 'focus-visible',
17301
+ impact: 'serious',
17302
+ nodes: 1,
17303
+ help: 'Interactive elements must have visible focus indicators',
17304
+ fix: 'Add outline, box-shadow, or other visible focus styles',
17305
+ elements: [element]
17306
+ });
17307
+ }
17308
+ });
17309
+ return { violations };
17310
+ }
17311
+ async function checkAriaLabels(target) {
17312
+ const violations = [];
17313
+ // Check for aria-labelledby pointing to non-existent elements
17314
+ const elementsWithLabelledBy = target.querySelectorAll('[aria-labelledby]');
17315
+ elementsWithLabelledBy.forEach(element => {
17316
+ const labelledBy = element.getAttribute('aria-labelledby');
17317
+ if (labelledBy) {
17318
+ const labelIds = labelledBy.split(' ');
17319
+ const missingIds = labelIds.filter(id => !target.querySelector(`#${id}`));
17320
+ if (missingIds.length > 0) {
17321
+ violations.push({
17322
+ id: 'aria-labelledby-invalid',
17323
+ impact: 'serious',
17324
+ nodes: 1,
17325
+ help: 'aria-labelledby must reference existing elements',
17326
+ fix: `Fix or remove references to missing IDs: ${missingIds.join(', ')}`,
17327
+ elements: [element]
17328
+ });
17329
+ }
17330
+ }
17331
+ });
17332
+ // Check for aria-describedby pointing to non-existent elements
17333
+ const elementsWithDescribedBy = target.querySelectorAll('[aria-describedby]');
17334
+ elementsWithDescribedBy.forEach(element => {
17335
+ const describedBy = element.getAttribute('aria-describedby');
17336
+ if (describedBy) {
17337
+ const descriptionIds = describedBy.split(' ');
17338
+ const missingIds = descriptionIds.filter(id => !target.querySelector(`#${id}`));
17339
+ if (missingIds.length > 0) {
17340
+ violations.push({
17341
+ id: 'aria-describedby-invalid',
17342
+ impact: 'moderate',
17343
+ nodes: 1,
17344
+ help: 'aria-describedby must reference existing elements',
17345
+ fix: `Fix or remove references to missing IDs: ${missingIds.join(', ')}`,
17346
+ elements: [element]
17347
+ });
17348
+ }
17349
+ }
17350
+ });
17351
+ return { violations };
17352
+ }
17353
+ async function checkLandmarkRoles(target) {
17354
+ const violations = [];
17355
+ // Check for missing main landmark
17356
+ const mainElements = target.querySelectorAll('main, [role="main"]');
17357
+ if (mainElements.length === 0) {
17358
+ violations.push({
17359
+ id: 'landmark-main-missing',
17360
+ impact: 'moderate',
17361
+ nodes: 0,
17362
+ help: 'Page should have a main landmark',
17363
+ fix: 'Add a <main> element or role="main" to identify the main content area'
17364
+ });
17365
+ }
17366
+ else if (mainElements.length > 1) {
17367
+ violations.push({
17368
+ id: 'landmark-main-multiple',
17369
+ impact: 'moderate',
17370
+ nodes: mainElements.length,
17371
+ help: 'Page should have only one main landmark',
17372
+ fix: 'Ensure only one main element or role="main" exists per page',
17373
+ elements: Array.from(mainElements)
17374
+ });
17375
+ }
17376
+ // Check for navigation landmarks without labels when multiple exist
17377
+ const navElements = target.querySelectorAll('nav, [role="navigation"]');
17378
+ if (navElements.length > 1) {
17379
+ navElements.forEach(nav => {
17380
+ const hasLabel = nav.hasAttribute('aria-label') ||
17381
+ nav.hasAttribute('aria-labelledby') ||
17382
+ nav.querySelector('h1, h2, h3, h4, h5, h6');
17383
+ if (!hasLabel) {
17384
+ violations.push({
17385
+ id: 'landmark-nav-unlabeled',
17386
+ impact: 'moderate',
17387
+ nodes: 1,
17388
+ help: 'Multiple navigation landmarks should be labeled',
17389
+ fix: 'Add aria-label or aria-labelledby to distinguish navigation areas',
17390
+ elements: [nav]
17391
+ });
17392
+ }
17393
+ });
17394
+ }
17395
+ return { violations };
17396
+ }
17397
+ async function checkSkipLinks(target) {
17398
+ const violations = [];
17399
+ // Check for skip links in documents with navigation
17400
+ const navElements = target.querySelectorAll('nav, [role="navigation"]');
17401
+ const mainElement = target.querySelector('main, [role="main"]');
17402
+ if (navElements.length > 0 && mainElement) {
17403
+ const skipLinks = target.querySelectorAll('a[href^="#"]');
17404
+ const hasSkipToMain = Array.from(skipLinks).some(link => {
17405
+ const href = link.getAttribute('href');
17406
+ return href && (href === '#main' ||
17407
+ href === `#${mainElement.id}` ||
17408
+ link.textContent?.toLowerCase().includes('skip to main') ||
17409
+ link.textContent?.toLowerCase().includes('skip to content'));
17410
+ });
17411
+ if (!hasSkipToMain) {
17412
+ violations.push({
17413
+ id: 'skip-link-missing',
17414
+ impact: 'moderate',
17415
+ nodes: 0,
17416
+ help: 'Page with navigation should have skip links',
17417
+ fix: 'Add a skip link to the main content area for keyboard users'
17418
+ });
17419
+ }
17420
+ }
17421
+ // Check that skip links are properly positioned (should be first focusable element)
17422
+ const firstFocusable = target.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
17423
+ if (firstFocusable && firstFocusable.tagName === 'A') {
17424
+ const href = firstFocusable.getAttribute('href');
17425
+ if (!href?.startsWith('#')) {
17426
+ violations.push({
17427
+ id: 'skip-link-not-first',
17428
+ impact: 'minor',
17429
+ nodes: 1,
17430
+ help: 'Skip links should be the first focusable elements',
17431
+ fix: 'Move skip links to the beginning of the document',
17432
+ elements: [firstFocusable]
17433
+ });
17434
+ }
17435
+ }
17436
+ return { violations };
17437
+ }
17438
+ // Utility functions
17439
+ function calculateContrast(color1, color2) {
17440
+ // Convert colors to RGB values
17441
+ const rgb1 = parseColor(color1);
17442
+ const rgb2 = parseColor(color2);
17443
+ if (!rgb1 || !rgb2)
17444
+ return 4.5; // Fallback if parsing fails
17445
+ // Calculate relative luminance
17446
+ const l1 = getRelativeLuminance(rgb1);
17447
+ const l2 = getRelativeLuminance(rgb2);
17448
+ // Calculate contrast ratio
17449
+ const lighter = Math.max(l1, l2);
17450
+ const darker = Math.min(l1, l2);
17451
+ return (lighter + 0.05) / (darker + 0.05);
17452
+ }
17453
+ function parseColor(color) {
17454
+ // Handle rgb() format
17455
+ const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
17456
+ if (rgbMatch && rgbMatch[1] && rgbMatch[2] && rgbMatch[3]) {
17457
+ return {
17458
+ r: parseInt(rgbMatch[1], 10),
17459
+ g: parseInt(rgbMatch[2], 10),
17460
+ b: parseInt(rgbMatch[3], 10)
17461
+ };
17462
+ }
17463
+ // Handle hex format
17464
+ const hexMatch = color.match(/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
17465
+ if (hexMatch && hexMatch[1] && hexMatch[2] && hexMatch[3]) {
17466
+ return {
17467
+ r: parseInt(hexMatch[1], 16),
17468
+ g: parseInt(hexMatch[2], 16),
17469
+ b: parseInt(hexMatch[3], 16)
17470
+ };
17471
+ }
17472
+ // Handle named colors (basic set)
17473
+ const namedColors = {
17474
+ 'black': { r: 0, g: 0, b: 0 },
17475
+ 'white': { r: 255, g: 255, b: 255 },
17476
+ 'red': { r: 255, g: 0, b: 0 },
17477
+ 'green': { r: 0, g: 128, b: 0 },
17478
+ 'blue': { r: 0, g: 0, b: 255 }
17479
+ };
17480
+ return namedColors[color.toLowerCase()] || null;
17481
+ }
17482
+ function getRelativeLuminance(rgb) {
17483
+ const { r, g, b } = rgb;
17484
+ // Convert to sRGB
17485
+ const rsRGB = r / 255;
17486
+ const gsRGB = g / 255;
17487
+ const bsRGB = b / 255;
17488
+ // Apply gamma correction
17489
+ const rLinear = rsRGB <= 0.03928 ? rsRGB / 12.92 : Math.pow((rsRGB + 0.055) / 1.055, 2.4);
17490
+ const gLinear = gsRGB <= 0.03928 ? gsRGB / 12.92 : Math.pow((gsRGB + 0.055) / 1.055, 2.4);
17491
+ const bLinear = bsRGB <= 0.03928 ? bsRGB / 12.92 : Math.pow((bsRGB + 0.055) / 1.055, 2.4);
17492
+ // Calculate relative luminance
17493
+ return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
17494
+ }
17495
+ function outputToConsole(report) {
17496
+ console.group('🔍 Accessibility Audit Results');
17497
+ if (report.violations.length === 0) {
17498
+ console.log('✅ No accessibility violations found!');
17499
+ }
17500
+ else {
17501
+ console.log(`❌ Found ${report.violations.length} accessibility violations:`);
17502
+ report.violations.forEach(violation => {
17503
+ const emoji = violation.impact === 'critical' ? '🚨' :
17504
+ violation.impact === 'serious' ? '⚠️' :
17505
+ violation.impact === 'moderate' ? '⚡' : 'ℹ️';
17506
+ console.group(`${emoji} ${violation.help}`);
17507
+ console.log(`Impact: ${violation.impact}`);
17508
+ console.log(`Fix: ${violation.fix}`);
17509
+ if (violation.elements) {
17510
+ console.log('Elements:', violation.elements);
17511
+ }
17512
+ console.groupEnd();
17513
+ });
17514
+ }
17515
+ console.log(`✅ ${report.passes} checks passed`);
17516
+ if (report.incomplete > 0) {
17517
+ console.log(`⚠️ ${report.incomplete} checks incomplete`);
17518
+ }
17519
+ console.groupEnd();
17520
+ }
17521
+ function openReportInBrowser(report) {
17522
+ const html = generateHTMLReport(report);
17523
+ const blob = new Blob([html], { type: 'text/html' });
17524
+ const url = URL.createObjectURL(blob);
17525
+ const newWindow = window.open(url, '_blank');
17526
+ if (!newWindow) {
17527
+ console.warn('Could not open report in new window. Please check popup blocker settings.');
17528
+ // Fallback: download the report
17529
+ const link = document.createElement('a');
17530
+ link.href = url;
17531
+ link.download = `proteus-a11y-report-${Date.now()}.html`;
17532
+ link.click();
17533
+ }
17534
+ // Clean up the blob URL after a delay
17535
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
17536
+ }
17537
+ function generateHTMLReport(report) {
17538
+ const violationsList = report.violations.map(violation => `
17539
+ <div class="violation violation--${violation.impact}">
17540
+ <h3>${violation.help}</h3>
17541
+ <p><strong>Impact:</strong> ${violation.impact}</p>
17542
+ <p><strong>Fix:</strong> ${violation.fix}</p>
17543
+ <p><strong>Affected elements:</strong> ${violation.nodes}</p>
17544
+ </div>
17545
+ `).join('');
17546
+ return `
17547
+ <!DOCTYPE html>
17548
+ <html lang="en">
17549
+ <head>
17550
+ <meta charset="UTF-8">
17551
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
17552
+ <title>ProteusJS Accessibility Report</title>
17553
+ <style>
17554
+ body { font-family: system-ui, sans-serif; margin: 2rem; line-height: 1.6; }
17555
+ .header { border-bottom: 2px solid #e5e7eb; padding-bottom: 1rem; margin-bottom: 2rem; }
17556
+ .summary { background: #f3f4f6; padding: 1rem; border-radius: 8px; margin-bottom: 2rem; }
17557
+ .violation { border-left: 4px solid #ef4444; padding: 1rem; margin-bottom: 1rem; background: #fef2f2; }
17558
+ .violation--critical { border-color: #dc2626; background: #fef2f2; }
17559
+ .violation--serious { border-color: #ea580c; background: #fff7ed; }
17560
+ .violation--moderate { border-color: #d97706; background: #fffbeb; }
17561
+ .violation--minor { border-color: #65a30d; background: #f7fee7; }
17562
+ .violation h3 { margin-top: 0; color: #374151; }
17563
+ .no-violations { text-align: center; color: #059669; font-size: 1.25rem; padding: 2rem; }
17564
+ </style>
17565
+ </head>
17566
+ <body>
17567
+ <div class="header">
17568
+ <h1>🌊 ProteusJS Accessibility Report</h1>
17569
+ <p>Generated on ${new Date(report.timestamp).toLocaleString()}</p>
17570
+ <p>URL: ${report.url}</p>
17571
+ </div>
17572
+
17573
+ <div class="summary">
17574
+ <h2>Summary</h2>
17575
+ <p><strong>Violations:</strong> ${report.violations.length}</p>
17576
+ <p><strong>Checks passed:</strong> ${report.passes}</p>
17577
+ <p><strong>Incomplete checks:</strong> ${report.incomplete}</p>
17578
+ </div>
17579
+
17580
+ ${report.violations.length === 0 ?
17581
+ '<div class="no-violations">✅ No accessibility violations found!</div>' :
17582
+ `<h2>Violations</h2>${violationsList}`}
17583
+ </body>
17584
+ </html>`;
17585
+ }
17586
+ // Export default object for convenience
17587
+ var index$4 = {
17588
+ audit
17589
+ };
17590
+
17591
+ var index$5 = /*#__PURE__*/Object.freeze({
17592
+ __proto__: null,
17593
+ audit: audit,
17594
+ default: index$4
17595
+ });
17596
+
17597
+ /**
17598
+ * @sc4rfurryx/proteusjs/a11y-primitives
17599
+ * Headless accessibility patterns (no styles)
17600
+ *
17601
+ * @version 1.1.0
17602
+ * @author sc4rfurry
17603
+ * @license MIT
17604
+ */
17605
+ /**
17606
+ * Dialog primitive with proper ARIA and focus management
17607
+ */
17608
+ function dialog(root, opts = {}) {
17609
+ const rootEl = typeof root === 'string' ? document.querySelector(root) : root;
17610
+ if (!rootEl)
17611
+ throw new Error('Dialog root element not found');
17612
+ const { modal = true, restoreFocus = true } = opts;
17613
+ let isOpen = false;
17614
+ const setup = () => {
17615
+ rootEl.setAttribute('role', 'dialog');
17616
+ if (modal) {
17617
+ rootEl.setAttribute('aria-modal', 'true');
17618
+ }
17619
+ // Ensure dialog is initially hidden
17620
+ if (!rootEl.hasAttribute('hidden')) {
17621
+ rootEl.setAttribute('hidden', '');
17622
+ }
17623
+ };
17624
+ const handleKeyDown = (e) => {
17625
+ if (e.key === 'Escape' && isOpen) ;
17626
+ };
17627
+ setup();
17628
+ document.addEventListener('keydown', handleKeyDown);
17629
+ return {
17630
+ destroy: () => {
17631
+ document.removeEventListener('keydown', handleKeyDown);
17632
+ }
17633
+ };
17634
+ }
17635
+ /**
17636
+ * Tooltip primitive with delay and proper ARIA
17637
+ */
17638
+ function tooltip(trigger, panel, opts = {}) {
17639
+ const triggerEl = typeof trigger === 'string' ? document.querySelector(trigger) : trigger;
17640
+ const panelEl = typeof panel === 'string' ? document.querySelector(panel) : panel;
17641
+ if (!triggerEl || !panelEl) {
17642
+ throw new Error('Both trigger and panel elements must exist');
17643
+ }
17644
+ const { delay = 500 } = opts;
17645
+ let timeoutId = null;
17646
+ let isVisible = false;
17647
+ const setup = () => {
17648
+ const tooltipId = panelEl.id || `tooltip-${Math.random().toString(36).substring(2, 11)}`;
17649
+ panelEl.id = tooltipId;
17650
+ panelEl.setAttribute('role', 'tooltip');
17651
+ triggerEl.setAttribute('aria-describedby', tooltipId);
17652
+ // Initially hidden
17653
+ panelEl.style.display = 'none';
17654
+ };
17655
+ const show = () => {
17656
+ if (isVisible)
17657
+ return;
17658
+ panelEl.style.display = 'block';
17659
+ isVisible = true;
17660
+ };
17661
+ const hide = () => {
17662
+ if (!isVisible)
17663
+ return;
17664
+ panelEl.style.display = 'none';
17665
+ isVisible = false;
17666
+ };
17667
+ const handleMouseEnter = () => {
17668
+ if (timeoutId)
17669
+ clearTimeout(timeoutId);
17670
+ timeoutId = window.setTimeout(show, delay);
17671
+ };
17672
+ const handleMouseLeave = () => {
17673
+ if (timeoutId) {
17674
+ clearTimeout(timeoutId);
17675
+ timeoutId = null;
17676
+ }
17677
+ hide();
17678
+ };
17679
+ const handleFocus = () => {
17680
+ show();
17681
+ };
17682
+ const handleBlur = () => {
17683
+ hide();
17684
+ };
17685
+ setup();
17686
+ triggerEl.addEventListener('mouseenter', handleMouseEnter);
17687
+ triggerEl.addEventListener('mouseleave', handleMouseLeave);
17688
+ triggerEl.addEventListener('focus', handleFocus);
17689
+ triggerEl.addEventListener('blur', handleBlur);
17690
+ return {
17691
+ destroy: () => {
17692
+ if (timeoutId)
17693
+ clearTimeout(timeoutId);
17694
+ triggerEl.removeEventListener('mouseenter', handleMouseEnter);
17695
+ triggerEl.removeEventListener('mouseleave', handleMouseLeave);
17696
+ triggerEl.removeEventListener('focus', handleFocus);
17697
+ triggerEl.removeEventListener('blur', handleBlur);
17698
+ hide();
17699
+ }
17700
+ };
17701
+ }
17702
+ /**
17703
+ * Listbox primitive with keyboard navigation
17704
+ */
17705
+ function listbox(root, opts = {}) {
17706
+ const rootEl = typeof root === 'string' ? document.querySelector(root) : root;
17707
+ if (!rootEl)
17708
+ throw new Error('Listbox root element not found');
17709
+ const { multiselect = false } = opts;
17710
+ let currentIndex = -1;
17711
+ const setup = () => {
17712
+ rootEl.setAttribute('role', 'listbox');
17713
+ if (multiselect) {
17714
+ rootEl.setAttribute('aria-multiselectable', 'true');
17715
+ }
17716
+ // Set up options
17717
+ const options = rootEl.querySelectorAll('[role="option"]');
17718
+ options.forEach((option, _index) => {
17719
+ option.setAttribute('aria-selected', 'false');
17720
+ option.setAttribute('tabindex', '-1');
17721
+ });
17722
+ if (options.length > 0) {
17723
+ options[0]?.setAttribute('tabindex', '0');
17724
+ currentIndex = 0;
17725
+ }
17726
+ };
17727
+ const getOptions = () => rootEl.querySelectorAll('[role="option"]');
17728
+ const setCurrentIndex = (index) => {
17729
+ const options = getOptions();
17730
+ if (index < 0 || index >= options.length)
17731
+ return;
17732
+ // Remove tabindex from all options
17733
+ options.forEach(option => option.setAttribute('tabindex', '-1'));
17734
+ // Set current option
17735
+ currentIndex = index;
17736
+ options[currentIndex]?.setAttribute('tabindex', '0');
17737
+ options[currentIndex]?.focus();
17738
+ };
17739
+ const selectOption = (index) => {
17740
+ const options = getOptions();
17741
+ if (index < 0 || index >= options.length)
17742
+ return;
17743
+ if (multiselect) {
17744
+ const isSelected = options[index]?.getAttribute('aria-selected') === 'true';
17745
+ options[index]?.setAttribute('aria-selected', (!isSelected).toString());
17746
+ }
17747
+ else {
17748
+ // Single select - clear all others
17749
+ options.forEach(option => option.setAttribute('aria-selected', 'false'));
17750
+ options[index]?.setAttribute('aria-selected', 'true');
17751
+ }
17752
+ };
17753
+ const handleKeyDown = (e) => {
17754
+ const keyEvent = e;
17755
+ const options = getOptions();
17756
+ switch (keyEvent.key) {
17757
+ case 'ArrowDown':
17758
+ keyEvent.preventDefault();
17759
+ setCurrentIndex(Math.min(currentIndex + 1, options.length - 1));
17760
+ break;
17761
+ case 'ArrowUp':
17762
+ keyEvent.preventDefault();
17763
+ setCurrentIndex(Math.max(currentIndex - 1, 0));
17764
+ break;
17765
+ case 'Home':
17766
+ keyEvent.preventDefault();
17767
+ setCurrentIndex(0);
17768
+ break;
17769
+ case 'End':
17770
+ keyEvent.preventDefault();
17771
+ setCurrentIndex(options.length - 1);
17772
+ break;
17773
+ case 'Enter':
17774
+ case ' ':
17775
+ keyEvent.preventDefault();
17776
+ selectOption(currentIndex);
17777
+ break;
17778
+ }
17779
+ };
17780
+ const handleClick = (e) => {
17781
+ const target = e.target;
17782
+ const option = target.closest('[role="option"]');
17783
+ if (!option)
17784
+ return;
17785
+ const options = Array.from(getOptions());
17786
+ const index = options.indexOf(option);
17787
+ if (index >= 0) {
17788
+ setCurrentIndex(index);
17789
+ selectOption(index);
17790
+ }
17791
+ };
17792
+ setup();
17793
+ rootEl.addEventListener('keydown', handleKeyDown);
17794
+ rootEl.addEventListener('click', handleClick);
17795
+ return {
17796
+ destroy: () => {
17797
+ rootEl.removeEventListener('keydown', handleKeyDown);
17798
+ rootEl.removeEventListener('click', handleClick);
17799
+ }
17800
+ };
17801
+ }
17802
+ /**
17803
+ * Combobox primitive with filtering and multiselect
17804
+ */
17805
+ function combobox(root, opts = {}) {
17806
+ const rootEl = typeof root === 'string' ? document.querySelector(root) : root;
17807
+ if (!rootEl)
17808
+ throw new Error('Combobox root element not found');
17809
+ const { multiselect = false, filtering: _filtering } = opts;
17810
+ let isOpen = false;
17811
+ const setup = () => {
17812
+ rootEl.setAttribute('role', 'combobox');
17813
+ rootEl.setAttribute('aria-expanded', 'false');
17814
+ if (multiselect) {
17815
+ rootEl.setAttribute('aria-multiselectable', 'true');
17816
+ }
17817
+ };
17818
+ const handleKeyDown = (e) => {
17819
+ const keyEvent = e;
17820
+ switch (keyEvent.key) {
17821
+ case 'ArrowDown':
17822
+ keyEvent.preventDefault();
17823
+ if (!isOpen) {
17824
+ isOpen = true;
17825
+ rootEl.setAttribute('aria-expanded', 'true');
17826
+ }
17827
+ // Navigate options logic would go here
17828
+ break;
17829
+ case 'Escape':
17830
+ keyEvent.preventDefault();
17831
+ isOpen = false;
17832
+ rootEl.setAttribute('aria-expanded', 'false');
17833
+ break;
17834
+ }
17835
+ };
17836
+ setup();
17837
+ rootEl.addEventListener('keydown', handleKeyDown);
17838
+ return {
17839
+ destroy: () => {
17840
+ rootEl.removeEventListener('keydown', handleKeyDown);
17841
+ }
17842
+ };
17843
+ }
17844
+ /**
17845
+ * Tabs primitive with keyboard navigation
17846
+ */
17847
+ function tabs(root) {
17848
+ const rootEl = typeof root === 'string' ? document.querySelector(root) : root;
17849
+ if (!rootEl)
17850
+ throw new Error('Tabs root element not found');
17851
+ let currentIndex = 0;
17852
+ const setup = () => {
17853
+ const tabList = rootEl.querySelector('[role="tablist"]');
17854
+ const tabs = rootEl.querySelectorAll('[role="tab"]');
17855
+ const panels = rootEl.querySelectorAll('[role="tabpanel"]');
17856
+ if (!tabList) {
17857
+ rootEl.setAttribute('role', 'tablist');
17858
+ }
17859
+ tabs.forEach((tab, index) => {
17860
+ tab.setAttribute('tabindex', index === 0 ? '0' : '-1');
17861
+ tab.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
17862
+ });
17863
+ panels.forEach((panel, index) => {
17864
+ panel.setAttribute('hidden', index === 0 ? '' : 'true');
17865
+ });
17866
+ };
17867
+ const handleKeyDown = (e) => {
17868
+ const keyEvent = e;
17869
+ const tabs = Array.from(rootEl.querySelectorAll('[role="tab"]'));
17870
+ switch (keyEvent.key) {
17871
+ case 'ArrowRight':
17872
+ keyEvent.preventDefault();
17873
+ currentIndex = (currentIndex + 1) % tabs.length;
17874
+ activateTab(currentIndex);
17875
+ break;
17876
+ case 'ArrowLeft':
17877
+ keyEvent.preventDefault();
17878
+ currentIndex = currentIndex === 0 ? tabs.length - 1 : currentIndex - 1;
17879
+ activateTab(currentIndex);
17880
+ break;
17881
+ }
17882
+ };
17883
+ const activateTab = (index) => {
17884
+ const tabs = rootEl.querySelectorAll('[role="tab"]');
17885
+ const panels = rootEl.querySelectorAll('[role="tabpanel"]');
17886
+ tabs.forEach((tab, i) => {
17887
+ tab.setAttribute('tabindex', i === index ? '0' : '-1');
17888
+ tab.setAttribute('aria-selected', i === index ? 'true' : 'false');
17889
+ if (i === index) {
17890
+ tab.focus();
17891
+ }
17892
+ });
17893
+ panels.forEach((panel, i) => {
17894
+ if (i === index) {
17895
+ panel.removeAttribute('hidden');
17896
+ }
17897
+ else {
17898
+ panel.setAttribute('hidden', 'true');
17899
+ }
17900
+ });
17901
+ };
17902
+ setup();
17903
+ rootEl.addEventListener('keydown', handleKeyDown);
17904
+ return {
17905
+ destroy: () => {
17906
+ rootEl.removeEventListener('keydown', handleKeyDown);
17907
+ }
17908
+ };
17909
+ }
17910
+ /**
17911
+ * Menu primitive with keyboard navigation
17912
+ */
17913
+ function menu(root) {
17914
+ const rootEl = typeof root === 'string' ? document.querySelector(root) : root;
17915
+ if (!rootEl)
17916
+ throw new Error('Menu root element not found');
17917
+ let currentIndex = -1;
17918
+ const setup = () => {
17919
+ rootEl.setAttribute('role', 'menu');
17920
+ const items = rootEl.querySelectorAll('[role="menuitem"]');
17921
+ items.forEach((item, index) => {
17922
+ item.setAttribute('tabindex', index === 0 ? '0' : '-1');
17923
+ });
17924
+ if (items.length > 0) {
17925
+ currentIndex = 0;
17926
+ }
17927
+ };
17928
+ const handleKeyDown = (e) => {
17929
+ const keyEvent = e;
17930
+ const items = Array.from(rootEl.querySelectorAll('[role="menuitem"]'));
17931
+ switch (keyEvent.key) {
17932
+ case 'ArrowDown':
17933
+ keyEvent.preventDefault();
17934
+ currentIndex = (currentIndex + 1) % items.length;
17935
+ setCurrentItem(currentIndex);
17936
+ break;
17937
+ case 'ArrowUp':
17938
+ keyEvent.preventDefault();
17939
+ currentIndex = currentIndex === 0 ? items.length - 1 : currentIndex - 1;
17940
+ setCurrentItem(currentIndex);
17941
+ break;
17942
+ case 'Enter':
17943
+ case ' ':
17944
+ keyEvent.preventDefault();
17945
+ if (items[currentIndex]) {
17946
+ items[currentIndex].click();
17947
+ }
17948
+ break;
17949
+ }
17950
+ };
17951
+ const setCurrentItem = (index) => {
17952
+ const items = rootEl.querySelectorAll('[role="menuitem"]');
17953
+ items.forEach((item, i) => {
17954
+ item.setAttribute('tabindex', i === index ? '0' : '-1');
17955
+ if (i === index) {
17956
+ item.focus();
17957
+ }
17958
+ });
17959
+ };
17960
+ setup();
17961
+ rootEl.addEventListener('keydown', handleKeyDown);
17962
+ return {
17963
+ destroy: () => {
17964
+ rootEl.removeEventListener('keydown', handleKeyDown);
17965
+ }
17966
+ };
17967
+ }
17968
+ /**
17969
+ * Focus trap utility
17970
+ */
17971
+ function focusTrap(root) {
17972
+ const rootEl = typeof root === 'string' ? document.querySelector(root) : root;
17973
+ if (!rootEl)
17974
+ throw new Error('Focus trap root element not found');
17975
+ let isActive = false;
17976
+ let focusableElements = [];
17977
+ const updateFocusableElements = () => {
17978
+ const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
17979
+ focusableElements = Array.from(rootEl.querySelectorAll(selector));
17980
+ };
17981
+ const handleKeyDown = (e) => {
17982
+ if (!isActive || e.key !== 'Tab')
17983
+ return;
17984
+ updateFocusableElements();
17985
+ if (focusableElements.length === 0)
17986
+ return;
17987
+ const firstElement = focusableElements[0];
17988
+ const lastElement = focusableElements[focusableElements.length - 1];
17989
+ if (e.shiftKey) {
17990
+ if (document.activeElement === firstElement) {
17991
+ e.preventDefault();
17992
+ lastElement.focus();
17993
+ }
17994
+ }
17995
+ else {
17996
+ if (document.activeElement === lastElement) {
17997
+ e.preventDefault();
17998
+ firstElement.focus();
17999
+ }
18000
+ }
18001
+ };
18002
+ const activate = () => {
18003
+ if (isActive)
18004
+ return;
18005
+ isActive = true;
18006
+ updateFocusableElements();
18007
+ if (focusableElements.length > 0) {
18008
+ focusableElements[0].focus();
18009
+ }
18010
+ document.addEventListener('keydown', handleKeyDown);
18011
+ };
18012
+ const deactivate = () => {
18013
+ if (!isActive)
18014
+ return;
18015
+ isActive = false;
18016
+ document.removeEventListener('keydown', handleKeyDown);
18017
+ };
18018
+ return {
18019
+ activate,
18020
+ deactivate
18021
+ };
18022
+ }
18023
+ // Export all functions
18024
+ var index$2 = {
18025
+ dialog,
18026
+ tooltip,
18027
+ combobox,
18028
+ listbox,
18029
+ tabs,
18030
+ menu,
18031
+ focusTrap
18032
+ };
18033
+
18034
+ var index$3 = /*#__PURE__*/Object.freeze({
18035
+ __proto__: null,
18036
+ combobox: combobox,
18037
+ default: index$2,
18038
+ dialog: dialog,
18039
+ focusTrap: focusTrap,
18040
+ listbox: listbox,
18041
+ menu: menu,
18042
+ tabs: tabs,
18043
+ tooltip: tooltip
18044
+ });
18045
+
18046
+ /**
18047
+ * @sc4rfurryx/proteusjs/perf
18048
+ * Performance guardrails and CWV-friendly patterns
18049
+ *
18050
+ * @version 1.1.0
18051
+ * @author sc4rfurry
18052
+ * @license MIT
18053
+ */
18054
+ /**
18055
+ * Apply content-visibility for performance optimization
18056
+ */
18057
+ function contentVisibility(selector, mode = 'auto', opts = {}) {
18058
+ const elements = typeof selector === 'string'
18059
+ ? document.querySelectorAll(selector)
18060
+ : [selector];
18061
+ const { containIntrinsicSize = '1000px 400px' } = opts;
18062
+ elements.forEach(element => {
18063
+ const el = element;
18064
+ el.style.contentVisibility = mode;
18065
+ if (mode === 'auto') {
18066
+ el.style.containIntrinsicSize = containIntrinsicSize;
18067
+ }
18068
+ });
18069
+ }
18070
+ /**
18071
+ * Set fetch priority for resources
18072
+ */
18073
+ function fetchPriority(selector, priority) {
18074
+ const elements = typeof selector === 'string'
18075
+ ? document.querySelectorAll(selector)
18076
+ : [selector];
18077
+ elements.forEach(element => {
18078
+ if (element instanceof HTMLImageElement ||
18079
+ element instanceof HTMLLinkElement ||
18080
+ element instanceof HTMLScriptElement) {
18081
+ element.fetchPriority = priority;
18082
+ }
18083
+ });
18084
+ }
18085
+ /**
18086
+ * Set up speculation rules for prerendering and prefetching
18087
+ */
18088
+ function speculate(opts) {
18089
+ const { prerender = [], prefetch = [], sameOriginOnly = true } = opts;
18090
+ // Check for Speculation Rules API support
18091
+ if (!('supports' in HTMLScriptElement && HTMLScriptElement.supports('speculationrules'))) {
18092
+ console.warn('Speculation Rules API not supported');
18093
+ return;
18094
+ }
18095
+ const rules = {};
18096
+ if (prerender.length > 0) {
18097
+ rules.prerender = prerender.map(url => {
18098
+ const rule = { where: { href_matches: url } };
18099
+ if (sameOriginOnly) {
18100
+ rule.where.href_matches = new URL(url, window.location.origin).href;
18101
+ }
18102
+ return rule;
18103
+ });
18104
+ }
18105
+ if (prefetch.length > 0) {
18106
+ rules.prefetch = prefetch.map(url => {
18107
+ const rule = { where: { href_matches: url } };
18108
+ if (sameOriginOnly) {
18109
+ rule.where.href_matches = new URL(url, window.location.origin).href;
18110
+ }
18111
+ return rule;
18112
+ });
18113
+ }
18114
+ if (Object.keys(rules).length === 0)
18115
+ return;
18116
+ // Create and inject speculation rules script
18117
+ const script = document.createElement('script');
18118
+ script.type = 'speculationrules';
18119
+ script.textContent = JSON.stringify(rules);
18120
+ document.head.appendChild(script);
18121
+ }
18122
+ /**
18123
+ * Yield to browser using scheduler.yield or postTask when available
18124
+ */
18125
+ async function yieldToBrowser() {
18126
+ // Use scheduler.yield if available (Chrome 115+)
18127
+ if ('scheduler' in window && 'yield' in window.scheduler) {
18128
+ return window.scheduler.yield();
18129
+ }
18130
+ // Use scheduler.postTask if available
18131
+ if ('scheduler' in window && 'postTask' in window.scheduler) {
18132
+ return new Promise(resolve => {
18133
+ window.scheduler.postTask(resolve, { priority: 'user-blocking' });
18134
+ });
18135
+ }
18136
+ // Fallback to setTimeout
18137
+ return new Promise(resolve => {
18138
+ setTimeout(resolve, 0);
18139
+ });
18140
+ }
18141
+ /**
18142
+ * Optimize images with loading and decoding hints
18143
+ */
18144
+ function optimizeImages(selector = 'img') {
18145
+ const images = typeof selector === 'string'
18146
+ ? document.querySelectorAll(selector)
18147
+ : [selector];
18148
+ images.forEach(img => {
18149
+ if (!(img instanceof HTMLImageElement))
18150
+ return;
18151
+ // Set loading attribute if not already set
18152
+ if (!img.hasAttribute('loading')) {
18153
+ const rect = img.getBoundingClientRect();
18154
+ const isAboveFold = rect.top < window.innerHeight;
18155
+ img.loading = isAboveFold ? 'eager' : 'lazy';
18156
+ }
18157
+ // Set decoding hint
18158
+ if (!img.hasAttribute('decoding')) {
18159
+ img.decoding = 'async';
18160
+ }
18161
+ // Set fetch priority for above-fold images
18162
+ if (!img.hasAttribute('fetchpriority')) {
18163
+ const rect = img.getBoundingClientRect();
18164
+ const isAboveFold = rect.top < window.innerHeight;
18165
+ if (isAboveFold) {
18166
+ img.fetchPriority = 'high';
18167
+ }
18168
+ }
18169
+ });
18170
+ }
18171
+ /**
18172
+ * Preload critical resources
18173
+ */
18174
+ function preloadCritical(resources) {
18175
+ resources.forEach(({ href, as, type }) => {
18176
+ // Check if already preloaded
18177
+ const existing = document.querySelector(`link[rel="preload"][href="${href}"]`);
18178
+ if (existing)
18179
+ return;
18180
+ const link = document.createElement('link');
18181
+ link.rel = 'preload';
18182
+ link.href = href;
18183
+ link.as = as;
18184
+ if (type) {
18185
+ link.type = type;
18186
+ }
18187
+ document.head.appendChild(link);
18188
+ });
18189
+ }
18190
+ /**
18191
+ * Measure and report Core Web Vitals
18192
+ */
18193
+ function measureCWV() {
18194
+ return new Promise(resolve => {
18195
+ const metrics = {};
18196
+ let metricsCount = 0;
18197
+ const totalMetrics = 3;
18198
+ const checkComplete = () => {
18199
+ metricsCount++;
18200
+ if (metricsCount >= totalMetrics) {
18201
+ resolve(metrics);
18202
+ }
18203
+ };
18204
+ // LCP (Largest Contentful Paint)
18205
+ if ('PerformanceObserver' in window) {
18206
+ try {
18207
+ const lcpObserver = new PerformanceObserver(list => {
18208
+ const entries = list.getEntries();
18209
+ const lastEntry = entries[entries.length - 1];
18210
+ metrics.lcp = lastEntry.startTime;
18211
+ });
18212
+ lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
18213
+ // Stop observing after 10 seconds
18214
+ setTimeout(() => {
18215
+ lcpObserver.disconnect();
18216
+ checkComplete();
18217
+ }, 10000);
18218
+ }
18219
+ catch {
18220
+ checkComplete();
18221
+ }
18222
+ // FID (First Input Delay)
18223
+ try {
18224
+ const fidObserver = new PerformanceObserver(list => {
18225
+ const entries = list.getEntries();
18226
+ entries.forEach((entry) => {
18227
+ metrics.fid = entry.processingStart - entry.startTime;
18228
+ });
18229
+ fidObserver.disconnect();
18230
+ checkComplete();
18231
+ });
18232
+ fidObserver.observe({ entryTypes: ['first-input'] });
18233
+ // If no input after 10 seconds, consider FID as 0
18234
+ setTimeout(() => {
18235
+ if (metrics.fid === undefined) {
18236
+ metrics.fid = 0;
18237
+ fidObserver.disconnect();
18238
+ checkComplete();
18239
+ }
18240
+ }, 10000);
18241
+ }
18242
+ catch {
18243
+ checkComplete();
18244
+ }
18245
+ // CLS (Cumulative Layout Shift)
18246
+ try {
18247
+ let clsValue = 0;
18248
+ const clsObserver = new PerformanceObserver(list => {
18249
+ list.getEntries().forEach((entry) => {
18250
+ if (!entry.hadRecentInput) {
18251
+ clsValue += entry.value;
18252
+ }
18253
+ });
18254
+ metrics.cls = clsValue;
18255
+ });
18256
+ clsObserver.observe({ entryTypes: ['layout-shift'] });
18257
+ // Stop observing after 10 seconds
18258
+ setTimeout(() => {
18259
+ clsObserver.disconnect();
18260
+ checkComplete();
18261
+ }, 10000);
18262
+ }
18263
+ catch {
18264
+ checkComplete();
18265
+ }
18266
+ }
18267
+ else {
18268
+ // Fallback if PerformanceObserver is not supported
18269
+ setTimeout(() => resolve(metrics), 100);
18270
+ }
18271
+ });
18272
+ }
18273
+ // Export boost object to match usage examples in upgrade spec
18274
+ const boost = {
18275
+ contentVisibility,
18276
+ fetchPriority,
18277
+ speculate,
18278
+ yieldToBrowser,
18279
+ optimizeImages,
18280
+ preloadCritical,
18281
+ measureCWV
18282
+ };
18283
+ // Export all functions as named exports and default object
18284
+ var index = {
18285
+ contentVisibility,
18286
+ fetchPriority,
18287
+ speculate,
18288
+ yieldToBrowser,
18289
+ optimizeImages,
18290
+ preloadCritical,
18291
+ measureCWV,
18292
+ boost
18293
+ };
18294
+
18295
+ var index$1 = /*#__PURE__*/Object.freeze({
18296
+ __proto__: null,
18297
+ boost: boost,
18298
+ contentVisibility: contentVisibility,
18299
+ default: index,
18300
+ fetchPriority: fetchPriority,
18301
+ measureCWV: measureCWV,
18302
+ optimizeImages: optimizeImages,
18303
+ preloadCritical: preloadCritical,
18304
+ speculate: speculate,
18305
+ yieldToBrowser: yieldToBrowser
18306
+ });
18307
+
18308
+ /**
18309
+ * ProteusJS - Native-first Web Development Primitives
15999
18310
  * Shape-shifting responsive design that adapts like the sea god himself
16000
18311
  *
16001
- * @version 1.0.0
16002
- * @author ProteusJS Team
18312
+ * @version 1.1.0
18313
+ * @author sc4rfurry
16003
18314
  * @license MIT
16004
18315
  */
16005
- // Core exports
18316
+ // Core exports (legacy compatibility)
16006
18317
  // Constants
16007
- const VERSION = '1.0.0';
18318
+ const VERSION = '1.1.0';
16008
18319
  const LIBRARY_NAME = 'ProteusJS';
16009
18320
 
16010
18321
  exports.LIBRARY_NAME = LIBRARY_NAME;
16011
18322
  exports.ProteusJS = ProteusJS;
16012
18323
  exports.VERSION = VERSION;
18324
+ exports.a11yAudit = index$5;
18325
+ exports.a11yPrimitives = index$3;
18326
+ exports.anchor = index$d;
18327
+ exports.container = index$9;
16013
18328
  exports.default = ProteusJS;
16014
- exports.isSupported = isSupported;
18329
+ exports.isSupported = isSupported$1;
18330
+ exports.perf = index$1;
18331
+ exports.popover = index$b;
18332
+ exports.scroll = index$f;
18333
+ exports.transitions = index$h;
18334
+ exports.typography = index$7;
16015
18335
  exports.version = version;
16016
18336
 
16017
18337
  Object.defineProperty(exports, '__esModule', { value: true });