@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
package/dist/proteus.js CHANGED
@@ -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
  (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,1565 @@
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
+ update: updatePosition,
16515
+ destroy
16516
+ };
16517
+ }
16518
+ // Export default object for convenience
16519
+ var index$c = {
16520
+ tether
16521
+ };
16522
+
16523
+ var index$d = /*#__PURE__*/Object.freeze({
16524
+ __proto__: null,
16525
+ default: index$c,
16526
+ tether: tether
16527
+ });
16528
+
16529
+ /**
16530
+ * @sc4rfurryx/proteusjs/popover
16531
+ * HTML Popover API wrapper with robust focus/inert handling
16532
+ *
16533
+ * @version 1.1.0
16534
+ * @author sc4rfurry
16535
+ * @license MIT
16536
+ */
16537
+ /**
16538
+ * Unified API for menus, tooltips, and dialogs using the native Popover API
16539
+ * with robust focus/inert handling
16540
+ */
16541
+ function attach(trigger, panel, opts = {}) {
16542
+ const triggerEl = typeof trigger === 'string' ? document.querySelector(trigger) : trigger;
16543
+ const panelEl = typeof panel === 'string' ? document.querySelector(panel) : panel;
16544
+ if (!triggerEl || !panelEl) {
16545
+ throw new Error('Both trigger and panel elements must exist');
16546
+ }
16547
+ const { type = 'menu', trapFocus = type === 'dialog', restoreFocus = true, closeOnEscape = true, onOpen, onClose } = opts;
16548
+ let isOpen = false;
16549
+ let previousFocus = null;
16550
+ let focusTrap = null;
16551
+ // Check for native Popover API support
16552
+ const hasPopoverAPI = 'popover' in HTMLElement.prototype;
16553
+ // Set up ARIA attributes
16554
+ const setupAria = () => {
16555
+ const panelId = panelEl.id || `popover-${Math.random().toString(36).substr(2, 9)}`;
16556
+ panelEl.id = panelId;
16557
+ triggerEl.setAttribute('aria-expanded', 'false');
16558
+ triggerEl.setAttribute('aria-controls', panelId);
16559
+ if (type === 'menu') {
16560
+ triggerEl.setAttribute('aria-haspopup', 'menu');
16561
+ panelEl.setAttribute('role', 'menu');
16562
+ }
16563
+ else if (type === 'dialog') {
16564
+ triggerEl.setAttribute('aria-haspopup', 'dialog');
16565
+ panelEl.setAttribute('role', 'dialog');
16566
+ panelEl.setAttribute('aria-modal', 'true');
16567
+ }
16568
+ else if (type === 'tooltip') {
16569
+ triggerEl.setAttribute('aria-describedby', panelId);
16570
+ panelEl.setAttribute('role', 'tooltip');
16571
+ }
16572
+ };
16573
+ // Set up native popover if supported
16574
+ const setupNativePopover = () => {
16575
+ if (hasPopoverAPI) {
16576
+ panelEl.popover = type === 'dialog' ? 'manual' : 'auto';
16577
+ triggerEl.setAttribute('popovertarget', panelEl.id);
16578
+ }
16579
+ };
16580
+ // Focus trap implementation
16581
+ class FocusTrap {
16582
+ constructor(container) {
16583
+ this.container = container;
16584
+ this.focusableElements = [];
16585
+ this.handleKeyDown = (e) => {
16586
+ if (e.key !== 'Tab')
16587
+ return;
16588
+ const firstElement = this.focusableElements[0];
16589
+ const lastElement = this.focusableElements[this.focusableElements.length - 1];
16590
+ if (e.shiftKey) {
16591
+ if (document.activeElement === firstElement) {
16592
+ e.preventDefault();
16593
+ lastElement.focus();
16594
+ }
16595
+ }
16596
+ else {
16597
+ if (document.activeElement === lastElement) {
16598
+ e.preventDefault();
16599
+ firstElement.focus();
16600
+ }
16601
+ }
16602
+ };
16603
+ this.updateFocusableElements();
16604
+ }
16605
+ updateFocusableElements() {
16606
+ const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
16607
+ this.focusableElements = Array.from(this.container.querySelectorAll(selector));
16608
+ }
16609
+ activate() {
16610
+ this.updateFocusableElements();
16611
+ if (this.focusableElements.length > 0) {
16612
+ this.focusableElements[0].focus();
16613
+ }
16614
+ document.addEventListener('keydown', this.handleKeyDown);
16615
+ }
16616
+ deactivate() {
16617
+ document.removeEventListener('keydown', this.handleKeyDown);
16618
+ }
16619
+ }
16620
+ const open = () => {
16621
+ if (isOpen)
16622
+ return;
16623
+ if (restoreFocus) {
16624
+ previousFocus = document.activeElement;
16625
+ }
16626
+ if (hasPopoverAPI) {
16627
+ panelEl.showPopover();
16628
+ }
16629
+ else {
16630
+ panelEl.style.display = 'block';
16631
+ panelEl.setAttribute('data-popover-open', 'true');
16632
+ }
16633
+ triggerEl.setAttribute('aria-expanded', 'true');
16634
+ isOpen = true;
16635
+ if (trapFocus) {
16636
+ focusTrap = new FocusTrap(panelEl);
16637
+ focusTrap.activate();
16638
+ }
16639
+ if (onOpen) {
16640
+ onOpen();
16641
+ }
16642
+ };
16643
+ const close = () => {
16644
+ if (!isOpen)
16645
+ return;
16646
+ if (hasPopoverAPI) {
16647
+ panelEl.hidePopover();
16648
+ }
16649
+ else {
16650
+ panelEl.style.display = 'none';
16651
+ panelEl.removeAttribute('data-popover-open');
16652
+ }
16653
+ triggerEl.setAttribute('aria-expanded', 'false');
16654
+ isOpen = false;
16655
+ if (focusTrap) {
16656
+ focusTrap.deactivate();
16657
+ focusTrap = null;
16658
+ }
16659
+ if (restoreFocus && previousFocus) {
16660
+ previousFocus.focus();
16661
+ previousFocus = null;
16662
+ }
16663
+ if (onClose) {
16664
+ onClose();
16665
+ }
16666
+ };
16667
+ const toggle = () => {
16668
+ if (isOpen) {
16669
+ close();
16670
+ }
16671
+ else {
16672
+ open();
16673
+ }
16674
+ };
16675
+ const handleKeyDown = (e) => {
16676
+ if (closeOnEscape && e.key === 'Escape' && isOpen) {
16677
+ e.preventDefault();
16678
+ close();
16679
+ }
16680
+ };
16681
+ const handleClick = (e) => {
16682
+ e.preventDefault();
16683
+ toggle();
16684
+ };
16685
+ const destroy = () => {
16686
+ triggerEl.removeEventListener('click', handleClick);
16687
+ document.removeEventListener('keydown', handleKeyDown);
16688
+ if (focusTrap) {
16689
+ focusTrap.deactivate();
16690
+ }
16691
+ if (isOpen) {
16692
+ close();
16693
+ }
16694
+ };
16695
+ // Initialize
16696
+ setupAria();
16697
+ setupNativePopover();
16698
+ triggerEl.addEventListener('click', handleClick);
16699
+ document.addEventListener('keydown', handleKeyDown);
16700
+ return {
16701
+ open,
16702
+ close,
16703
+ toggle,
16704
+ destroy
16705
+ };
16706
+ }
16707
+ // Export default object for convenience
16708
+ var index$a = {
16709
+ attach
16710
+ };
16711
+
16712
+ var index$b = /*#__PURE__*/Object.freeze({
16713
+ __proto__: null,
16714
+ attach: attach,
16715
+ default: index$a
16716
+ });
16717
+
16718
+ /**
16719
+ * @sc4rfurryx/proteusjs/container
16720
+ * Container/Style Query helpers with visualization devtools
16721
+ *
16722
+ * @version 1.1.0
16723
+ * @author sc4rfurry
16724
+ * @license MIT
16725
+ */
16726
+ /**
16727
+ * Sugar on native container queries with dev visualization
16728
+ */
16729
+ function defineContainer(target, name, opts = {}) {
16730
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16731
+ if (!targetEl) {
16732
+ throw new Error('Target element not found');
16733
+ }
16734
+ const { type = 'size', inlineSize: _inlineSize = true } = opts;
16735
+ const containerName = name || `container-${Math.random().toString(36).substring(2, 11)}`;
16736
+ // Apply container properties
16737
+ const element = targetEl;
16738
+ element.style.containerName = containerName;
16739
+ element.style.containerType = type;
16740
+ // Warn if containment settings are missing
16741
+ const computedStyle = getComputedStyle(element);
16742
+ if (!computedStyle.contain || computedStyle.contain === 'none') {
16743
+ console.warn(`Container "${containerName}" may need explicit containment settings for optimal performance`);
16744
+ }
16745
+ // Dev overlay (only in development)
16746
+ if (process.env['NODE_ENV'] === 'development' || window.__PROTEUS_DEV__) {
16747
+ createDevOverlay(element, containerName);
16748
+ }
16749
+ }
16750
+ /**
16751
+ * Create development overlay showing container bounds and breakpoints
16752
+ */
16753
+ function createDevOverlay(element, name) {
16754
+ const overlay = document.createElement('div');
16755
+ overlay.className = 'proteus-container-overlay';
16756
+ overlay.style.cssText = `
16757
+ position: absolute;
16758
+ top: 0;
16759
+ left: 0;
16760
+ right: 0;
16761
+ bottom: 0;
16762
+ pointer-events: none;
16763
+ border: 2px dashed rgba(255, 0, 255, 0.5);
16764
+ background: rgba(255, 0, 255, 0.05);
16765
+ z-index: 9999;
16766
+ font-family: monospace;
16767
+ font-size: 12px;
16768
+ color: #ff00ff;
16769
+ `;
16770
+ const label = document.createElement('div');
16771
+ label.style.cssText = `
16772
+ position: absolute;
16773
+ top: -20px;
16774
+ left: 0;
16775
+ background: rgba(255, 0, 255, 0.9);
16776
+ color: white;
16777
+ padding: 2px 6px;
16778
+ border-radius: 3px;
16779
+ font-size: 10px;
16780
+ white-space: nowrap;
16781
+ `;
16782
+ label.textContent = `Container: ${name}`;
16783
+ const sizeInfo = document.createElement('div');
16784
+ sizeInfo.style.cssText = `
16785
+ position: absolute;
16786
+ bottom: 2px;
16787
+ right: 2px;
16788
+ background: rgba(0, 0, 0, 0.7);
16789
+ color: white;
16790
+ padding: 2px 4px;
16791
+ border-radius: 2px;
16792
+ font-size: 10px;
16793
+ `;
16794
+ overlay.appendChild(label);
16795
+ overlay.appendChild(sizeInfo);
16796
+ // Position overlay relative to container
16797
+ if (getComputedStyle(element).position === 'static') {
16798
+ element.style.position = 'relative';
16799
+ }
16800
+ element.appendChild(overlay);
16801
+ // Update size info
16802
+ const updateSizeInfo = () => {
16803
+ const rect = element.getBoundingClientRect();
16804
+ sizeInfo.textContent = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
16805
+ };
16806
+ updateSizeInfo();
16807
+ // Update on resize
16808
+ if ('ResizeObserver' in window) {
16809
+ const resizeObserver = new ResizeObserver(updateSizeInfo);
16810
+ resizeObserver.observe(element);
16811
+ }
16812
+ // Store cleanup function
16813
+ element._proteusContainerCleanup = () => {
16814
+ overlay.remove();
16815
+ };
16816
+ }
16817
+ /**
16818
+ * Helper to create container query CSS rules
16819
+ */
16820
+ function createContainerQuery(containerName, condition, styles) {
16821
+ const cssRules = Object.entries(styles)
16822
+ .map(([property, value]) => ` ${property}: ${value};`)
16823
+ .join('\n');
16824
+ return `@container ${containerName} (${condition}) {\n${cssRules}\n}`;
16825
+ }
16826
+ /**
16827
+ * Apply container query styles dynamically
16828
+ */
16829
+ function applyContainerQuery(containerName, condition, styles) {
16830
+ const css = createContainerQuery(containerName, condition, styles);
16831
+ const styleElement = document.createElement('style');
16832
+ styleElement.textContent = css;
16833
+ styleElement.setAttribute('data-proteus-container', containerName);
16834
+ document.head.appendChild(styleElement);
16835
+ }
16836
+ /**
16837
+ * Remove container query styles
16838
+ */
16839
+ function removeContainerQuery(containerName) {
16840
+ const styleElements = document.querySelectorAll(`style[data-proteus-container="${containerName}"]`);
16841
+ styleElements.forEach(element => element.remove());
16842
+ }
16843
+ /**
16844
+ * Get container size information
16845
+ */
16846
+ function getContainerSize(target) {
16847
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16848
+ if (!targetEl) {
16849
+ throw new Error('Target element not found');
16850
+ }
16851
+ const rect = targetEl.getBoundingClientRect();
16852
+ return {
16853
+ width: rect.width,
16854
+ height: rect.height
16855
+ };
16856
+ }
16857
+ /**
16858
+ * Check if container queries are supported
16859
+ */
16860
+ function isSupported() {
16861
+ return CSS.supports('container-type', 'size');
16862
+ }
16863
+ /**
16864
+ * Cleanup container overlays and observers
16865
+ */
16866
+ function cleanup$1(target) {
16867
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
16868
+ if (!targetEl)
16869
+ return;
16870
+ // Call stored cleanup function if it exists
16871
+ const elementWithCleanup = targetEl;
16872
+ if (elementWithCleanup._proteusContainerCleanup) {
16873
+ elementWithCleanup._proteusContainerCleanup();
16874
+ delete elementWithCleanup._proteusContainerCleanup;
16875
+ }
16876
+ }
16877
+ /**
16878
+ * Toggle dev overlay visibility
16879
+ */
16880
+ function toggleDevOverlay(visible) {
16881
+ const overlays = document.querySelectorAll('.proteus-container-overlay');
16882
+ overlays.forEach(overlay => {
16883
+ const element = overlay;
16884
+ if (visible !== undefined) {
16885
+ element.style.display = visible ? 'block' : 'none';
16886
+ }
16887
+ else {
16888
+ element.style.display = element.style.display === 'none' ? 'block' : 'none';
16889
+ }
16890
+ });
16891
+ }
16892
+ // Export default object for convenience
16893
+ var index$8 = {
16894
+ defineContainer,
16895
+ createContainerQuery,
16896
+ applyContainerQuery,
16897
+ removeContainerQuery,
16898
+ getContainerSize,
16899
+ isSupported,
16900
+ cleanup: cleanup$1,
16901
+ toggleDevOverlay
16902
+ };
16903
+
16904
+ var index$9 = /*#__PURE__*/Object.freeze({
16905
+ __proto__: null,
16906
+ applyContainerQuery: applyContainerQuery,
16907
+ cleanup: cleanup$1,
16908
+ createContainerQuery: createContainerQuery,
16909
+ default: index$8,
16910
+ defineContainer: defineContainer,
16911
+ getContainerSize: getContainerSize,
16912
+ isSupported: isSupported,
16913
+ removeContainerQuery: removeContainerQuery,
16914
+ toggleDevOverlay: toggleDevOverlay
16915
+ });
16916
+
16917
+ /**
16918
+ * @sc4rfurryx/proteusjs/typography
16919
+ * Fluid typography with CSS-first approach
16920
+ *
16921
+ * @version 1.1.0
16922
+ * @author sc4rfurry
16923
+ * @license MIT
16924
+ */
16925
+ /**
16926
+ * Generate pure-CSS clamp() rules for fluid typography
16927
+ */
16928
+ function fluidType(minRem, maxRem, options = {}) {
16929
+ const { minViewportPx = 320, maxViewportPx = 1200, lineHeight, containerUnits = false } = options;
16930
+ // Convert rem to px for calculations (assuming 16px base)
16931
+ const minPx = minRem * 16;
16932
+ const maxPx = maxRem * 16;
16933
+ // Calculate slope and y-intercept for linear interpolation
16934
+ const slope = (maxPx - minPx) / (maxViewportPx - minViewportPx);
16935
+ const yIntercept = minPx - slope * minViewportPx;
16936
+ // Generate clamp() function
16937
+ const viewportUnit = containerUnits ? 'cqw' : 'vw';
16938
+ const clampValue = `clamp(${minRem}rem, ${yIntercept / 16}rem + ${slope * 100}${viewportUnit}, ${maxRem}rem)`;
16939
+ let css = `font-size: ${clampValue};`;
16940
+ // Add line-height if specified
16941
+ if (lineHeight) {
16942
+ css += `\nline-height: ${lineHeight};`;
16943
+ }
16944
+ return { css };
16945
+ }
16946
+ /**
16947
+ * Apply fluid typography to elements
16948
+ */
16949
+ function applyFluidType(selector, minRem, maxRem, options = {}) {
16950
+ const { css } = fluidType(minRem, maxRem, options);
16951
+ const styleElement = document.createElement('style');
16952
+ styleElement.textContent = `${selector} {\n ${css.replace(/\n/g, '\n ')}\n}`;
16953
+ styleElement.setAttribute('data-proteus-typography', selector);
16954
+ document.head.appendChild(styleElement);
16955
+ }
16956
+ /**
16957
+ * Create a complete typographic scale
16958
+ */
16959
+ function createTypographicScale(baseSize = 1, ratio = 1.25, steps = 6, options = {}) {
16960
+ const scale = {};
16961
+ for (let i = -2; i <= steps - 3; i++) {
16962
+ const size = baseSize * Math.pow(ratio, i);
16963
+ const minSize = size * 0.8; // 20% smaller at min viewport
16964
+ const maxSize = size * 1.2; // 20% larger at max viewport
16965
+ const stepName = i <= 0 ? `small${Math.abs(i)}` : `large${i}`;
16966
+ scale[stepName] = fluidType(minSize, maxSize, options);
16967
+ }
16968
+ return scale;
16969
+ }
16970
+ /**
16971
+ * Generate CSS custom properties for a typographic scale
16972
+ */
16973
+ function generateScaleCSS(scale, prefix = '--font-size') {
16974
+ const cssVars = Object.entries(scale)
16975
+ .map(([name, result]) => ` ${prefix}-${name}: ${result.css.replace('font-size: ', '').replace(';', '')};`)
16976
+ .join('\n');
16977
+ return `:root {\n${cssVars}\n}`;
16978
+ }
16979
+ /**
16980
+ * Optimize line height for readability
16981
+ */
16982
+ function optimizeLineHeight(fontSize, measure = 65) {
16983
+ // Optimal line height based on font size and measure (characters per line)
16984
+ // Smaller fonts need more line height, larger fonts need less
16985
+ const baseLineHeight = 1.4;
16986
+ const sizeAdjustment = Math.max(0.1, Math.min(0.3, (1 - fontSize) * 0.5));
16987
+ const measureAdjustment = Math.max(-0.1, Math.min(0.1, (65 - measure) * 0.002));
16988
+ return baseLineHeight + sizeAdjustment + measureAdjustment;
16989
+ }
16990
+ /**
16991
+ * Calculate optimal font size for container width
16992
+ */
16993
+ function calculateOptimalSize(containerWidth, targetCharacters = 65, baseCharWidth = 0.5) {
16994
+ // Calculate font size to achieve target characters per line
16995
+ const optimalFontSize = containerWidth / (targetCharacters * baseCharWidth);
16996
+ // Clamp to reasonable bounds (12px to 24px)
16997
+ return Math.max(0.75, Math.min(1.5, optimalFontSize));
16998
+ }
16999
+ /**
17000
+ * Apply responsive typography to an element
17001
+ */
17002
+ function makeResponsive(target, options = {}) {
17003
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
17004
+ if (!targetEl) {
17005
+ throw new Error('Target element not found');
17006
+ }
17007
+ const { minSize = 0.875, maxSize = 1.25, targetCharacters = 65, autoLineHeight = true } = options;
17008
+ // Apply fluid typography
17009
+ const { css } = fluidType(minSize, maxSize);
17010
+ const element = targetEl;
17011
+ // Parse and apply CSS
17012
+ const styles = css.split(';').filter(Boolean);
17013
+ styles.forEach(style => {
17014
+ const [property, value] = style.split(':').map(s => s.trim());
17015
+ if (property && value) {
17016
+ element.style.setProperty(property, value);
17017
+ }
17018
+ });
17019
+ // Auto line height if enabled
17020
+ if (autoLineHeight) {
17021
+ const updateLineHeight = () => {
17022
+ const computedStyle = getComputedStyle(element);
17023
+ const fontSize = parseFloat(computedStyle.fontSize);
17024
+ const containerWidth = element.getBoundingClientRect().width;
17025
+ const charactersPerLine = containerWidth / (fontSize * 0.5);
17026
+ const optimalLineHeight = optimizeLineHeight(fontSize / 16, charactersPerLine);
17027
+ element.style.lineHeight = optimalLineHeight.toString();
17028
+ };
17029
+ updateLineHeight();
17030
+ // Update on resize
17031
+ if ('ResizeObserver' in window) {
17032
+ const resizeObserver = new ResizeObserver(updateLineHeight);
17033
+ resizeObserver.observe(element);
17034
+ // Store cleanup function
17035
+ element._proteusTypographyCleanup = () => {
17036
+ resizeObserver.disconnect();
17037
+ };
17038
+ }
17039
+ }
17040
+ }
17041
+ /**
17042
+ * Remove applied typography styles
17043
+ */
17044
+ function cleanup(target) {
17045
+ if (target) {
17046
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
17047
+ if (targetEl && targetEl._proteusTypographyCleanup) {
17048
+ targetEl._proteusTypographyCleanup();
17049
+ delete targetEl._proteusTypographyCleanup;
17050
+ }
17051
+ }
17052
+ else {
17053
+ // Remove all typography style elements
17054
+ const styleElements = document.querySelectorAll('style[data-proteus-typography]');
17055
+ styleElements.forEach(element => element.remove());
17056
+ }
17057
+ }
17058
+ /**
17059
+ * Check if container query units are supported
17060
+ */
17061
+ function supportsContainerUnits() {
17062
+ return CSS.supports('width', '1cqw');
17063
+ }
17064
+ // Export default object for convenience
17065
+ var index$6 = {
17066
+ fluidType,
17067
+ applyFluidType,
17068
+ createTypographicScale,
17069
+ generateScaleCSS,
17070
+ optimizeLineHeight,
17071
+ calculateOptimalSize,
17072
+ makeResponsive,
17073
+ cleanup,
17074
+ supportsContainerUnits
17075
+ };
17076
+
17077
+ var index$7 = /*#__PURE__*/Object.freeze({
17078
+ __proto__: null,
17079
+ applyFluidType: applyFluidType,
17080
+ calculateOptimalSize: calculateOptimalSize,
17081
+ cleanup: cleanup,
17082
+ createTypographicScale: createTypographicScale,
17083
+ default: index$6,
17084
+ fluidType: fluidType,
17085
+ generateScaleCSS: generateScaleCSS,
17086
+ makeResponsive: makeResponsive,
17087
+ optimizeLineHeight: optimizeLineHeight,
17088
+ supportsContainerUnits: supportsContainerUnits
17089
+ });
17090
+
17091
+ /**
17092
+ * @sc4rfurryx/proteusjs/a11y-audit
17093
+ * Lightweight accessibility audits for development
17094
+ *
17095
+ * @version 1.1.0
17096
+ * @author sc4rfurry
17097
+ * @license MIT
17098
+ */
17099
+ async function audit(target = document, options = {}) {
17100
+ if (typeof window === 'undefined' || process.env['NODE_ENV'] === 'production') {
17101
+ return { violations: [], passes: 0, timestamp: Date.now(), url: '' };
17102
+ }
17103
+ const { rules = ['images', 'headings', 'forms'], format = 'console' } = options;
17104
+ const violations = [];
17105
+ let passes = 0;
17106
+ if (rules.includes('images')) {
17107
+ const imgs = target.querySelectorAll('img:not([alt])');
17108
+ if (imgs.length > 0) {
17109
+ violations.push({
17110
+ id: 'image-alt', impact: 'critical', nodes: imgs.length, help: 'Images need alt text'
17111
+ });
17112
+ }
17113
+ passes += target.querySelectorAll('img[alt]').length;
17114
+ }
17115
+ if (rules.includes('headings')) {
17116
+ const h1s = target.querySelectorAll('h1');
17117
+ if (h1s.length !== 1) {
17118
+ violations.push({
17119
+ id: 'heading-structure', impact: 'moderate', nodes: h1s.length, help: 'Page should have exactly one h1'
17120
+ });
17121
+ }
17122
+ else
17123
+ passes++;
17124
+ }
17125
+ if (rules.includes('forms')) {
17126
+ const unlabeled = target.querySelectorAll('input:not([aria-label]):not([aria-labelledby])');
17127
+ if (unlabeled.length > 0) {
17128
+ violations.push({
17129
+ id: 'form-labels', impact: 'critical', nodes: unlabeled.length, help: 'Form inputs need labels'
17130
+ });
17131
+ }
17132
+ passes += target.querySelectorAll('input[aria-label], input[aria-labelledby]').length;
17133
+ }
17134
+ const report = {
17135
+ violations, passes, timestamp: Date.now(),
17136
+ url: typeof window !== 'undefined' ? window.location.href : ''
17137
+ };
17138
+ if (format === 'console' && violations.length > 0) {
17139
+ console.group('🔍 A11y Audit Results');
17140
+ violations.forEach(v => console.warn(`${v.impact}: ${v.help}`));
17141
+ console.groupEnd();
17142
+ }
17143
+ return report;
17144
+ }
17145
+ var index$4 = { audit };
17146
+
17147
+ var index$5 = /*#__PURE__*/Object.freeze({
17148
+ __proto__: null,
17149
+ audit: audit,
17150
+ default: index$4
17151
+ });
17152
+
17153
+ /**
17154
+ * @sc4rfurryx/proteusjs/a11y-primitives
17155
+ * Lightweight accessibility patterns
17156
+ *
17157
+ * @version 1.1.0
17158
+ * @author sc4rfurry
17159
+ * @license MIT
17160
+ */
17161
+ function dialog(root, opts = {}) {
17162
+ const el = typeof root === 'string' ? document.querySelector(root) : root;
17163
+ if (!el)
17164
+ throw new Error('Dialog element not found');
17165
+ const { modal = true, restoreFocus = true } = opts;
17166
+ const close = () => {
17167
+ };
17168
+ return { destroy: () => close() };
17169
+ }
17170
+ function tooltip(trigger, content, opts = {}) {
17171
+ const { delay = 300 } = opts;
17172
+ let timeout;
17173
+ const show = () => {
17174
+ clearTimeout(timeout);
17175
+ timeout = window.setTimeout(() => {
17176
+ content.setAttribute('role', 'tooltip');
17177
+ trigger.setAttribute('aria-describedby', content.id || 'tooltip');
17178
+ content.style.display = 'block';
17179
+ }, delay);
17180
+ };
17181
+ const hide = () => {
17182
+ clearTimeout(timeout);
17183
+ content.style.display = 'none';
17184
+ trigger.removeAttribute('aria-describedby');
17185
+ };
17186
+ trigger.addEventListener('mouseenter', show);
17187
+ trigger.addEventListener('mouseleave', hide);
17188
+ trigger.addEventListener('focus', show);
17189
+ trigger.addEventListener('blur', hide);
17190
+ return {
17191
+ destroy: () => {
17192
+ clearTimeout(timeout);
17193
+ trigger.removeEventListener('mouseenter', show);
17194
+ trigger.removeEventListener('mouseleave', hide);
17195
+ trigger.removeEventListener('focus', show);
17196
+ trigger.removeEventListener('blur', hide);
17197
+ }
17198
+ };
17199
+ }
17200
+ function focusTrap(container) {
17201
+ const focusable = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
17202
+ const activate = () => {
17203
+ const elements = container.querySelectorAll(focusable);
17204
+ if (elements.length === 0)
17205
+ return;
17206
+ const first = elements[0];
17207
+ const last = elements[elements.length - 1];
17208
+ const handleTab = (e) => {
17209
+ if (e.key !== 'Tab')
17210
+ return;
17211
+ if (e.shiftKey && document.activeElement === first) {
17212
+ e.preventDefault();
17213
+ last.focus();
17214
+ }
17215
+ else if (!e.shiftKey && document.activeElement === last) {
17216
+ e.preventDefault();
17217
+ first.focus();
17218
+ }
17219
+ };
17220
+ container.addEventListener('keydown', handleTab);
17221
+ first.focus();
17222
+ return () => container.removeEventListener('keydown', handleTab);
17223
+ };
17224
+ let deactivate = () => { };
17225
+ return {
17226
+ activate: () => { deactivate = activate() || (() => { }); },
17227
+ deactivate: () => deactivate()
17228
+ };
17229
+ }
17230
+ function menu(container) {
17231
+ const items = container.querySelectorAll('[role="menuitem"]');
17232
+ let currentIndex = 0;
17233
+ const navigate = (e) => {
17234
+ switch (e.key) {
17235
+ case 'ArrowDown':
17236
+ e.preventDefault();
17237
+ currentIndex = (currentIndex + 1) % items.length;
17238
+ items[currentIndex].focus();
17239
+ break;
17240
+ case 'ArrowUp':
17241
+ e.preventDefault();
17242
+ currentIndex = currentIndex === 0 ? items.length - 1 : currentIndex - 1;
17243
+ items[currentIndex].focus();
17244
+ break;
17245
+ case 'Escape':
17246
+ e.preventDefault();
17247
+ container.dispatchEvent(new CustomEvent('menu:close'));
17248
+ break;
17249
+ }
17250
+ };
17251
+ container.setAttribute('role', 'menu');
17252
+ container.addEventListener('keydown', navigate);
17253
+ return {
17254
+ destroy: () => container.removeEventListener('keydown', navigate)
17255
+ };
17256
+ }
17257
+ var index$2 = { dialog, tooltip, focusTrap, menu };
17258
+
17259
+ var index$3 = /*#__PURE__*/Object.freeze({
17260
+ __proto__: null,
17261
+ default: index$2,
17262
+ dialog: dialog,
17263
+ focusTrap: focusTrap,
17264
+ menu: menu,
17265
+ tooltip: tooltip
17266
+ });
17267
+
17268
+ /**
17269
+ * @sc4rfurryx/proteusjs/perf
17270
+ * Performance guardrails and CWV-friendly patterns
17271
+ *
17272
+ * @version 1.1.0
17273
+ * @author sc4rfurry
17274
+ * @license MIT
17275
+ */
17276
+ /**
17277
+ * Apply content-visibility for performance optimization
17278
+ */
17279
+ function contentVisibility(selector, mode = 'auto', opts = {}) {
17280
+ const elements = typeof selector === 'string'
17281
+ ? document.querySelectorAll(selector)
17282
+ : [selector];
17283
+ const { containIntrinsicSize = '1000px 400px' } = opts;
17284
+ elements.forEach(element => {
17285
+ const el = element;
17286
+ el.style.contentVisibility = mode;
17287
+ if (mode === 'auto') {
17288
+ el.style.containIntrinsicSize = containIntrinsicSize;
17289
+ }
17290
+ });
17291
+ }
17292
+ /**
17293
+ * Set fetch priority for resources
17294
+ */
17295
+ function fetchPriority(selector, priority) {
17296
+ const elements = typeof selector === 'string'
17297
+ ? document.querySelectorAll(selector)
17298
+ : [selector];
17299
+ elements.forEach(element => {
17300
+ if (element instanceof HTMLImageElement ||
17301
+ element instanceof HTMLLinkElement ||
17302
+ element instanceof HTMLScriptElement) {
17303
+ element.fetchPriority = priority;
17304
+ }
17305
+ });
17306
+ }
17307
+ /**
17308
+ * Set up speculation rules for prerendering and prefetching
17309
+ */
17310
+ function speculate(opts) {
17311
+ const { prerender = [], prefetch = [], sameOriginOnly = true } = opts;
17312
+ // Check for Speculation Rules API support
17313
+ if (!('supports' in HTMLScriptElement && HTMLScriptElement.supports('speculationrules'))) {
17314
+ console.warn('Speculation Rules API not supported');
17315
+ return;
17316
+ }
17317
+ const rules = {};
17318
+ if (prerender.length > 0) {
17319
+ rules.prerender = prerender.map(url => {
17320
+ const rule = { where: { href_matches: url } };
17321
+ if (sameOriginOnly) {
17322
+ rule.where.href_matches = new URL(url, window.location.origin).href;
17323
+ }
17324
+ return rule;
17325
+ });
17326
+ }
17327
+ if (prefetch.length > 0) {
17328
+ rules.prefetch = prefetch.map(url => {
17329
+ const rule = { where: { href_matches: url } };
17330
+ if (sameOriginOnly) {
17331
+ rule.where.href_matches = new URL(url, window.location.origin).href;
17332
+ }
17333
+ return rule;
17334
+ });
17335
+ }
17336
+ if (Object.keys(rules).length === 0)
17337
+ return;
17338
+ // Create and inject speculation rules script
17339
+ const script = document.createElement('script');
17340
+ script.type = 'speculationrules';
17341
+ script.textContent = JSON.stringify(rules);
17342
+ document.head.appendChild(script);
17343
+ }
17344
+ /**
17345
+ * Yield to browser using scheduler.yield or postTask when available
17346
+ */
17347
+ async function yieldToBrowser() {
17348
+ // Use scheduler.yield if available (Chrome 115+)
17349
+ if ('scheduler' in window && 'yield' in window.scheduler) {
17350
+ return window.scheduler.yield();
17351
+ }
17352
+ // Use scheduler.postTask if available
17353
+ if ('scheduler' in window && 'postTask' in window.scheduler) {
17354
+ return new Promise(resolve => {
17355
+ window.scheduler.postTask(resolve, { priority: 'user-blocking' });
17356
+ });
17357
+ }
17358
+ // Fallback to setTimeout
17359
+ return new Promise(resolve => {
17360
+ setTimeout(resolve, 0);
17361
+ });
17362
+ }
17363
+ /**
17364
+ * Optimize images with loading and decoding hints
17365
+ */
17366
+ function optimizeImages(selector = 'img') {
17367
+ const images = typeof selector === 'string'
17368
+ ? document.querySelectorAll(selector)
17369
+ : [selector];
17370
+ images.forEach(img => {
17371
+ if (!(img instanceof HTMLImageElement))
17372
+ return;
17373
+ // Set loading attribute if not already set
17374
+ if (!img.hasAttribute('loading')) {
17375
+ const rect = img.getBoundingClientRect();
17376
+ const isAboveFold = rect.top < window.innerHeight;
17377
+ img.loading = isAboveFold ? 'eager' : 'lazy';
17378
+ }
17379
+ // Set decoding hint
17380
+ if (!img.hasAttribute('decoding')) {
17381
+ img.decoding = 'async';
17382
+ }
17383
+ // Set fetch priority for above-fold images
17384
+ if (!img.hasAttribute('fetchpriority')) {
17385
+ const rect = img.getBoundingClientRect();
17386
+ const isAboveFold = rect.top < window.innerHeight;
17387
+ if (isAboveFold) {
17388
+ img.fetchPriority = 'high';
17389
+ }
17390
+ }
17391
+ });
17392
+ }
17393
+ /**
17394
+ * Preload critical resources
17395
+ */
17396
+ function preloadCritical(resources) {
17397
+ resources.forEach(({ href, as, type }) => {
17398
+ // Check if already preloaded
17399
+ const existing = document.querySelector(`link[rel="preload"][href="${href}"]`);
17400
+ if (existing)
17401
+ return;
17402
+ const link = document.createElement('link');
17403
+ link.rel = 'preload';
17404
+ link.href = href;
17405
+ link.as = as;
17406
+ if (type) {
17407
+ link.type = type;
17408
+ }
17409
+ document.head.appendChild(link);
17410
+ });
17411
+ }
17412
+ /**
17413
+ * Measure and report Core Web Vitals
17414
+ */
17415
+ function measureCWV() {
17416
+ return new Promise(resolve => {
17417
+ const metrics = {};
17418
+ let metricsCount = 0;
17419
+ const totalMetrics = 3;
17420
+ const checkComplete = () => {
17421
+ metricsCount++;
17422
+ if (metricsCount >= totalMetrics) {
17423
+ resolve(metrics);
17424
+ }
17425
+ };
17426
+ // LCP (Largest Contentful Paint)
17427
+ if ('PerformanceObserver' in window) {
17428
+ try {
17429
+ const lcpObserver = new PerformanceObserver(list => {
17430
+ const entries = list.getEntries();
17431
+ const lastEntry = entries[entries.length - 1];
17432
+ metrics.lcp = lastEntry.startTime;
17433
+ });
17434
+ lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
17435
+ // Stop observing after 10 seconds
17436
+ setTimeout(() => {
17437
+ lcpObserver.disconnect();
17438
+ checkComplete();
17439
+ }, 10000);
17440
+ }
17441
+ catch {
17442
+ checkComplete();
17443
+ }
17444
+ // FID (First Input Delay)
17445
+ try {
17446
+ const fidObserver = new PerformanceObserver(list => {
17447
+ const entries = list.getEntries();
17448
+ entries.forEach((entry) => {
17449
+ metrics.fid = entry.processingStart - entry.startTime;
17450
+ });
17451
+ fidObserver.disconnect();
17452
+ checkComplete();
17453
+ });
17454
+ fidObserver.observe({ entryTypes: ['first-input'] });
17455
+ // If no input after 10 seconds, consider FID as 0
17456
+ setTimeout(() => {
17457
+ if (metrics.fid === undefined) {
17458
+ metrics.fid = 0;
17459
+ fidObserver.disconnect();
17460
+ checkComplete();
17461
+ }
17462
+ }, 10000);
17463
+ }
17464
+ catch {
17465
+ checkComplete();
17466
+ }
17467
+ // CLS (Cumulative Layout Shift)
17468
+ try {
17469
+ let clsValue = 0;
17470
+ const clsObserver = new PerformanceObserver(list => {
17471
+ list.getEntries().forEach((entry) => {
17472
+ if (!entry.hadRecentInput) {
17473
+ clsValue += entry.value;
17474
+ }
17475
+ });
17476
+ metrics.cls = clsValue;
17477
+ });
17478
+ clsObserver.observe({ entryTypes: ['layout-shift'] });
17479
+ // Stop observing after 10 seconds
17480
+ setTimeout(() => {
17481
+ clsObserver.disconnect();
17482
+ checkComplete();
17483
+ }, 10000);
17484
+ }
17485
+ catch {
17486
+ checkComplete();
17487
+ }
17488
+ }
17489
+ else {
17490
+ // Fallback if PerformanceObserver is not supported
17491
+ setTimeout(() => resolve(metrics), 100);
17492
+ }
17493
+ });
17494
+ }
17495
+ // Export boost object to match usage examples in upgrade spec
17496
+ const boost = {
17497
+ contentVisibility,
17498
+ fetchPriority,
17499
+ speculate,
17500
+ yieldToBrowser,
17501
+ optimizeImages,
17502
+ preloadCritical,
17503
+ measureCWV
17504
+ };
17505
+ // Export all functions as named exports and default object
17506
+ var index = {
17507
+ contentVisibility,
17508
+ fetchPriority,
17509
+ speculate,
17510
+ yieldToBrowser,
17511
+ optimizeImages,
17512
+ preloadCritical,
17513
+ measureCWV,
17514
+ boost
17515
+ };
17516
+
17517
+ var index$1 = /*#__PURE__*/Object.freeze({
17518
+ __proto__: null,
17519
+ boost: boost,
17520
+ contentVisibility: contentVisibility,
17521
+ default: index,
17522
+ fetchPriority: fetchPriority,
17523
+ measureCWV: measureCWV,
17524
+ optimizeImages: optimizeImages,
17525
+ preloadCritical: preloadCritical,
17526
+ speculate: speculate,
17527
+ yieldToBrowser: yieldToBrowser
17528
+ });
17529
+
17530
+ /**
17531
+ * ProteusJS - Native-first Web Development Primitives
15999
17532
  * Shape-shifting responsive design that adapts like the sea god himself
16000
17533
  *
16001
- * @version 1.0.0
16002
- * @author ProteusJS Team
17534
+ * @version 1.1.0
17535
+ * @author sc4rfurry
16003
17536
  * @license MIT
16004
17537
  */
16005
- // Core exports
17538
+ // Core exports (legacy compatibility)
16006
17539
  // Constants
16007
- const VERSION = '1.0.0';
17540
+ const VERSION = '1.1.0';
16008
17541
  const LIBRARY_NAME = 'ProteusJS';
16009
17542
 
16010
17543
  exports.LIBRARY_NAME = LIBRARY_NAME;
16011
17544
  exports.ProteusJS = ProteusJS;
16012
17545
  exports.VERSION = VERSION;
17546
+ exports.a11yAudit = index$5;
17547
+ exports.a11yPrimitives = index$3;
17548
+ exports.anchor = index$d;
17549
+ exports.container = index$9;
16013
17550
  exports.default = ProteusJS;
16014
- exports.isSupported = isSupported;
17551
+ exports.isSupported = isSupported$1;
17552
+ exports.perf = index$1;
17553
+ exports.popover = index$b;
17554
+ exports.scroll = index$f;
17555
+ exports.transitions = index$h;
17556
+ exports.typography = index$7;
16015
17557
  exports.version = version;
16016
17558
 
16017
17559
  Object.defineProperty(exports, '__esModule', { value: true });