@sc4rfurryx/proteusjs 1.0.0 → 1.1.1

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