@sc4rfurryx/proteusjs 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +331 -77
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/react.d.ts +140 -0
  5. package/dist/adapters/react.esm.js +849 -0
  6. package/dist/adapters/react.esm.js.map +1 -0
  7. package/dist/adapters/svelte.d.ts +181 -0
  8. package/dist/adapters/svelte.esm.js +909 -0
  9. package/dist/adapters/svelte.esm.js.map +1 -0
  10. package/dist/adapters/vue.d.ts +205 -0
  11. package/dist/adapters/vue.esm.js +873 -0
  12. package/dist/adapters/vue.esm.js.map +1 -0
  13. package/dist/modules/a11y-audit.d.ts +31 -0
  14. package/dist/modules/a11y-audit.esm.js +64 -0
  15. package/dist/modules/a11y-audit.esm.js.map +1 -0
  16. package/dist/modules/a11y-primitives.d.ts +36 -0
  17. package/dist/modules/a11y-primitives.esm.js +114 -0
  18. package/dist/modules/a11y-primitives.esm.js.map +1 -0
  19. package/dist/modules/anchor.d.ts +30 -0
  20. package/dist/modules/anchor.esm.js +219 -0
  21. package/dist/modules/anchor.esm.js.map +1 -0
  22. package/dist/modules/container.d.ts +60 -0
  23. package/dist/modules/container.esm.js +194 -0
  24. package/dist/modules/container.esm.js.map +1 -0
  25. package/dist/modules/perf.d.ts +82 -0
  26. package/dist/modules/perf.esm.js +257 -0
  27. package/dist/modules/perf.esm.js.map +1 -0
  28. package/dist/modules/popover.d.ts +33 -0
  29. package/dist/modules/popover.esm.js +191 -0
  30. package/dist/modules/popover.esm.js.map +1 -0
  31. package/dist/modules/scroll.d.ts +43 -0
  32. package/dist/modules/scroll.esm.js +195 -0
  33. package/dist/modules/scroll.esm.js.map +1 -0
  34. package/dist/modules/transitions.d.ts +35 -0
  35. package/dist/modules/transitions.esm.js +120 -0
  36. package/dist/modules/transitions.esm.js.map +1 -0
  37. package/dist/modules/typography.d.ts +72 -0
  38. package/dist/modules/typography.esm.js +168 -0
  39. package/dist/modules/typography.esm.js.map +1 -0
  40. package/dist/proteus.cjs.js +1554 -12
  41. package/dist/proteus.cjs.js.map +1 -1
  42. package/dist/proteus.d.ts +516 -12
  43. package/dist/proteus.esm.js +1545 -12
  44. package/dist/proteus.esm.js.map +1 -1
  45. package/dist/proteus.esm.min.js +3 -3
  46. package/dist/proteus.esm.min.js.map +1 -1
  47. package/dist/proteus.js +1554 -12
  48. package/dist/proteus.js.map +1 -1
  49. package/dist/proteus.min.js +3 -3
  50. package/dist/proteus.min.js.map +1 -1
  51. package/package.json +69 -7
  52. package/src/adapters/react.ts +264 -0
  53. package/src/adapters/svelte.ts +321 -0
  54. package/src/adapters/vue.ts +268 -0
  55. package/src/index.ts +33 -6
  56. package/src/modules/a11y-audit/index.ts +84 -0
  57. package/src/modules/a11y-primitives/index.ts +152 -0
  58. package/src/modules/anchor/index.ts +259 -0
  59. package/src/modules/container/index.ts +230 -0
  60. package/src/modules/perf/index.ts +291 -0
  61. package/src/modules/popover/index.ts +238 -0
  62. package/src/modules/scroll/index.ts +251 -0
  63. package/src/modules/transitions/index.ts +145 -0
  64. package/src/modules/typography/index.ts +239 -0
  65. package/src/utils/version.ts +1 -1
