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