@sc4rfurryx/proteusjs 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +331 -77
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/react.d.ts +140 -0
- package/dist/adapters/react.esm.js +849 -0
- package/dist/adapters/react.esm.js.map +1 -0
- package/dist/adapters/svelte.d.ts +181 -0
- package/dist/adapters/svelte.esm.js +909 -0
- package/dist/adapters/svelte.esm.js.map +1 -0
- package/dist/adapters/vue.d.ts +205 -0
- package/dist/adapters/vue.esm.js +873 -0
- package/dist/adapters/vue.esm.js.map +1 -0
- package/dist/modules/a11y-audit.d.ts +31 -0
- package/dist/modules/a11y-audit.esm.js +64 -0
- package/dist/modules/a11y-audit.esm.js.map +1 -0
- package/dist/modules/a11y-primitives.d.ts +36 -0
- package/dist/modules/a11y-primitives.esm.js +114 -0
- package/dist/modules/a11y-primitives.esm.js.map +1 -0
- package/dist/modules/anchor.d.ts +30 -0
- package/dist/modules/anchor.esm.js +219 -0
- package/dist/modules/anchor.esm.js.map +1 -0
- package/dist/modules/container.d.ts +60 -0
- package/dist/modules/container.esm.js +194 -0
- package/dist/modules/container.esm.js.map +1 -0
- package/dist/modules/perf.d.ts +82 -0
- package/dist/modules/perf.esm.js +257 -0
- package/dist/modules/perf.esm.js.map +1 -0
- package/dist/modules/popover.d.ts +33 -0
- package/dist/modules/popover.esm.js +191 -0
- package/dist/modules/popover.esm.js.map +1 -0
- package/dist/modules/scroll.d.ts +43 -0
- package/dist/modules/scroll.esm.js +195 -0
- package/dist/modules/scroll.esm.js.map +1 -0
- package/dist/modules/transitions.d.ts +35 -0
- package/dist/modules/transitions.esm.js +120 -0
- package/dist/modules/transitions.esm.js.map +1 -0
- package/dist/modules/typography.d.ts +72 -0
- package/dist/modules/typography.esm.js +168 -0
- package/dist/modules/typography.esm.js.map +1 -0
- package/dist/proteus.cjs.js +1554 -12
- package/dist/proteus.cjs.js.map +1 -1
- package/dist/proteus.d.ts +516 -12
- package/dist/proteus.esm.js +1545 -12
- package/dist/proteus.esm.js.map +1 -1
- package/dist/proteus.esm.min.js +3 -3
- package/dist/proteus.esm.min.js.map +1 -1
- package/dist/proteus.js +1554 -12
- package/dist/proteus.js.map +1 -1
- package/dist/proteus.min.js +3 -3
- package/dist/proteus.min.js.map +1 -1
- package/package.json +69 -7
- package/src/adapters/react.ts +264 -0
- package/src/adapters/svelte.ts +321 -0
- package/src/adapters/vue.ts +268 -0
- package/src/index.ts +33 -6
- package/src/modules/a11y-audit/index.ts +84 -0
- package/src/modules/a11y-primitives/index.ts +152 -0
- package/src/modules/anchor/index.ts +259 -0
- package/src/modules/container/index.ts +230 -0
- package/src/modules/perf/index.ts +291 -0
- package/src/modules/popover/index.ts +238 -0
- package/src/modules/scroll/index.ts +251 -0
- package/src/modules/transitions/index.ts +145 -0
- package/src/modules/typography/index.ts +239 -0
- package/src/utils/version.ts +1 -1
package/package.json
CHANGED
@@ -1,11 +1,67 @@
|
|
1
1
|
{
|
2
2
|
"name": "@sc4rfurryx/proteusjs",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.1.1",
|
4
4
|
"type": "module",
|
5
5
|
"description": "The Modern Web Development Framework for Accessible, Responsive, and High-Performance Applications. Intelligent container queries, fluid typography, WCAG compliance, and performance optimization.",
|
6
6
|
"main": "dist/proteus.js",
|
7
7
|
"module": "dist/proteus.esm.js",
|
8
8
|
"types": "dist/proteus.d.ts",
|
9
|
+
"exports": {
|
10
|
+
".": {
|
11
|
+
"types": "./dist/proteus.d.ts",
|
12
|
+
"import": "./dist/proteus.esm.js",
|
13
|
+
"require": "./dist/proteus.cjs.js"
|
14
|
+
},
|
15
|
+
"./transitions": {
|
16
|
+
"types": "./dist/modules/transitions.d.ts",
|
17
|
+
"import": "./dist/modules/transitions.esm.js"
|
18
|
+
},
|
19
|
+
"./scroll": {
|
20
|
+
"types": "./dist/modules/scroll.d.ts",
|
21
|
+
"import": "./dist/modules/scroll.esm.js"
|
22
|
+
},
|
23
|
+
"./anchor": {
|
24
|
+
"types": "./dist/modules/anchor.d.ts",
|
25
|
+
"import": "./dist/modules/anchor.esm.js"
|
26
|
+
},
|
27
|
+
"./popover": {
|
28
|
+
"types": "./dist/modules/popover.d.ts",
|
29
|
+
"import": "./dist/modules/popover.esm.js"
|
30
|
+
},
|
31
|
+
"./container": {
|
32
|
+
"types": "./dist/modules/container.d.ts",
|
33
|
+
"import": "./dist/modules/container.esm.js"
|
34
|
+
},
|
35
|
+
"./typography": {
|
36
|
+
"types": "./dist/modules/typography.d.ts",
|
37
|
+
"import": "./dist/modules/typography.esm.js"
|
38
|
+
},
|
39
|
+
"./a11y-audit": {
|
40
|
+
"types": "./dist/modules/a11y-audit.d.ts",
|
41
|
+
"import": "./dist/modules/a11y-audit.esm.js"
|
42
|
+
},
|
43
|
+
"./a11y-primitives": {
|
44
|
+
"types": "./dist/modules/a11y-primitives.d.ts",
|
45
|
+
"import": "./dist/modules/a11y-primitives.esm.js"
|
46
|
+
},
|
47
|
+
"./perf": {
|
48
|
+
"types": "./dist/modules/perf.d.ts",
|
49
|
+
"import": "./dist/modules/perf.esm.js"
|
50
|
+
},
|
51
|
+
"./adapters/react": {
|
52
|
+
"types": "./dist/adapters/react.d.ts",
|
53
|
+
"import": "./dist/adapters/react.esm.js"
|
54
|
+
},
|
55
|
+
"./adapters/vue": {
|
56
|
+
"types": "./dist/adapters/vue.d.ts",
|
57
|
+
"import": "./dist/adapters/vue.esm.js"
|
58
|
+
},
|
59
|
+
"./adapters/svelte": {
|
60
|
+
"types": "./dist/adapters/svelte.d.ts",
|
61
|
+
"import": "./dist/adapters/svelte.esm.js"
|
62
|
+
}
|
63
|
+
},
|
64
|
+
"sideEffects": false,
|
9
65
|
"files": [
|
10
66
|
"dist",
|
11
67
|
"src",
|
@@ -27,13 +83,19 @@
|
|
27
83
|
"lint": "eslint src --ext .ts,.tsx",
|
28
84
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
29
85
|
"typecheck": "tsc --noEmit",
|
86
|
+
"typecheck:adapters": "tsc --noEmit -p tsconfig.adapters.json",
|
30
87
|
"validate": "npm run typecheck && npm run lint",
|
31
88
|
"size-check": "echo 'Bundle size: ~50KB gzipped'",
|
32
89
|
"benchmark": "vitest run tests/benchmarks/performance-benchmark.ts",
|
33
90
|
"accessibility": "vitest run tests/validation/accessibility-validation.test.ts",
|
34
91
|
"docs:build": "typedoc src/index.ts",
|
35
|
-
"prepublishOnly": "npm run
|
36
|
-
"release": "npm run validate && npm run build:prod && npm publish"
|
92
|
+
"prepublishOnly": "npm run build:prod",
|
93
|
+
"release": "npm run validate && npm run build:prod && npm publish",
|
94
|
+
"release:patch": "node scripts/release.js patch",
|
95
|
+
"release:minor": "node scripts/release.js minor",
|
96
|
+
"release:major": "node scripts/release.js major",
|
97
|
+
"release:dry": "node scripts/release.js patch --dry-run",
|
98
|
+
"publish:dev": "node scripts/publish-dev.js"
|
37
99
|
},
|
38
100
|
"keywords": [
|
39
101
|
"responsive",
|
@@ -71,8 +133,8 @@
|
|
71
133
|
"@types/node": "^22.0.0",
|
72
134
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
73
135
|
"@typescript-eslint/parser": "^8.0.0",
|
74
|
-
"@vitest/coverage-v8": "^2.
|
75
|
-
"@vitest/ui": "^2.
|
136
|
+
"@vitest/coverage-v8": "^3.2.4",
|
137
|
+
"@vitest/ui": "^3.2.4",
|
76
138
|
"cross-env": "^7.0.3",
|
77
139
|
"eslint": "^9.0.0",
|
78
140
|
"globals": "^16.3.0",
|
@@ -81,7 +143,7 @@
|
|
81
143
|
"rollup-plugin-analyzer": "^4.0.0",
|
82
144
|
"rollup-plugin-dts": "^6.1.1",
|
83
145
|
"typescript": "^5.5.0",
|
84
|
-
"vitest": "^2.
|
146
|
+
"vitest": "^3.2.4"
|
85
147
|
},
|
86
148
|
"engines": {
|
87
149
|
"node": ">=16.0.0"
|
@@ -95,4 +157,4 @@
|
|
95
157
|
"dependencies": {
|
96
158
|
"tslib": "^2.8.1"
|
97
159
|
}
|
98
|
-
}
|
160
|
+
}
|
@@ -0,0 +1,264 @@
|
|
1
|
+
/**
|
2
|
+
* @sc4rfurryx/proteusjs/adapters/react
|
3
|
+
* React hooks and components for ProteusJS
|
4
|
+
*
|
5
|
+
* @version 1.1.0
|
6
|
+
* @author sc4rfurry
|
7
|
+
* @license MIT
|
8
|
+
*/
|
9
|
+
|
10
|
+
import React, { useEffect, useRef, useCallback, RefObject, createElement } from 'react';
|
11
|
+
import { transition, TransitionOptions } from '../modules/transitions';
|
12
|
+
import { scrollAnimate, ScrollAnimateOptions } from '../modules/scroll';
|
13
|
+
import { attach as attachPopover, PopoverOptions, PopoverController } from '../modules/popover';
|
14
|
+
import { tether, TetherOptions, TetherController } from '../modules/anchor';
|
15
|
+
import { defineContainer, ContainerOptions } from '../modules/container';
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Hook for view transitions
|
19
|
+
*/
|
20
|
+
export function useTransition() {
|
21
|
+
return useCallback(async (
|
22
|
+
run: () => Promise<any> | any,
|
23
|
+
opts?: TransitionOptions
|
24
|
+
) => {
|
25
|
+
return transition(run, opts);
|
26
|
+
}, []);
|
27
|
+
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Hook for scroll-driven animations
|
31
|
+
*/
|
32
|
+
export function useScrollAnimate(
|
33
|
+
ref: RefObject<HTMLElement>,
|
34
|
+
opts: ScrollAnimateOptions
|
35
|
+
) {
|
36
|
+
useEffect(() => {
|
37
|
+
if (!ref.current) return;
|
38
|
+
|
39
|
+
scrollAnimate(ref.current, opts);
|
40
|
+
|
41
|
+
return () => {
|
42
|
+
// Cleanup would be handled by the scroll module
|
43
|
+
};
|
44
|
+
}, [ref, opts]);
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Hook for popover functionality
|
49
|
+
*/
|
50
|
+
export function usePopover(
|
51
|
+
triggerRef: RefObject<HTMLElement>,
|
52
|
+
panelRef: RefObject<HTMLElement>,
|
53
|
+
opts?: PopoverOptions
|
54
|
+
): PopoverController | null {
|
55
|
+
const controllerRef = useRef<PopoverController | null>(null);
|
56
|
+
|
57
|
+
useEffect(() => {
|
58
|
+
if (!triggerRef.current || !panelRef.current) return;
|
59
|
+
|
60
|
+
controllerRef.current = attachPopover(triggerRef.current, panelRef.current, opts);
|
61
|
+
|
62
|
+
return () => {
|
63
|
+
if (controllerRef.current) {
|
64
|
+
controllerRef.current.destroy();
|
65
|
+
controllerRef.current = null;
|
66
|
+
}
|
67
|
+
};
|
68
|
+
}, [triggerRef, panelRef, opts]);
|
69
|
+
|
70
|
+
return controllerRef.current;
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* Hook for anchor positioning
|
75
|
+
*/
|
76
|
+
export function useAnchor(
|
77
|
+
floatingRef: RefObject<HTMLElement>,
|
78
|
+
anchorRef: RefObject<HTMLElement>,
|
79
|
+
opts?: Omit<TetherOptions, 'anchor'>
|
80
|
+
): TetherController | null {
|
81
|
+
const controllerRef = useRef<TetherController | null>(null);
|
82
|
+
|
83
|
+
useEffect(() => {
|
84
|
+
if (!floatingRef.current || !anchorRef.current) return;
|
85
|
+
|
86
|
+
controllerRef.current = tether(floatingRef.current, {
|
87
|
+
anchor: anchorRef.current,
|
88
|
+
...opts
|
89
|
+
});
|
90
|
+
|
91
|
+
return () => {
|
92
|
+
if (controllerRef.current) {
|
93
|
+
controllerRef.current.destroy();
|
94
|
+
controllerRef.current = null;
|
95
|
+
}
|
96
|
+
};
|
97
|
+
}, [floatingRef, anchorRef, opts]);
|
98
|
+
|
99
|
+
return controllerRef.current;
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Hook for container queries
|
104
|
+
*/
|
105
|
+
export function useContainer(
|
106
|
+
ref: RefObject<HTMLElement>,
|
107
|
+
name?: string,
|
108
|
+
opts?: ContainerOptions
|
109
|
+
) {
|
110
|
+
useEffect(() => {
|
111
|
+
if (!ref.current) return;
|
112
|
+
|
113
|
+
defineContainer(ref.current, name, opts);
|
114
|
+
}, [ref, name, opts]);
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Hook for performance optimizations
|
119
|
+
*/
|
120
|
+
export function usePerformance(ref: RefObject<HTMLElement>) {
|
121
|
+
useEffect(() => {
|
122
|
+
if (!ref.current) return;
|
123
|
+
|
124
|
+
// Apply basic performance optimizations
|
125
|
+
const element = ref.current;
|
126
|
+
|
127
|
+
// Content visibility for off-screen content
|
128
|
+
const observer = new IntersectionObserver(
|
129
|
+
(entries) => {
|
130
|
+
entries.forEach(entry => {
|
131
|
+
if (entry.isIntersecting) {
|
132
|
+
element.style.contentVisibility = 'visible';
|
133
|
+
} else {
|
134
|
+
element.style.contentVisibility = 'auto';
|
135
|
+
}
|
136
|
+
});
|
137
|
+
},
|
138
|
+
{ rootMargin: '50px' }
|
139
|
+
);
|
140
|
+
|
141
|
+
observer.observe(element);
|
142
|
+
|
143
|
+
return () => {
|
144
|
+
observer.disconnect();
|
145
|
+
};
|
146
|
+
}, [ref]);
|
147
|
+
}
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Hook for accessibility features
|
151
|
+
*/
|
152
|
+
export function useA11y(ref: RefObject<HTMLElement>, options: {
|
153
|
+
announceChanges?: boolean;
|
154
|
+
focusManagement?: boolean;
|
155
|
+
} = {}): void {
|
156
|
+
const { announceChanges = false, focusManagement = true } = options;
|
157
|
+
|
158
|
+
useEffect(() => {
|
159
|
+
if (!ref.current) return;
|
160
|
+
|
161
|
+
const element = ref.current;
|
162
|
+
|
163
|
+
// Basic accessibility enhancements
|
164
|
+
if (focusManagement) {
|
165
|
+
// Ensure focusable elements have visible focus indicators
|
166
|
+
const focusableElements = element.querySelectorAll(
|
167
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
168
|
+
);
|
169
|
+
|
170
|
+
focusableElements.forEach((el: Element) => {
|
171
|
+
const htmlEl = el as HTMLElement;
|
172
|
+
if (!htmlEl.style.outline && !getComputedStyle(htmlEl).outline) {
|
173
|
+
htmlEl.style.outline = '2px solid transparent';
|
174
|
+
htmlEl.style.outlineOffset = '2px';
|
175
|
+
|
176
|
+
htmlEl.addEventListener('focus', () => {
|
177
|
+
htmlEl.style.outline = '2px solid #0066cc';
|
178
|
+
});
|
179
|
+
|
180
|
+
htmlEl.addEventListener('blur', () => {
|
181
|
+
htmlEl.style.outline = '2px solid transparent';
|
182
|
+
});
|
183
|
+
}
|
184
|
+
});
|
185
|
+
}
|
186
|
+
|
187
|
+
if (announceChanges) {
|
188
|
+
// Set up mutation observer for announcing changes
|
189
|
+
const observer = new MutationObserver((mutations) => {
|
190
|
+
mutations.forEach(mutation => {
|
191
|
+
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
192
|
+
// Announce significant changes to screen readers
|
193
|
+
const announcement = document.createElement('div');
|
194
|
+
announcement.setAttribute('aria-live', 'polite');
|
195
|
+
announcement.setAttribute('aria-atomic', 'true');
|
196
|
+
announcement.style.position = 'absolute';
|
197
|
+
announcement.style.left = '-10000px';
|
198
|
+
announcement.textContent = 'Content updated';
|
199
|
+
document.body.appendChild(announcement);
|
200
|
+
|
201
|
+
setTimeout(() => {
|
202
|
+
document.body.removeChild(announcement);
|
203
|
+
}, 1000);
|
204
|
+
}
|
205
|
+
});
|
206
|
+
});
|
207
|
+
|
208
|
+
observer.observe(element, {
|
209
|
+
childList: true,
|
210
|
+
subtree: true
|
211
|
+
});
|
212
|
+
|
213
|
+
return () => {
|
214
|
+
observer.disconnect();
|
215
|
+
};
|
216
|
+
}
|
217
|
+
}, [ref, announceChanges, focusManagement]);
|
218
|
+
}
|
219
|
+
|
220
|
+
/**
|
221
|
+
* Higher-order component for adding ProteusJS features
|
222
|
+
*/
|
223
|
+
export function withProteus<P extends object>(
|
224
|
+
Component: React.ComponentType<P>,
|
225
|
+
features: {
|
226
|
+
container?: { name?: string; options?: ContainerOptions };
|
227
|
+
performance?: boolean;
|
228
|
+
accessibility?: boolean;
|
229
|
+
} = {}
|
230
|
+
) {
|
231
|
+
return function ProteusEnhanced(props: P) {
|
232
|
+
const ref = useRef<HTMLDivElement>(null);
|
233
|
+
|
234
|
+
if (features.container) {
|
235
|
+
useContainer(ref, features.container.name, features.container.options);
|
236
|
+
}
|
237
|
+
|
238
|
+
if (features.performance) {
|
239
|
+
usePerformance(ref);
|
240
|
+
}
|
241
|
+
|
242
|
+
if (features.accessibility) {
|
243
|
+
useA11y(ref);
|
244
|
+
}
|
245
|
+
|
246
|
+
return createElement(
|
247
|
+
'div',
|
248
|
+
{ ref },
|
249
|
+
createElement(Component, props)
|
250
|
+
);
|
251
|
+
};
|
252
|
+
}
|
253
|
+
|
254
|
+
// Export all hooks and utilities
|
255
|
+
export default {
|
256
|
+
useTransition,
|
257
|
+
useScrollAnimate,
|
258
|
+
usePopover,
|
259
|
+
useAnchor,
|
260
|
+
useContainer,
|
261
|
+
usePerformance,
|
262
|
+
useA11y,
|
263
|
+
withProteus
|
264
|
+
};
|
@@ -0,0 +1,321 @@
|
|
1
|
+
/**
|
2
|
+
* @sc4rfurryx/proteusjs/adapters/svelte
|
3
|
+
* Svelte actions and stores for ProteusJS
|
4
|
+
*
|
5
|
+
* @version 1.1.0
|
6
|
+
* @author sc4rfurry
|
7
|
+
* @license MIT
|
8
|
+
*/
|
9
|
+
|
10
|
+
import { writable } from 'svelte/store';
|
11
|
+
import { transition, TransitionOptions } from '../modules/transitions';
|
12
|
+
import { scrollAnimate, ScrollAnimateOptions } from '../modules/scroll';
|
13
|
+
import { attach as attachPopover, PopoverOptions, PopoverController } from '../modules/popover';
|
14
|
+
import { tether, TetherOptions, TetherController } from '../modules/anchor';
|
15
|
+
import { defineContainer, ContainerOptions } from '../modules/container';
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Svelte action for scroll animations
|
19
|
+
*/
|
20
|
+
export function proteusScroll(node: HTMLElement, options: ScrollAnimateOptions) {
|
21
|
+
scrollAnimate(node, options);
|
22
|
+
|
23
|
+
return {
|
24
|
+
update(newOptions: ScrollAnimateOptions) {
|
25
|
+
// Re-apply with new options
|
26
|
+
scrollAnimate(node, newOptions);
|
27
|
+
},
|
28
|
+
destroy() {
|
29
|
+
// Cleanup handled by scroll module
|
30
|
+
}
|
31
|
+
};
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Svelte action for container queries
|
36
|
+
*/
|
37
|
+
export function proteusContainer(
|
38
|
+
node: HTMLElement,
|
39
|
+
options: { name?: string; containerOptions?: ContainerOptions } = {}
|
40
|
+
) {
|
41
|
+
const { name, containerOptions } = options;
|
42
|
+
defineContainer(node, name, containerOptions);
|
43
|
+
|
44
|
+
return {
|
45
|
+
update(newOptions: { name?: string; containerOptions?: ContainerOptions }) {
|
46
|
+
const { name: newName, containerOptions: newContainerOptions } = newOptions;
|
47
|
+
defineContainer(node, newName, newContainerOptions);
|
48
|
+
}
|
49
|
+
};
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
* Svelte action for popover functionality
|
54
|
+
*/
|
55
|
+
export function proteusPopover(
|
56
|
+
node: HTMLElement,
|
57
|
+
options: { panel: HTMLElement | string; popoverOptions?: PopoverOptions }
|
58
|
+
) {
|
59
|
+
let controller: PopoverController | null = null;
|
60
|
+
const { panel, popoverOptions } = options;
|
61
|
+
|
62
|
+
controller = attachPopover(node, panel, popoverOptions);
|
63
|
+
|
64
|
+
return {
|
65
|
+
update(newOptions: { panel: HTMLElement | string; popoverOptions?: PopoverOptions }) {
|
66
|
+
if (controller) {
|
67
|
+
controller.destroy();
|
68
|
+
}
|
69
|
+
controller = attachPopover(node, newOptions.panel, newOptions.popoverOptions);
|
70
|
+
},
|
71
|
+
destroy() {
|
72
|
+
if (controller) {
|
73
|
+
controller.destroy();
|
74
|
+
}
|
75
|
+
}
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
79
|
+
/**
|
80
|
+
* Svelte action for anchor positioning
|
81
|
+
*/
|
82
|
+
export function proteusAnchor(
|
83
|
+
node: HTMLElement,
|
84
|
+
options: TetherOptions
|
85
|
+
) {
|
86
|
+
let controller: TetherController | null = null;
|
87
|
+
|
88
|
+
controller = tether(node, options);
|
89
|
+
|
90
|
+
return {
|
91
|
+
update(newOptions: TetherOptions) {
|
92
|
+
if (controller) {
|
93
|
+
controller.destroy();
|
94
|
+
}
|
95
|
+
controller = tether(node, newOptions);
|
96
|
+
},
|
97
|
+
destroy() {
|
98
|
+
if (controller) {
|
99
|
+
controller.destroy();
|
100
|
+
}
|
101
|
+
}
|
102
|
+
};
|
103
|
+
}
|
104
|
+
|
105
|
+
/**
|
106
|
+
* Svelte action for performance optimizations
|
107
|
+
*/
|
108
|
+
export function proteusPerf(node: HTMLElement) {
|
109
|
+
const observer = new IntersectionObserver(
|
110
|
+
(entries) => {
|
111
|
+
entries.forEach(entry => {
|
112
|
+
if (entry.isIntersecting) {
|
113
|
+
node.style.contentVisibility = 'visible';
|
114
|
+
} else {
|
115
|
+
node.style.contentVisibility = 'auto';
|
116
|
+
}
|
117
|
+
});
|
118
|
+
},
|
119
|
+
{ rootMargin: '50px' }
|
120
|
+
);
|
121
|
+
|
122
|
+
observer.observe(node);
|
123
|
+
|
124
|
+
return {
|
125
|
+
destroy() {
|
126
|
+
observer.disconnect();
|
127
|
+
}
|
128
|
+
};
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Svelte action for accessibility enhancements
|
133
|
+
*/
|
134
|
+
export function proteusA11y(
|
135
|
+
node: HTMLElement,
|
136
|
+
options: { announceChanges?: boolean } = {}
|
137
|
+
) {
|
138
|
+
const { announceChanges = false } = options;
|
139
|
+
|
140
|
+
// Enhance focus indicators
|
141
|
+
const focusableElements = node.querySelectorAll(
|
142
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
143
|
+
);
|
144
|
+
|
145
|
+
const focusHandlers = new Map();
|
146
|
+
|
147
|
+
focusableElements.forEach(element => {
|
148
|
+
const htmlEl = element as HTMLElement;
|
149
|
+
|
150
|
+
const focusHandler = () => {
|
151
|
+
htmlEl.style.outline = '2px solid #0066cc';
|
152
|
+
htmlEl.style.outlineOffset = '2px';
|
153
|
+
};
|
154
|
+
|
155
|
+
const blurHandler = () => {
|
156
|
+
htmlEl.style.outline = 'none';
|
157
|
+
};
|
158
|
+
|
159
|
+
htmlEl.addEventListener('focus', focusHandler);
|
160
|
+
htmlEl.addEventListener('blur', blurHandler);
|
161
|
+
|
162
|
+
focusHandlers.set(htmlEl, { focusHandler, blurHandler });
|
163
|
+
});
|
164
|
+
|
165
|
+
let mutationObserver: MutationObserver | null = null;
|
166
|
+
|
167
|
+
if (announceChanges) {
|
168
|
+
mutationObserver = new MutationObserver(() => {
|
169
|
+
const announcement = document.createElement('div');
|
170
|
+
announcement.setAttribute('aria-live', 'polite');
|
171
|
+
announcement.style.position = 'absolute';
|
172
|
+
announcement.style.left = '-10000px';
|
173
|
+
announcement.textContent = 'Content updated';
|
174
|
+
document.body.appendChild(announcement);
|
175
|
+
|
176
|
+
setTimeout(() => {
|
177
|
+
document.body.removeChild(announcement);
|
178
|
+
}, 1000);
|
179
|
+
});
|
180
|
+
|
181
|
+
mutationObserver.observe(node, { childList: true, subtree: true });
|
182
|
+
}
|
183
|
+
|
184
|
+
return {
|
185
|
+
destroy() {
|
186
|
+
// Clean up focus handlers
|
187
|
+
focusHandlers.forEach(({ focusHandler, blurHandler }, element) => {
|
188
|
+
element.removeEventListener('focus', focusHandler);
|
189
|
+
element.removeEventListener('blur', blurHandler);
|
190
|
+
});
|
191
|
+
|
192
|
+
if (mutationObserver) {
|
193
|
+
mutationObserver.disconnect();
|
194
|
+
}
|
195
|
+
}
|
196
|
+
};
|
197
|
+
}
|
198
|
+
|
199
|
+
/**
|
200
|
+
* Store for managing popover state
|
201
|
+
*/
|
202
|
+
export function createPopover(
|
203
|
+
triggerSelector: string,
|
204
|
+
panelSelector: string,
|
205
|
+
options?: PopoverOptions
|
206
|
+
) {
|
207
|
+
const isOpen = writable(false);
|
208
|
+
let controller: PopoverController | null = null;
|
209
|
+
|
210
|
+
const initialize = () => {
|
211
|
+
const trigger = document.querySelector(triggerSelector);
|
212
|
+
const panel = document.querySelector(panelSelector);
|
213
|
+
|
214
|
+
if (trigger && panel) {
|
215
|
+
controller = attachPopover(trigger, panel, {
|
216
|
+
...options,
|
217
|
+
onOpen: () => {
|
218
|
+
isOpen.set(true);
|
219
|
+
options?.onOpen?.();
|
220
|
+
},
|
221
|
+
onClose: () => {
|
222
|
+
isOpen.set(false);
|
223
|
+
options?.onClose?.();
|
224
|
+
}
|
225
|
+
});
|
226
|
+
}
|
227
|
+
};
|
228
|
+
|
229
|
+
const open = () => controller?.open();
|
230
|
+
const close = () => controller?.close();
|
231
|
+
const toggle = () => controller?.toggle();
|
232
|
+
const destroy = () => {
|
233
|
+
if (controller) {
|
234
|
+
controller.destroy();
|
235
|
+
controller = null;
|
236
|
+
}
|
237
|
+
};
|
238
|
+
|
239
|
+
return {
|
240
|
+
isOpen,
|
241
|
+
initialize,
|
242
|
+
open,
|
243
|
+
close,
|
244
|
+
toggle,
|
245
|
+
destroy
|
246
|
+
};
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* Store for managing transition state
|
251
|
+
*/
|
252
|
+
export function createTransition() {
|
253
|
+
const isTransitioning = writable(false);
|
254
|
+
|
255
|
+
const runTransition = async (
|
256
|
+
run: () => Promise<any> | any,
|
257
|
+
opts?: TransitionOptions
|
258
|
+
) => {
|
259
|
+
isTransitioning.set(true);
|
260
|
+
try {
|
261
|
+
await transition(run, opts);
|
262
|
+
} finally {
|
263
|
+
isTransitioning.set(false);
|
264
|
+
}
|
265
|
+
};
|
266
|
+
|
267
|
+
return {
|
268
|
+
isTransitioning,
|
269
|
+
runTransition
|
270
|
+
};
|
271
|
+
}
|
272
|
+
|
273
|
+
/**
|
274
|
+
* Utility function to create reactive container size store
|
275
|
+
*/
|
276
|
+
export function createContainerSize(element: HTMLElement) {
|
277
|
+
const size = writable({ width: 0, height: 0 });
|
278
|
+
|
279
|
+
if ('ResizeObserver' in window) {
|
280
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
281
|
+
for (const entry of entries) {
|
282
|
+
const { width, height } = entry.contentRect;
|
283
|
+
size.set({ width, height });
|
284
|
+
}
|
285
|
+
});
|
286
|
+
|
287
|
+
resizeObserver.observe(element);
|
288
|
+
|
289
|
+
return {
|
290
|
+
size,
|
291
|
+
destroy: () => resizeObserver.disconnect()
|
292
|
+
};
|
293
|
+
}
|
294
|
+
|
295
|
+
// Fallback for browsers without ResizeObserver
|
296
|
+
const updateSize = () => {
|
297
|
+
const rect = element.getBoundingClientRect();
|
298
|
+
size.set({ width: rect.width, height: rect.height });
|
299
|
+
};
|
300
|
+
|
301
|
+
updateSize();
|
302
|
+
(window as any).addEventListener('resize', updateSize);
|
303
|
+
|
304
|
+
return {
|
305
|
+
size,
|
306
|
+
destroy: () => (window as any).removeEventListener('resize', updateSize)
|
307
|
+
};
|
308
|
+
}
|
309
|
+
|
310
|
+
// Export all actions and utilities
|
311
|
+
export default {
|
312
|
+
proteusScroll,
|
313
|
+
proteusContainer,
|
314
|
+
proteusPopover,
|
315
|
+
proteusAnchor,
|
316
|
+
proteusPerf,
|
317
|
+
proteusA11y,
|
318
|
+
createPopover,
|
319
|
+
createTransition,
|
320
|
+
createContainerSize
|
321
|
+
};
|