@@ -0,0 +1,268 @@
1
+ /**
2
+ * @sc4rfurryx/proteusjs/adapters/vue
3
+ * Vue composables and directives for ProteusJS
4
+ *
5
+ * @version 1.1.0
6
+ * @author sc4rfurry
7
+ * @license MIT
8
+ */
9
+
10
+ import { ref, onMounted, onUnmounted, Ref } from 'vue';
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
+ * Composable for view transitions
19
+ */
20
+ export function useTransition() {
21
+ return {
22
+ transition: async (run: () => Promise<any> | any, opts?: TransitionOptions) => {
23
+ return transition(run, opts);
24
+ }
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Composable for scroll animations
30
+ */
31
+ export function useScrollAnimate(
32
+ elementRef: Ref<HTMLElement | undefined>,
33
+ opts: ScrollAnimateOptions
34
+ ) {
35
+ onMounted(() => {
36
+ if (elementRef.value) {
37
+ scrollAnimate(elementRef.value, opts);
38
+ }
39
+ });
40
+
41
+ return {
42
+ elementRef
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Composable for popover functionality
48
+ */
49
+ export function usePopover(
50
+ triggerRef: Ref<HTMLElement | undefined>,
51
+ panelRef: Ref<HTMLElement | undefined>,
52
+ opts?: PopoverOptions
53
+ ) {
54
+ const controller = ref<PopoverController | null>(null);
55
+ const isOpen = ref(false);
56
+
57
+ onMounted(() => {
58
+ if (triggerRef.value && panelRef.value) {
59
+ controller.value = attachPopover(triggerRef.value, panelRef.value, {
60
+ ...opts,
61
+ onOpen: () => {
62
+ isOpen.value = true;
63
+ opts?.onOpen?.();
64
+ },
65
+ onClose: () => {
66
+ isOpen.value = false;
67
+ opts?.onClose?.();
68
+ }
69
+ });
70
+ }
71
+ });
72
+
73
+ onUnmounted(() => {
74
+ if (controller.value) {
75
+ controller.value.destroy();
76
+ }
77
+ });
78
+
79
+ const open = () => controller.value?.open();
80
+ const close = () => controller.value?.close();
81
+ const toggle = () => controller.value?.toggle();
82
+
83
+ return {
84
+ isOpen,
85
+ open,
86
+ close,
87
+ toggle
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Composable for anchor positioning
93
+ */
94
+ export function useAnchor(
95
+ floatingRef: Ref<HTMLElement | undefined>,
96
+ anchorRef: Ref<HTMLElement | undefined>,
97
+ opts?: Omit<TetherOptions, 'anchor'>
98
+ ) {
99
+ const controller = ref<TetherController | null>(null);
100
+
101
+ onMounted(() => {
102
+ if (floatingRef.value && anchorRef.value) {
103
+ controller.value = tether(floatingRef.value, {
104
+ anchor: anchorRef.value,
105
+ ...opts
106
+ });
107
+ }
108
+ });
109
+
110
+ onUnmounted(() => {
111
+ if (controller.value) {
112
+ controller.value.destroy();
113
+ }
114
+ });
115
+
116
+ return {
117
+ controller
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Composable for container queries
123
+ */
124
+ export function useContainer(
125
+ elementRef: Ref<HTMLElement | undefined>,
126
+ name?: string,
127
+ opts?: ContainerOptions
128
+ ) {
129
+ onMounted(() => {
130
+ if (elementRef.value) {
131
+ defineContainer(elementRef.value, name, opts);
132
+ }
133
+ });
134
+
135
+ return {
136
+ elementRef
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Vue directive for scroll animations
142
+ */
143
+ export const vProteusScroll = {
144
+ mounted(el: HTMLElement, binding: { value: ScrollAnimateOptions }) {
145
+ scrollAnimate(el, binding.value);
146
+ }
147
+ };
148
+
149
+ /**
150
+ * Vue directive for container queries
151
+ */
152
+ export const vProteusContainer = {
153
+ mounted(el: HTMLElement, binding: { value?: { name?: string; options?: ContainerOptions } }) {
154
+ const { name, options } = binding.value || {};
155
+ defineContainer(el, name, options);
156
+ }
157
+ };
158
+
159
+ /**
160
+ * Vue directive for performance optimizations
161
+ */
162
+ export const vProteusPerf = {
163
+ mounted(el: HTMLElement) {
164
+ // Apply content visibility optimization
165
+ const observer = new IntersectionObserver(
166
+ (entries) => {
167
+ entries.forEach(entry => {
168
+ if (entry.isIntersecting) {
169
+ el.style.contentVisibility = 'visible';
170
+ } else {
171
+ el.style.contentVisibility = 'auto';
172
+ }
173
+ });
174
+ },
175
+ { rootMargin: '50px' }
176
+ );
177
+
178
+ observer.observe(el);
179
+
180
+ // Store cleanup function
181
+ (el as any)._proteusCleanup = () => {
182
+ observer.disconnect();
183
+ };
184
+ },
185
+ unmounted(el: HTMLElement) {
186
+ if ((el as any)._proteusCleanup) {
187
+ (el as any)._proteusCleanup();
188
+ }
189
+ }
190
+ };
191
+
192
+ /**
193
+ * Vue directive for accessibility enhancements
194
+ */
195
+ export const vProteusA11y = {
196
+ mounted(el: HTMLElement, binding: { value?: { announceChanges?: boolean } }) {
197
+ const { announceChanges = false } = binding.value || {};
198
+
199
+ // Enhance focus indicators
200
+ const focusableElements = el.querySelectorAll(
201
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
202
+ );
203
+
204
+ focusableElements.forEach(element => {
205
+ const htmlEl = element as HTMLElement;
206
+ htmlEl.addEventListener('focus', () => {
207
+ htmlEl.style.outline = '2px solid #0066cc';
208
+ htmlEl.style.outlineOffset = '2px';
209
+ });
210
+
211
+ htmlEl.addEventListener('blur', () => {
212
+ htmlEl.style.outline = 'none';
213
+ });
214
+ });
215
+
216
+ if (announceChanges) {
217
+ const observer = new MutationObserver(() => {
218
+ const announcement = document.createElement('div');
219
+ announcement.setAttribute('aria-live', 'polite');
220
+ announcement.style.position = 'absolute';
221
+ announcement.style.left = '-10000px';
222
+ announcement.textContent = 'Content updated';
223
+ document.body.appendChild(announcement);
224
+
225
+ setTimeout(() => {
226
+ document.body.removeChild(announcement);
227
+ }, 1000);
228
+ });
229
+
230
+ observer.observe(el, { childList: true, subtree: true });
231
+
232
+ (el as any)._proteusA11yCleanup = () => {
233
+ observer.disconnect();
234
+ };
235
+ }
236
+ },
237
+ unmounted(el: HTMLElement) {
238
+ if ((el as any)._proteusA11yCleanup) {
239
+ (el as any)._proteusA11yCleanup();
240
+ }
241
+ }
242
+ };
243
+
244
+ /**
245
+ * Plugin for Vue 3
246
+ */
247
+ export const ProteusPlugin = {
248
+ install(app: any) {
249
+ app.directive('proteus-scroll', vProteusScroll);
250
+ app.directive('proteus-container', vProteusContainer);
251
+ app.directive('proteus-perf', vProteusPerf);
252
+ app.directive('proteus-a11y', vProteusA11y);
253
+ }
254
+ };
255
+
256
+ // Export all composables and directives
257
+ export default {
258
+ useTransition,
259
+ useScrollAnimate,
260
+ usePopover,
261
+ useAnchor,
262
+ useContainer,
263
+ vProteusScroll,
264
+ vProteusContainer,
265
+ vProteusPerf,
266
+ vProteusA11y,
267
+ ProteusPlugin
268
+ };
package/src/index.ts CHANGED
@@ -1,16 +1,32 @@
1
1
  /**
2
- * ProteusJS - Dynamic Responsive Design Library
2
+ * ProteusJS - Native-first Web Development Primitives
3
3
  * Shape-shifting responsive design that adapts like the sea god himself
4
- *
5
- * @version 1.0.0
6
- * @author ProteusJS Team
4
+ *
5
+ * @version 1.1.0
6
+ * @author sc4rfurry
7
7
  * @license MIT
8
8
  */
9
9
 
10
- // Core exports
10
+ // Core exports (legacy compatibility)
11
11
  export { ProteusJS as default } from './core/ProteusJS';
12
12
  export { ProteusJS } from './core/ProteusJS';
13
13
 
14
+ // New modular exports
15
+ export * as transitions from './modules/transitions';
16
+ export * as scroll from './modules/scroll';
17
+ export * as anchor from './modules/anchor';
18
+ export * as popover from './modules/popover';
19
+ export * as container from './modules/container';
20
+ export * as typography from './modules/typography';
21
+ export * as a11yAudit from './modules/a11y-audit';
22
+ export * as a11yPrimitives from './modules/a11y-primitives';
23
+ export * as perf from './modules/perf';
24
+
25
+ // Framework adapters are available as separate subpath exports:
26
+ // import { ... } from '@sc4rfurryx/proteusjs/adapters/react'
27
+ // import { ... } from '@sc4rfurryx/proteusjs/adapters/vue'
28
+ // import { ... } from '@sc4rfurryx/proteusjs/adapters/svelte'
29
+
14
30
  // Type exports
15
31
  export type {
16
32
  ProteusConfig,
@@ -23,6 +39,17 @@ export type {
23
39
  PerformanceConfig
24
40
  } from './types';
25
41
 
42
+ // Module-specific type exports
43
+ export type { TransitionOptions, NavigateOptions } from './modules/transitions';
44
+ export type { ScrollAnimateOptions } from './modules/scroll';
45
+ export type { TetherOptions, TetherController } from './modules/anchor';
46
+ export type { PopoverOptions, PopoverController } from './modules/popover';
47
+ export type { ContainerOptions } from './modules/container';
48
+ export type { FluidTypeOptions, FluidTypeResult } from './modules/typography';
49
+ export type { AuditOptions, AuditReport, AuditViolation } from './modules/a11y-audit';
50
+ export type { Controller, DialogOptions, TooltipOptions, FocusTrapController } from './modules/a11y-primitives';
51
+ export type { SpeculationOptions, ContentVisibilityOptions } from './modules/perf';
52
+
26
53
  // Utility exports
27
54
  export { version } from './utils/version';
28
55
  export { isSupported } from './utils/support';
@@ -31,5 +58,5 @@ export { isSupported } from './utils/support';
31
58
  export type { ProteusPlugin } from './core/PluginSystem';
32
59
 
33
60
  // Constants
34
- export const VERSION = '1.0.0';
61
+ export const VERSION = '1.1.0';
35
62
  export const LIBRARY_NAME = 'ProteusJS';
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @sc4rfurryx/proteusjs/a11y-audit
3
+ * Lightweight accessibility audits for development
4
+ *
5
+ * @version 1.1.0
6
+ * @author sc4rfurry
7
+ * @license MIT
8
+ */
9
+
10
+ export interface AuditOptions {
11
+ rules?: string[];
12
+ format?: 'console' | 'json';
13
+ }
14
+
15
+ export interface AuditViolation {
16
+ id: string;
17
+ impact: 'minor' | 'moderate' | 'serious' | 'critical';
18
+ nodes: number;
19
+ help: string;
20
+ }
21
+
22
+ export interface AuditReport {
23
+ violations: AuditViolation[];
24
+ passes: number;
25
+ timestamp: number;
26
+ url: string;
27
+ }
28
+
29
+ export async function audit(
30
+ target: Document | Element = document,
31
+ options: AuditOptions = {}
32
+ ): Promise<AuditReport> {
33
+ if (typeof window === 'undefined' || process.env['NODE_ENV'] === 'production') {
34
+ return { violations: [], passes: 0, timestamp: Date.now(), url: '' };
35
+ }
36
+
37
+ const { rules = ['images', 'headings', 'forms'], format = 'console' } = options;
38
+ const violations: AuditViolation[] = [];
39
+ let passes = 0;
40
+
41
+ if (rules.includes('images')) {
42
+ const imgs = target.querySelectorAll('img:not([alt])');
43
+ if (imgs.length > 0) {
44
+ violations.push({
45
+ id: 'image-alt', impact: 'critical', nodes: imgs.length, help: 'Images need alt text'
46
+ });
47
+ }
48
+ passes += target.querySelectorAll('img[alt]').length;
49
+ }
50
+
51
+ if (rules.includes('headings')) {
52
+ const h1s = target.querySelectorAll('h1');
53
+ if (h1s.length !== 1) {
54
+ violations.push({
55
+ id: 'heading-structure', impact: 'moderate', nodes: h1s.length, help: 'Page should have exactly one h1'
56
+ });
57
+ } else passes++;
58
+ }
59
+
60
+ if (rules.includes('forms')) {
61
+ const unlabeled = target.querySelectorAll('input:not([aria-label]):not([aria-labelledby])');
62
+ if (unlabeled.length > 0) {
63
+ violations.push({
64
+ id: 'form-labels', impact: 'critical', nodes: unlabeled.length, help: 'Form inputs need labels'
65
+ });
66
+ }
67
+ passes += target.querySelectorAll('input[aria-label], input[aria-labelledby]').length;
68
+ }
69
+
70
+ const report: AuditReport = {
71
+ violations, passes, timestamp: Date.now(),
72
+ url: typeof window !== 'undefined' ? window.location.href : ''
73
+ };
74
+
75
+ if (format === 'console' && violations.length > 0) {
76
+ console.group('🔍 A11y Audit Results');
77
+ violations.forEach(v => console.warn(`${v.impact}: ${v.help}`));
78
+ console.groupEnd();
79
+ }
80
+
81
+ return report;
82
+ }
83
+
84
+ export default { audit };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @sc4rfurryx/proteusjs/a11y-primitives
3
+ * Lightweight accessibility patterns
4
+ *
5
+ * @version 1.1.0
6
+ * @author sc4rfurry
7
+ * @license MIT
8
+ */
9
+
10
+ export interface Controller {
11
+ destroy(): void;
12
+ }
13
+
14
+ export interface DialogOptions {
15
+ modal?: boolean;
16
+ restoreFocus?: boolean;
17
+ }
18
+
19
+ export interface TooltipOptions {
20
+ delay?: number;
21
+ placement?: 'top' | 'bottom' | 'left' | 'right';
22
+ }
23
+
24
+ export interface FocusTrapController {
25
+ activate(): void;
26
+ deactivate(): void;
27
+ }
28
+
29
+ export function dialog(root: Element | string, opts: DialogOptions = {}): Controller {
30
+ const el = typeof root === 'string' ? document.querySelector(root) : root;
31
+ if (!el) throw new Error('Dialog element not found');
32
+
33
+ const { modal = true, restoreFocus = true } = opts;
34
+ let prevFocus: Element | null = null;
35
+
36
+ const open = () => {
37
+ if (restoreFocus) prevFocus = document.activeElement;
38
+ el.setAttribute('role', 'dialog');
39
+ if (modal) el.setAttribute('aria-modal', 'true');
40
+ (el as HTMLElement).focus();
41
+ };
42
+
43
+ const close = () => {
44
+ if (restoreFocus && prevFocus) (prevFocus as HTMLElement).focus();
45
+ };
46
+
47
+ return { destroy: () => close() };
48
+ }
49
+
50
+ export function tooltip(trigger: Element, content: Element, opts: TooltipOptions = {}): Controller {
51
+ const { delay = 300 } = opts;
52
+ let timeout: number;
53
+
54
+ const show = () => {
55
+ clearTimeout(timeout);
56
+ timeout = window.setTimeout(() => {
57
+ content.setAttribute('role', 'tooltip');
58
+ trigger.setAttribute('aria-describedby', content.id || 'tooltip');
59
+ content.style.display = 'block';
60
+ }, delay);
61
+ };
62
+
63
+ const hide = () => {
64
+ clearTimeout(timeout);
65
+ content.style.display = 'none';
66
+ trigger.removeAttribute('aria-describedby');
67
+ };
68
+
69
+ trigger.addEventListener('mouseenter', show);
70
+ trigger.addEventListener('mouseleave', hide);
71
+ trigger.addEventListener('focus', show);
72
+ trigger.addEventListener('blur', hide);
73
+
74
+ return {
75
+ destroy: () => {
76
+ clearTimeout(timeout);
77
+ trigger.removeEventListener('mouseenter', show);
78
+ trigger.removeEventListener('mouseleave', hide);
79
+ trigger.removeEventListener('focus', show);
80
+ trigger.removeEventListener('blur', hide);
81
+ }
82
+ };
83
+ }
84
+
85
+ export function focusTrap(container: Element): FocusTrapController {
86
+ const focusable = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
87
+
88
+ const activate = () => {
89
+ const elements = container.querySelectorAll(focusable);
90
+ if (elements.length === 0) return;
91
+
92
+ const first = elements[0] as HTMLElement;
93
+ const last = elements[elements.length - 1] as HTMLElement;
94
+
95
+ const handleTab = (e: KeyboardEvent) => {
96
+ if (e.key !== 'Tab') return;
97
+
98
+ if (e.shiftKey && document.activeElement === first) {
99
+ e.preventDefault();
100
+ last.focus();
101
+ } else if (!e.shiftKey && document.activeElement === last) {
102
+ e.preventDefault();
103
+ first.focus();
104
+ }
105
+ };
106
+
107
+ container.addEventListener('keydown', handleTab);
108
+ first.focus();
109
+
110
+ return () => container.removeEventListener('keydown', handleTab);
111
+ };
112
+
113
+ let deactivate = () => {};
114
+
115
+ return {
116
+ activate: () => { deactivate = activate() || (() => {}); },
117
+ deactivate: () => deactivate()
118
+ };
119
+ }
120
+
121
+ export function menu(container: Element): Controller {
122
+ const items = container.querySelectorAll('[role="menuitem"]');
123
+ let currentIndex = 0;
124
+
125
+ const navigate = (e: KeyboardEvent) => {
126
+ switch (e.key) {
127
+ case 'ArrowDown':
128
+ e.preventDefault();
129
+ currentIndex = (currentIndex + 1) % items.length;
130
+ (items[currentIndex] as HTMLElement).focus();
131
+ break;
132
+ case 'ArrowUp':
133
+ e.preventDefault();
134
+ currentIndex = currentIndex === 0 ? items.length - 1 : currentIndex - 1;
135
+ (items[currentIndex] as HTMLElement).focus();
136
+ break;
137
+ case 'Escape':
138
+ e.preventDefault();
139
+ container.dispatchEvent(new CustomEvent('menu:close'));
140
+ break;
141
+ }
142
+ };
143
+
144
+ container.setAttribute('role', 'menu');
145
+ container.addEventListener('keydown', navigate);
146
+
147
+ return {
148
+ destroy: () => container.removeEventListener('keydown', navigate)
149
+ };
150
+ }
151
+
152
+ export default { dialog, tooltip, focusTrap, menu };