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