@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,259 @@
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
+ update(): void;
20
+ destroy(): void;
21
+ }
22
+
23
+ /**
24
+ * Declarative tethers (tooltips, callouts) via CSS Anchor Positioning when available;
25
+ * robust JS fallback with flip/collision detection
26
+ */
27
+ export function tether(
28
+ floating: Element | string,
29
+ opts: TetherOptions
30
+ ): TetherController {
31
+ const floatingEl = typeof floating === 'string' ? document.querySelector(floating) : floating;
32
+ const anchorEl = typeof opts.anchor === 'string' ? document.querySelector(opts.anchor) : opts.anchor;
33
+
34
+ if (!floatingEl || !anchorEl) {
35
+ throw new Error('Both floating and anchor elements must exist');
36
+ }
37
+
38
+ const {
39
+ placement = 'bottom',
40
+ align = 'center',
41
+ offset = 8,
42
+ strategy = 'absolute'
43
+ } = opts;
44
+
45
+ // Check for CSS Anchor Positioning support
46
+ const hasAnchorPositioning = CSS.supports('anchor-name', 'test');
47
+
48
+ let isDestroyed = false;
49
+ let resizeObserver: ResizeObserver | null = null;
50
+
51
+ const setupCSSAnchorPositioning = () => {
52
+ if (!hasAnchorPositioning) return false;
53
+
54
+ // Generate unique anchor name
55
+ const anchorName = `anchor-${Math.random().toString(36).substring(2, 11)}`;
56
+
57
+ // Set anchor name on anchor element
58
+ (anchorEl as HTMLElement).style.setProperty('anchor-name', anchorName);
59
+
60
+ // Position floating element using CSS anchor positioning
61
+ const floatingStyle = floatingEl as HTMLElement;
62
+ floatingStyle.style.position = strategy;
63
+ floatingStyle.style.setProperty('position-anchor', anchorName);
64
+
65
+ // Set position based on placement
66
+ switch (placement) {
67
+ case 'top':
68
+ floatingStyle.style.bottom = `anchor(bottom, ${offset}px)`;
69
+ break;
70
+ case 'bottom':
71
+ floatingStyle.style.top = `anchor(bottom, ${offset}px)`;
72
+ break;
73
+ case 'left':
74
+ floatingStyle.style.right = `anchor(left, ${offset}px)`;
75
+ break;
76
+ case 'right':
77
+ floatingStyle.style.left = `anchor(right, ${offset}px)`;
78
+ break;
79
+ }
80
+
81
+ // Set alignment
82
+ if (placement === 'top' || placement === 'bottom') {
83
+ switch (align) {
84
+ case 'start':
85
+ floatingStyle.style.left = 'anchor(left)';
86
+ break;
87
+ case 'center':
88
+ floatingStyle.style.left = 'anchor(center)';
89
+ floatingStyle.style.transform = 'translateX(-50%)';
90
+ break;
91
+ case 'end':
92
+ floatingStyle.style.right = 'anchor(right)';
93
+ break;
94
+ }
95
+ } else {
96
+ switch (align) {
97
+ case 'start':
98
+ floatingStyle.style.top = 'anchor(top)';
99
+ break;
100
+ case 'center':
101
+ floatingStyle.style.top = 'anchor(center)';
102
+ floatingStyle.style.transform = 'translateY(-50%)';
103
+ break;
104
+ case 'end':
105
+ floatingStyle.style.bottom = 'anchor(bottom)';
106
+ break;
107
+ }
108
+ }
109
+
110
+ return true;
111
+ };
112
+
113
+ const calculatePosition = () => {
114
+ const anchorRect = anchorEl.getBoundingClientRect();
115
+ const floatingRect = floatingEl.getBoundingClientRect();
116
+ const viewport = {
117
+ width: window.innerWidth,
118
+ height: window.innerHeight
119
+ };
120
+
121
+ let finalPlacement = placement;
122
+ let x = 0;
123
+ let y = 0;
124
+
125
+ // Calculate base position
126
+ switch (finalPlacement) {
127
+ case 'top':
128
+ x = anchorRect.left;
129
+ y = anchorRect.top - floatingRect.height - offset;
130
+ break;
131
+ case 'bottom':
132
+ x = anchorRect.left;
133
+ y = anchorRect.bottom + offset;
134
+ break;
135
+ case 'left':
136
+ x = anchorRect.left - floatingRect.width - offset;
137
+ y = anchorRect.top;
138
+ break;
139
+ case 'right':
140
+ x = anchorRect.right + offset;
141
+ y = anchorRect.top;
142
+ break;
143
+ case 'auto': {
144
+ // Choose best placement based on available space
145
+ const spaces = {
146
+ top: anchorRect.top,
147
+ bottom: viewport.height - anchorRect.bottom,
148
+ left: anchorRect.left,
149
+ right: viewport.width - anchorRect.right
150
+ };
151
+
152
+ const bestPlacement = Object.entries(spaces).reduce((a, b) =>
153
+ spaces[a[0] as keyof typeof spaces] > spaces[b[0] as keyof typeof spaces] ? a : b
154
+ )[0] as typeof finalPlacement;
155
+
156
+ finalPlacement = bestPlacement;
157
+ return calculatePosition(); // Recursive call with determined placement
158
+ }
159
+ }
160
+
161
+ // Apply alignment
162
+ if (finalPlacement === 'top' || finalPlacement === 'bottom') {
163
+ switch (align) {
164
+ case 'start':
165
+ // x already set correctly
166
+ break;
167
+ case 'center':
168
+ x = anchorRect.left + (anchorRect.width - floatingRect.width) / 2;
169
+ break;
170
+ case 'end':
171
+ x = anchorRect.right - floatingRect.width;
172
+ break;
173
+ }
174
+ } else {
175
+ switch (align) {
176
+ case 'start':
177
+ // y already set correctly
178
+ break;
179
+ case 'center':
180
+ y = anchorRect.top + (anchorRect.height - floatingRect.height) / 2;
181
+ break;
182
+ case 'end':
183
+ y = anchorRect.bottom - floatingRect.height;
184
+ break;
185
+ }
186
+ }
187
+
188
+ // Collision detection and adjustment
189
+ if (x < 0) x = 8;
190
+ if (y < 0) y = 8;
191
+ if (x + floatingRect.width > viewport.width) {
192
+ x = viewport.width - floatingRect.width - 8;
193
+ }
194
+ if (y + floatingRect.height > viewport.height) {
195
+ y = viewport.height - floatingRect.height - 8;
196
+ }
197
+
198
+ return { x, y };
199
+ };
200
+
201
+ const updatePosition = () => {
202
+ if (isDestroyed) return;
203
+
204
+ if (!hasAnchorPositioning) {
205
+ const { x, y } = calculatePosition();
206
+ const floatingStyle = floatingEl as HTMLElement;
207
+ floatingStyle.style.position = strategy;
208
+ floatingStyle.style.left = `${x}px`;
209
+ floatingStyle.style.top = `${y}px`;
210
+ }
211
+ };
212
+
213
+ const setupJSFallback = () => {
214
+ updatePosition();
215
+
216
+ // Set up observers for position updates
217
+ resizeObserver = new ResizeObserver(updatePosition);
218
+ resizeObserver.observe(anchorEl);
219
+ resizeObserver.observe(floatingEl);
220
+
221
+ window.addEventListener('scroll', updatePosition, { passive: true });
222
+ window.addEventListener('resize', updatePosition, { passive: true });
223
+ };
224
+
225
+ const destroy = () => {
226
+ isDestroyed = true;
227
+
228
+ if (resizeObserver) {
229
+ resizeObserver.disconnect();
230
+ resizeObserver = null;
231
+ }
232
+
233
+ window.removeEventListener('scroll', updatePosition);
234
+ window.removeEventListener('resize', updatePosition);
235
+
236
+ // Clean up CSS anchor positioning
237
+ if (hasAnchorPositioning) {
238
+ (anchorEl as HTMLElement).style.removeProperty('anchor-name');
239
+ const floatingStyle = floatingEl as HTMLElement;
240
+ floatingStyle.style.removeProperty('position-anchor');
241
+ floatingStyle.style.position = '';
242
+ }
243
+ };
244
+
245
+ // Initialize
246
+ if (!setupCSSAnchorPositioning()) {
247
+ setupJSFallback();
248
+ }
249
+
250
+ return {
251
+ update: updatePosition,
252
+ destroy
253
+ };
254
+ }
255
+
256
+ // Export default object for convenience
257
+ export default {
258
+ tether
259
+ };
@@ -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
+ };