@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.
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 +139 -0
  5. package/dist/adapters/react.esm.js +848 -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 +908 -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 +872 -0
  12. package/dist/adapters/vue.esm.js.map +1 -0
  13. package/dist/modules/a11y-audit.d.ts +39 -0
  14. package/dist/modules/a11y-audit.esm.js +509 -0
  15. package/dist/modules/a11y-audit.esm.js.map +1 -0
  16. package/dist/modules/a11y-primitives.d.ts +69 -0
  17. package/dist/modules/a11y-primitives.esm.js +445 -0
  18. package/dist/modules/a11y-primitives.esm.js.map +1 -0
  19. package/dist/modules/anchor.d.ts +29 -0
  20. package/dist/modules/anchor.esm.js +218 -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 +2332 -12
  41. package/dist/proteus.cjs.js.map +1 -1
  42. package/dist/proteus.d.ts +561 -12
  43. package/dist/proteus.esm.js +2323 -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 +2332 -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 +61 -4
  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 +608 -0
  57. package/src/modules/a11y-primitives/index.ts +554 -0
  58. package/src/modules/anchor/index.ts +257 -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,257 @@
1
+ /**
2
+ * @sc4rfurryx/proteusjs/anchor
3
+ * CSS Anchor Positioning utilities with robust JS fallback
4
+ *
5
+ * @version 1.1.0
6
+ * @author sc4rfurry
7
+ * @license MIT
8
+ */
9
+
10
+ export interface TetherOptions {
11
+ anchor: Element | string;
12
+ placement?: 'top' | 'bottom' | 'left' | 'right' | 'auto';
13
+ align?: 'start' | 'center' | 'end';
14
+ offset?: number;
15
+ strategy?: 'absolute' | 'fixed';
16
+ }
17
+
18
+ export interface TetherController {
19
+ destroy(): void;
20
+ }
21
+
22
+ /**
23
+ * Declarative tethers (tooltips, callouts) via CSS Anchor Positioning when available;
24
+ * robust JS fallback with flip/collision detection
25
+ */
26
+ export function tether(
27
+ floating: Element | string,
28
+ opts: TetherOptions
29
+ ): TetherController {
30
+ const floatingEl = typeof floating === 'string' ? document.querySelector(floating) : floating;
31
+ const anchorEl = typeof opts.anchor === 'string' ? document.querySelector(opts.anchor) : opts.anchor;
32
+
33
+ if (!floatingEl || !anchorEl) {
34
+ throw new Error('Both floating and anchor elements must exist');
35
+ }
36
+
37
+ const {
38
+ placement = 'bottom',
39
+ align = 'center',
40
+ offset = 8,
41
+ strategy = 'absolute'
42
+ } = opts;
43
+
44
+ // Check for CSS Anchor Positioning support
45
+ const hasAnchorPositioning = CSS.supports('anchor-name', 'test');
46
+
47
+ let isDestroyed = false;
48
+ let resizeObserver: ResizeObserver | null = null;
49
+
50
+ const setupCSSAnchorPositioning = () => {
51
+ if (!hasAnchorPositioning) return false;
52
+
53
+ // Generate unique anchor name
54
+ const anchorName = `anchor-${Math.random().toString(36).substring(2, 11)}`;
55
+
56
+ // Set anchor name on anchor element
57
+ (anchorEl as HTMLElement).style.setProperty('anchor-name', anchorName);
58
+
59
+ // Position floating element using CSS anchor positioning
60
+ const floatingStyle = floatingEl as HTMLElement;
61
+ floatingStyle.style.position = strategy;
62
+ floatingStyle.style.setProperty('position-anchor', anchorName);
63
+
64
+ // Set position based on placement
65
+ switch (placement) {
66
+ case 'top':
67
+ floatingStyle.style.bottom = `anchor(bottom, ${offset}px)`;
68
+ break;
69
+ case 'bottom':
70
+ floatingStyle.style.top = `anchor(bottom, ${offset}px)`;
71
+ break;
72
+ case 'left':
73
+ floatingStyle.style.right = `anchor(left, ${offset}px)`;
74
+ break;
75
+ case 'right':
76
+ floatingStyle.style.left = `anchor(right, ${offset}px)`;
77
+ break;
78
+ }
79
+
80
+ // Set alignment
81
+ if (placement === 'top' || placement === 'bottom') {
82
+ switch (align) {
83
+ case 'start':
84
+ floatingStyle.style.left = 'anchor(left)';
85
+ break;
86
+ case 'center':
87
+ floatingStyle.style.left = 'anchor(center)';
88
+ floatingStyle.style.transform = 'translateX(-50%)';
89
+ break;
90
+ case 'end':
91
+ floatingStyle.style.right = 'anchor(right)';
92
+ break;
93
+ }
94
+ } else {
95
+ switch (align) {
96
+ case 'start':
97
+ floatingStyle.style.top = 'anchor(top)';
98
+ break;
99
+ case 'center':
100
+ floatingStyle.style.top = 'anchor(center)';
101
+ floatingStyle.style.transform = 'translateY(-50%)';
102
+ break;
103
+ case 'end':
104
+ floatingStyle.style.bottom = 'anchor(bottom)';
105
+ break;
106
+ }
107
+ }
108
+
109
+ return true;
110
+ };
111
+
112
+ const calculatePosition = () => {
113
+ const anchorRect = anchorEl.getBoundingClientRect();
114
+ const floatingRect = floatingEl.getBoundingClientRect();
115
+ const viewport = {
116
+ width: window.innerWidth,
117
+ height: window.innerHeight
118
+ };
119
+
120
+ let finalPlacement = placement;
121
+ let x = 0;
122
+ let y = 0;
123
+
124
+ // Calculate base position
125
+ switch (finalPlacement) {
126
+ case 'top':
127
+ x = anchorRect.left;
128
+ y = anchorRect.top - floatingRect.height - offset;
129
+ break;
130
+ case 'bottom':
131
+ x = anchorRect.left;
132
+ y = anchorRect.bottom + offset;
133
+ break;
134
+ case 'left':
135
+ x = anchorRect.left - floatingRect.width - offset;
136
+ y = anchorRect.top;
137
+ break;
138
+ case 'right':
139
+ x = anchorRect.right + offset;
140
+ y = anchorRect.top;
141
+ break;
142
+ case 'auto': {
143
+ // Choose best placement based on available space
144
+ const spaces = {
145
+ top: anchorRect.top,
146
+ bottom: viewport.height - anchorRect.bottom,
147
+ left: anchorRect.left,
148
+ right: viewport.width - anchorRect.right
149
+ };
150
+
151
+ const bestPlacement = Object.entries(spaces).reduce((a, b) =>
152
+ spaces[a[0] as keyof typeof spaces] > spaces[b[0] as keyof typeof spaces] ? a : b
153
+ )[0] as typeof finalPlacement;
154
+
155
+ finalPlacement = bestPlacement;
156
+ return calculatePosition(); // Recursive call with determined placement
157
+ }
158
+ }
159
+
160
+ // Apply alignment
161
+ if (finalPlacement === 'top' || finalPlacement === 'bottom') {
162
+ switch (align) {
163
+ case 'start':
164
+ // x already set correctly
165
+ break;
166
+ case 'center':
167
+ x = anchorRect.left + (anchorRect.width - floatingRect.width) / 2;
168
+ break;
169
+ case 'end':
170
+ x = anchorRect.right - floatingRect.width;
171
+ break;
172
+ }
173
+ } else {
174
+ switch (align) {
175
+ case 'start':
176
+ // y already set correctly
177
+ break;
178
+ case 'center':
179
+ y = anchorRect.top + (anchorRect.height - floatingRect.height) / 2;
180
+ break;
181
+ case 'end':
182
+ y = anchorRect.bottom - floatingRect.height;
183
+ break;
184
+ }
185
+ }
186
+
187
+ // Collision detection and adjustment
188
+ if (x < 0) x = 8;
189
+ if (y < 0) y = 8;
190
+ if (x + floatingRect.width > viewport.width) {
191
+ x = viewport.width - floatingRect.width - 8;
192
+ }
193
+ if (y + floatingRect.height > viewport.height) {
194
+ y = viewport.height - floatingRect.height - 8;
195
+ }
196
+
197
+ return { x, y };
198
+ };
199
+
200
+ const updatePosition = () => {
201
+ if (isDestroyed) return;
202
+
203
+ if (!hasAnchorPositioning) {
204
+ const { x, y } = calculatePosition();
205
+ const floatingStyle = floatingEl as HTMLElement;
206
+ floatingStyle.style.position = strategy;
207
+ floatingStyle.style.left = `${x}px`;
208
+ floatingStyle.style.top = `${y}px`;
209
+ }
210
+ };
211
+
212
+ const setupJSFallback = () => {
213
+ updatePosition();
214
+
215
+ // Set up observers for position updates
216
+ resizeObserver = new ResizeObserver(updatePosition);
217
+ resizeObserver.observe(anchorEl);
218
+ resizeObserver.observe(floatingEl);
219
+
220
+ window.addEventListener('scroll', updatePosition, { passive: true });
221
+ window.addEventListener('resize', updatePosition, { passive: true });
222
+ };
223
+
224
+ const destroy = () => {
225
+ isDestroyed = true;
226
+
227
+ if (resizeObserver) {
228
+ resizeObserver.disconnect();
229
+ resizeObserver = null;
230
+ }
231
+
232
+ window.removeEventListener('scroll', updatePosition);
233
+ window.removeEventListener('resize', updatePosition);
234
+
235
+ // Clean up CSS anchor positioning
236
+ if (hasAnchorPositioning) {
237
+ (anchorEl as HTMLElement).style.removeProperty('anchor-name');
238
+ const floatingStyle = floatingEl as HTMLElement;
239
+ floatingStyle.style.removeProperty('position-anchor');
240
+ floatingStyle.style.position = '';
241
+ }
242
+ };
243
+
244
+ // Initialize
245
+ if (!setupCSSAnchorPositioning()) {
246
+ setupJSFallback();
247
+ }
248
+
249
+ return {
250
+ destroy
251
+ };
252
+ }
253
+
254
+ // Export default object for convenience
255
+ export default {
256
+ tether
257
+ };
@@ -0,0 +1,230 @@
1
+ /**
2
+ * @sc4rfurryx/proteusjs/container
3
+ * Container/Style Query helpers with visualization devtools
4
+ *
5
+ * @version 1.1.0
6
+ * @author sc4rfurry
7
+ * @license MIT
8
+ */
9
+
10
+ export interface ContainerOptions {
11
+ type?: 'size' | 'style';
12
+ inlineSize?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Sugar on native container queries with dev visualization
17
+ */
18
+ export function defineContainer(
19
+ target: Element | string,
20
+ name?: string,
21
+ opts: ContainerOptions = {}
22
+ ): void {
23
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
24
+ if (!targetEl) {
25
+ throw new Error('Target element not found');
26
+ }
27
+
28
+ const {
29
+ type = 'size',
30
+ inlineSize: _inlineSize = true
31
+ } = opts;
32
+
33
+ const containerName = name || `container-${Math.random().toString(36).substring(2, 11)}`;
34
+
35
+ // Apply container properties
36
+ const element = targetEl as HTMLElement;
37
+ element.style.containerName = containerName;
38
+ element.style.containerType = type;
39
+
40
+ // Warn if containment settings are missing
41
+ const computedStyle = getComputedStyle(element);
42
+ if (!computedStyle.contain || computedStyle.contain === 'none') {
43
+ console.warn(`Container "${containerName}" may need explicit containment settings for optimal performance`);
44
+ }
45
+
46
+ // Dev overlay (only in development)
47
+ if (process.env['NODE_ENV'] === 'development' || (window as unknown as { __PROTEUS_DEV__?: boolean }).__PROTEUS_DEV__) {
48
+ createDevOverlay(element, containerName);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Create development overlay showing container bounds and breakpoints
54
+ */
55
+ function createDevOverlay(element: HTMLElement, name: string): void {
56
+ const overlay = document.createElement('div');
57
+ overlay.className = 'proteus-container-overlay';
58
+ overlay.style.cssText = `
59
+ position: absolute;
60
+ top: 0;
61
+ left: 0;
62
+ right: 0;
63
+ bottom: 0;
64
+ pointer-events: none;
65
+ border: 2px dashed rgba(255, 0, 255, 0.5);
66
+ background: rgba(255, 0, 255, 0.05);
67
+ z-index: 9999;
68
+ font-family: monospace;
69
+ font-size: 12px;
70
+ color: #ff00ff;
71
+ `;
72
+
73
+ const label = document.createElement('div');
74
+ label.style.cssText = `
75
+ position: absolute;
76
+ top: -20px;
77
+ left: 0;
78
+ background: rgba(255, 0, 255, 0.9);
79
+ color: white;
80
+ padding: 2px 6px;
81
+ border-radius: 3px;
82
+ font-size: 10px;
83
+ white-space: nowrap;
84
+ `;
85
+ label.textContent = `Container: ${name}`;
86
+
87
+ const sizeInfo = document.createElement('div');
88
+ sizeInfo.style.cssText = `
89
+ position: absolute;
90
+ bottom: 2px;
91
+ right: 2px;
92
+ background: rgba(0, 0, 0, 0.7);
93
+ color: white;
94
+ padding: 2px 4px;
95
+ border-radius: 2px;
96
+ font-size: 10px;
97
+ `;
98
+
99
+ overlay.appendChild(label);
100
+ overlay.appendChild(sizeInfo);
101
+
102
+ // Position overlay relative to container
103
+ if (getComputedStyle(element).position === 'static') {
104
+ element.style.position = 'relative';
105
+ }
106
+ element.appendChild(overlay);
107
+
108
+ // Update size info
109
+ const updateSizeInfo = () => {
110
+ const rect = element.getBoundingClientRect();
111
+ sizeInfo.textContent = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
112
+ };
113
+
114
+ updateSizeInfo();
115
+
116
+ // Update on resize
117
+ if ('ResizeObserver' in window) {
118
+ const resizeObserver = new ResizeObserver(updateSizeInfo);
119
+ resizeObserver.observe(element);
120
+ }
121
+
122
+ // Store cleanup function
123
+ (element as HTMLElement & { _proteusContainerCleanup?: () => void })._proteusContainerCleanup = () => {
124
+ overlay.remove();
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Helper to create container query CSS rules
130
+ */
131
+ export function createContainerQuery(
132
+ containerName: string,
133
+ condition: string,
134
+ styles: Record<string, string>
135
+ ): string {
136
+ const cssRules = Object.entries(styles)
137
+ .map(([property, value]) => ` ${property}: ${value};`)
138
+ .join('\n');
139
+
140
+ return `@container ${containerName} (${condition}) {\n${cssRules}\n}`;
141
+ }
142
+
143
+ /**
144
+ * Apply container query styles dynamically
145
+ */
146
+ export function applyContainerQuery(
147
+ containerName: string,
148
+ condition: string,
149
+ styles: Record<string, string>
150
+ ): void {
151
+ const css = createContainerQuery(containerName, condition, styles);
152
+
153
+ const styleElement = document.createElement('style');
154
+ styleElement.textContent = css;
155
+ styleElement.setAttribute('data-proteus-container', containerName);
156
+ document.head.appendChild(styleElement);
157
+ }
158
+
159
+ /**
160
+ * Remove container query styles
161
+ */
162
+ export function removeContainerQuery(containerName: string): void {
163
+ const styleElements = document.querySelectorAll(`style[data-proteus-container="${containerName}"]`);
164
+ styleElements.forEach(element => element.remove());
165
+ }
166
+
167
+ /**
168
+ * Get container size information
169
+ */
170
+ export function getContainerSize(target: Element | string): { width: number; height: number } {
171
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
172
+ if (!targetEl) {
173
+ throw new Error('Target element not found');
174
+ }
175
+
176
+ const rect = targetEl.getBoundingClientRect();
177
+ return {
178
+ width: rect.width,
179
+ height: rect.height
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Check if container queries are supported
185
+ */
186
+ export function isSupported(): boolean {
187
+ return CSS.supports('container-type', 'size');
188
+ }
189
+
190
+ /**
191
+ * Cleanup container overlays and observers
192
+ */
193
+ export function cleanup(target: Element | string): void {
194
+ const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
195
+ if (!targetEl) return;
196
+
197
+ // Call stored cleanup function if it exists
198
+ const elementWithCleanup = targetEl as HTMLElement & { _proteusContainerCleanup?: () => void };
199
+ if (elementWithCleanup._proteusContainerCleanup) {
200
+ elementWithCleanup._proteusContainerCleanup();
201
+ delete elementWithCleanup._proteusContainerCleanup;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Toggle dev overlay visibility
207
+ */
208
+ export function toggleDevOverlay(visible?: boolean): void {
209
+ const overlays = document.querySelectorAll('.proteus-container-overlay');
210
+ overlays.forEach(overlay => {
211
+ const element = overlay as HTMLElement;
212
+ if (visible !== undefined) {
213
+ element.style.display = visible ? 'block' : 'none';
214
+ } else {
215
+ element.style.display = element.style.display === 'none' ? 'block' : 'none';
216
+ }
217
+ });
218
+ }
219
+
220
+ // Export default object for convenience
221
+ export default {
222
+ defineContainer,
223
+ createContainerQuery,
224
+ applyContainerQuery,
225
+ removeContainerQuery,
226
+ getContainerSize,
227
+ isSupported,
228
+ cleanup,
229
+ toggleDevOverlay
230
+ };