app-studio 0.6.59 → 0.7.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/dist/app-studio.cjs.development.js +454 -127
- package/dist/app-studio.cjs.development.js.map +1 -1
- package/dist/app-studio.cjs.production.min.js +1 -1
- package/dist/app-studio.esm.js +447 -128
- package/dist/app-studio.esm.js.map +1 -1
- package/dist/app-studio.umd.development.js +454 -127
- package/dist/app-studio.umd.development.js.map +1 -1
- package/dist/app-studio.umd.production.min.js +1 -1
- package/dist/hooks/useClickOutside.d.ts +5 -1
- package/dist/hooks/useElementPosition.d.ts +2 -0
- package/dist/hooks/useIframeStyles.d.ts +41 -0
- package/dist/hooks/useInView.d.ts +2 -0
- package/dist/hooks/useOnScreen.d.ts +5 -1
- package/dist/hooks/useResponsive.d.ts +46 -0
- package/dist/hooks/useScroll.d.ts +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/providers/Responsive.d.ts +38 -4
- package/dist/providers/Theme.d.ts +2 -1
- package/dist/providers/WindowSize.d.ts +5 -2
- package/dist/stories/Animation.stories.d.ts +5 -0
- package/dist/stories/IframeCSSSupport.stories.d.ts +28 -0
- package/dist/stories/IframeSupport.stories.d.ts +16 -0
- package/dist/stories/ScrollTimeline.stories.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1072,7 +1072,8 @@ const ThemeProvider = _ref => {
|
|
|
1072
1072
|
dark: darkOverride = {},
|
|
1073
1073
|
light: lightOverride = {},
|
|
1074
1074
|
children,
|
|
1075
|
-
strict = false
|
|
1075
|
+
strict = false,
|
|
1076
|
+
targetWindow
|
|
1076
1077
|
} = _ref;
|
|
1077
1078
|
const [themeMode, setThemeMode] = React.useState(initialMode);
|
|
1078
1079
|
const colorCache = React.useRef(new Map()).current;
|
|
@@ -1394,9 +1395,34 @@ const ThemeProvider = _ref => {
|
|
|
1394
1395
|
themeMode,
|
|
1395
1396
|
setThemeMode
|
|
1396
1397
|
}), [getColor, getColorHex, getColorRGBA, getColorScheme, getContrastColor, mergedTheme, currentColors, themeMode]);
|
|
1398
|
+
// Generate CSS variables
|
|
1399
|
+
const cssVariables = React.useMemo(() => generateCSSVariables(mergedTheme, themeColors.light, themeColors.dark), [mergedTheme, themeColors]);
|
|
1400
|
+
// Inject CSS variables into target document (for iframe support)
|
|
1401
|
+
React.useEffect(() => {
|
|
1402
|
+
if (!targetWindow) return;
|
|
1403
|
+
const targetDoc = targetWindow.document;
|
|
1404
|
+
const styleId = 'app-studio-theme-vars';
|
|
1405
|
+
// Remove existing style tag if any
|
|
1406
|
+
const existing = targetDoc.getElementById(styleId);
|
|
1407
|
+
if (existing) {
|
|
1408
|
+
existing.remove();
|
|
1409
|
+
}
|
|
1410
|
+
// Create and inject new style tag
|
|
1411
|
+
const styleTag = targetDoc.createElement('style');
|
|
1412
|
+
styleTag.id = styleId;
|
|
1413
|
+
styleTag.textContent = cssVariables;
|
|
1414
|
+
targetDoc.head.appendChild(styleTag);
|
|
1415
|
+
// Cleanup on unmount
|
|
1416
|
+
return () => {
|
|
1417
|
+
const styleToRemove = targetDoc.getElementById(styleId);
|
|
1418
|
+
if (styleToRemove) {
|
|
1419
|
+
styleToRemove.remove();
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
}, [targetWindow, cssVariables]);
|
|
1397
1423
|
return /*#__PURE__*/React__default.createElement(ThemeContext.Provider, {
|
|
1398
1424
|
value: contextValue
|
|
1399
|
-
}, /*#__PURE__*/React__default.createElement("style", null,
|
|
1425
|
+
}, !targetWindow && /*#__PURE__*/React__default.createElement("style", null, cssVariables), /*#__PURE__*/React__default.createElement("div", {
|
|
1400
1426
|
"data-theme": themeMode,
|
|
1401
1427
|
style: {
|
|
1402
1428
|
backgroundColor: 'white',
|
|
@@ -1465,32 +1491,57 @@ const debounce = (func, wait) => {
|
|
|
1465
1491
|
};
|
|
1466
1492
|
// Helper to compute breakpoint from width
|
|
1467
1493
|
const getBreakpointFromWidth = (width, breakpoints) => {
|
|
1468
|
-
// console.log('[ResponsiveProvider] Computing breakpoint for width:', width);
|
|
1469
|
-
// console.log('[ResponsiveProvider] Breakpoints config:', breakpoints);
|
|
1470
1494
|
const sortedBreakpoints = Object.entries(breakpoints).sort((_ref, _ref2) => {
|
|
1471
1495
|
let [, a] = _ref;
|
|
1472
1496
|
let [, b] = _ref2;
|
|
1473
1497
|
return b - a;
|
|
1474
1498
|
}); // Sort descending by min value
|
|
1475
|
-
// console.log('[ResponsiveProvider] Sorted breakpoints:', sortedBreakpoints);
|
|
1476
1499
|
for (const [name, minWidth] of sortedBreakpoints) {
|
|
1477
1500
|
if (width >= minWidth) {
|
|
1478
|
-
// console.log(
|
|
1479
|
-
// '[ResponsiveProvider] ✓ Match found:',
|
|
1480
|
-
// name,
|
|
1481
|
-
// 'for width',
|
|
1482
|
-
// width,
|
|
1483
|
-
// '>= minWidth',
|
|
1484
|
-
// minWidth
|
|
1485
|
-
// );
|
|
1486
1501
|
return name;
|
|
1487
1502
|
}
|
|
1488
1503
|
}
|
|
1489
1504
|
const fallback = sortedBreakpoints[sortedBreakpoints.length - 1]?.[0] || 'xs';
|
|
1490
|
-
// console.log('[ResponsiveProvider] No match, using fallback:', fallback);
|
|
1491
1505
|
return fallback;
|
|
1492
1506
|
};
|
|
1493
|
-
//
|
|
1507
|
+
// ============================================================================
|
|
1508
|
+
// SPLIT CONTEXTS FOR OPTIMIZED RE-RENDERS
|
|
1509
|
+
// ============================================================================
|
|
1510
|
+
// BreakpointContext - changes rarely (only on breakpoint threshold crossing)
|
|
1511
|
+
const defaultBreakpointConfig = {
|
|
1512
|
+
breakpoints: defaultBreakpointsConfig,
|
|
1513
|
+
devices: defaultDeviceConfig,
|
|
1514
|
+
mediaQueries: /*#__PURE__*/getMediaQueries(defaultBreakpointsConfig),
|
|
1515
|
+
currentBreakpoint: 'xs',
|
|
1516
|
+
currentDevice: 'mobile',
|
|
1517
|
+
orientation: 'portrait'
|
|
1518
|
+
};
|
|
1519
|
+
const BreakpointContext = /*#__PURE__*/React.createContext(defaultBreakpointConfig);
|
|
1520
|
+
// WindowDimensionsContext - changes often (on every resize)
|
|
1521
|
+
const defaultWindowDimensions = {
|
|
1522
|
+
width: 0,
|
|
1523
|
+
height: 0
|
|
1524
|
+
};
|
|
1525
|
+
const WindowDimensionsContext = /*#__PURE__*/React.createContext(defaultWindowDimensions);
|
|
1526
|
+
// ============================================================================
|
|
1527
|
+
// HOOKS FOR SPLIT CONTEXTS
|
|
1528
|
+
// ============================================================================
|
|
1529
|
+
/**
|
|
1530
|
+
* Hook to access breakpoint information only.
|
|
1531
|
+
* Components using this hook will NOT re-render on every resize,
|
|
1532
|
+
* only when the breakpoint actually changes.
|
|
1533
|
+
*/
|
|
1534
|
+
const useBreakpointContext = () => React.useContext(BreakpointContext);
|
|
1535
|
+
/**
|
|
1536
|
+
* Hook to access window dimensions.
|
|
1537
|
+
* Components using this hook WILL re-render on every resize.
|
|
1538
|
+
* Use sparingly - prefer useBreakpointContext when possible.
|
|
1539
|
+
*/
|
|
1540
|
+
const useWindowDimensionsContext = () => React.useContext(WindowDimensionsContext);
|
|
1541
|
+
// ============================================================================
|
|
1542
|
+
// LEGACY CONTEXT FOR BACKWARD COMPATIBILITY
|
|
1543
|
+
// ============================================================================
|
|
1544
|
+
// Create the combined Context with default values (for backward compatibility)
|
|
1494
1545
|
const ResponsiveContext = /*#__PURE__*/React.createContext({
|
|
1495
1546
|
breakpoints: defaultBreakpointsConfig,
|
|
1496
1547
|
devices: defaultDeviceConfig,
|
|
@@ -1501,56 +1552,63 @@ const ResponsiveContext = /*#__PURE__*/React.createContext({
|
|
|
1501
1552
|
currentDevice: 'mobile',
|
|
1502
1553
|
orientation: 'portrait'
|
|
1503
1554
|
});
|
|
1504
|
-
|
|
1555
|
+
/**
|
|
1556
|
+
* Legacy hook for backward compatibility.
|
|
1557
|
+
* Prefer useBreakpointContext for better performance.
|
|
1558
|
+
* @deprecated Use useBreakpointContext instead for better performance
|
|
1559
|
+
*/
|
|
1505
1560
|
const useResponsiveContext = () => React.useContext(ResponsiveContext);
|
|
1506
1561
|
const ResponsiveProvider = _ref3 => {
|
|
1507
1562
|
let {
|
|
1508
1563
|
breakpoints = defaultBreakpointsConfig,
|
|
1509
1564
|
devices = defaultDeviceConfig,
|
|
1510
|
-
children
|
|
1565
|
+
children,
|
|
1566
|
+
targetWindow
|
|
1511
1567
|
} = _ref3;
|
|
1568
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
1569
|
+
// Track current breakpoint - only updates when crossing thresholds
|
|
1512
1570
|
const [screen, setScreen] = React.useState(() => {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
return getBreakpointFromWidth(window.innerWidth, breakpoints);
|
|
1571
|
+
if (win) {
|
|
1572
|
+
return getBreakpointFromWidth(win.innerWidth, breakpoints);
|
|
1516
1573
|
}
|
|
1517
1574
|
return 'xs';
|
|
1518
1575
|
});
|
|
1576
|
+
// Track orientation - rarely changes
|
|
1519
1577
|
const [orientation, setOrientation] = React.useState('portrait');
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1578
|
+
// Track window dimensions - changes often
|
|
1579
|
+
const [dimensions, setDimensions] = React.useState({
|
|
1580
|
+
width: win?.innerWidth || 0,
|
|
1581
|
+
height: win?.innerHeight || 0
|
|
1523
1582
|
});
|
|
1583
|
+
// Use ref to track previous breakpoint to avoid unnecessary state updates
|
|
1584
|
+
const prevBreakpointRef = React.useRef(screen);
|
|
1524
1585
|
const mediaQueries = React.useMemo(() => getMediaQueries(breakpoints), [breakpoints]);
|
|
1525
1586
|
React.useEffect(() => {
|
|
1526
|
-
|
|
1587
|
+
if (!win) return;
|
|
1527
1588
|
// Set initial screen size immediately based on window width
|
|
1528
|
-
const initialScreen = getBreakpointFromWidth(
|
|
1529
|
-
// console.log(
|
|
1530
|
-
// '[ResponsiveProvider] Setting initial screen to:',
|
|
1531
|
-
// initialScreen
|
|
1532
|
-
// );
|
|
1589
|
+
const initialScreen = getBreakpointFromWidth(win.innerWidth, breakpoints);
|
|
1533
1590
|
setScreen(initialScreen);
|
|
1591
|
+
prevBreakpointRef.current = initialScreen;
|
|
1534
1592
|
const handleResize = () => {
|
|
1535
|
-
const newWidth =
|
|
1536
|
-
const newHeight =
|
|
1537
|
-
//
|
|
1538
|
-
|
|
1539
|
-
// height: newHeight,
|
|
1540
|
-
// });
|
|
1541
|
-
setSize({
|
|
1593
|
+
const newWidth = win.innerWidth;
|
|
1594
|
+
const newHeight = win.innerHeight;
|
|
1595
|
+
// Always update dimensions (WindowDimensionsContext will re-render)
|
|
1596
|
+
setDimensions({
|
|
1542
1597
|
width: newWidth,
|
|
1543
1598
|
height: newHeight
|
|
1544
1599
|
});
|
|
1545
|
-
//
|
|
1600
|
+
// Only update breakpoint if it actually changed
|
|
1601
|
+
// This prevents BreakpointContext from causing unnecessary re-renders
|
|
1546
1602
|
const newScreen = getBreakpointFromWidth(newWidth, breakpoints);
|
|
1547
|
-
|
|
1548
|
-
|
|
1603
|
+
if (newScreen !== prevBreakpointRef.current) {
|
|
1604
|
+
prevBreakpointRef.current = newScreen;
|
|
1605
|
+
setScreen(newScreen);
|
|
1606
|
+
}
|
|
1549
1607
|
};
|
|
1550
1608
|
const debouncedResize = debounce(handleResize, 100);
|
|
1551
|
-
|
|
1609
|
+
win.addEventListener('resize', debouncedResize);
|
|
1552
1610
|
// Set up orientation listener
|
|
1553
|
-
const orientationMql =
|
|
1611
|
+
const orientationMql = win.matchMedia('(orientation: landscape)');
|
|
1554
1612
|
const onOrientationChange = () => setOrientation(orientationMql.matches ? 'landscape' : 'portrait');
|
|
1555
1613
|
if (orientationMql.addEventListener) {
|
|
1556
1614
|
orientationMql.addEventListener('change', onOrientationChange);
|
|
@@ -1559,30 +1617,46 @@ const ResponsiveProvider = _ref3 => {
|
|
|
1559
1617
|
}
|
|
1560
1618
|
onOrientationChange();
|
|
1561
1619
|
return () => {
|
|
1562
|
-
|
|
1620
|
+
win.removeEventListener('resize', debouncedResize);
|
|
1563
1621
|
if (orientationMql.removeEventListener) {
|
|
1564
1622
|
orientationMql.removeEventListener('change', onOrientationChange);
|
|
1565
1623
|
} else {
|
|
1566
1624
|
orientationMql.removeListener(onOrientationChange);
|
|
1567
1625
|
}
|
|
1568
1626
|
};
|
|
1569
|
-
}, [breakpoints]);
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1627
|
+
}, [breakpoints, win]);
|
|
1628
|
+
// Breakpoint context value - only updates when breakpoint/orientation changes
|
|
1629
|
+
const breakpointValue = React.useMemo(() => ({
|
|
1630
|
+
breakpoints,
|
|
1631
|
+
devices,
|
|
1632
|
+
mediaQueries,
|
|
1633
|
+
currentBreakpoint: screen,
|
|
1634
|
+
currentDevice: determineCurrentDevice(screen, devices),
|
|
1635
|
+
orientation
|
|
1636
|
+
}), [breakpoints, devices, mediaQueries, screen, orientation]);
|
|
1637
|
+
// Window dimensions context value - updates on every resize
|
|
1638
|
+
const dimensionsValue = React.useMemo(() => ({
|
|
1639
|
+
width: dimensions.width,
|
|
1640
|
+
height: dimensions.height
|
|
1641
|
+
}), [dimensions.width, dimensions.height]);
|
|
1642
|
+
// Combined legacy context value for backward compatibility
|
|
1643
|
+
const legacyValue = React.useMemo(() => ({
|
|
1644
|
+
breakpoints,
|
|
1645
|
+
devices,
|
|
1646
|
+
mediaQueries,
|
|
1647
|
+
currentWidth: dimensions.width,
|
|
1648
|
+
currentHeight: dimensions.height,
|
|
1649
|
+
currentBreakpoint: screen,
|
|
1650
|
+
currentDevice: determineCurrentDevice(screen, devices),
|
|
1651
|
+
orientation
|
|
1652
|
+
}), [breakpoints, devices, mediaQueries, dimensions, screen, orientation]);
|
|
1653
|
+
return /*#__PURE__*/React__default.createElement(BreakpointContext.Provider, {
|
|
1654
|
+
value: breakpointValue
|
|
1655
|
+
}, /*#__PURE__*/React__default.createElement(WindowDimensionsContext.Provider, {
|
|
1656
|
+
value: dimensionsValue
|
|
1657
|
+
}, /*#__PURE__*/React__default.createElement(ResponsiveContext.Provider, {
|
|
1658
|
+
value: legacyValue
|
|
1659
|
+
}, children)));
|
|
1586
1660
|
};
|
|
1587
1661
|
|
|
1588
1662
|
const Shadows = {
|
|
@@ -1733,7 +1807,9 @@ const cssProperties = /*#__PURE__*/new Set([
|
|
|
1733
1807
|
// Borders
|
|
1734
1808
|
'border', 'borderWidth', 'borderStyle', 'borderColor', 'borderRadius', 'borderTop', 'borderRight', 'borderBottom', 'borderLeft', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius',
|
|
1735
1809
|
// Effects
|
|
1736
|
-
'boxShadow', 'textShadow', 'transform', 'transition', 'animation', '
|
|
1810
|
+
'boxShadow', 'textShadow', 'transform', 'transition', 'animation', 'animationName', 'animationDuration', 'animationTimingFunction', 'animationDelay', 'animationIterationCount', 'animationDirection', 'animationFillMode', 'animationPlayState',
|
|
1811
|
+
// Scroll-driven animation properties (CSS Scroll-Driven Animations spec)
|
|
1812
|
+
'animationTimeline', 'animationRange', 'animationRangeStart', 'animationRangeEnd', 'scrollTimeline', 'scrollTimelineName', 'scrollTimelineAxis', 'viewTimeline', 'viewTimelineName', 'viewTimelineAxis', 'viewTimelineInset', 'filter', 'backdropFilter', 'mixBlendMode',
|
|
1737
1813
|
// Layout
|
|
1738
1814
|
'display', 'visibility', 'overflow', 'overflowX', 'overflowY', 'float', 'clear', 'objectFit', 'objectPosition',
|
|
1739
1815
|
// Interactivity
|
|
@@ -1850,8 +1926,10 @@ const generateKeyframes = animation => {
|
|
|
1850
1926
|
playState,
|
|
1851
1927
|
timeline,
|
|
1852
1928
|
range,
|
|
1853
|
-
|
|
1929
|
+
keyframes: explicitKeyframes,
|
|
1930
|
+
...rest
|
|
1854
1931
|
} = animation;
|
|
1932
|
+
const keyframesDef = explicitKeyframes || rest;
|
|
1855
1933
|
// Générer une clé pour le cache basée sur les keyframes
|
|
1856
1934
|
const animationConfigString = JSON.stringify(keyframesDef);
|
|
1857
1935
|
if (keyframesCache.has(animationConfigString)) {
|
|
@@ -1878,14 +1956,14 @@ const generateKeyframes = animation => {
|
|
|
1878
1956
|
const styles = keyframesDef[key];
|
|
1879
1957
|
keyframesContent.push(`${cssKey} { ${styleObjectToCss(styles)} }`);
|
|
1880
1958
|
});
|
|
1881
|
-
const
|
|
1959
|
+
const keyframesCss = `
|
|
1882
1960
|
@keyframes ${keyframesName} {
|
|
1883
1961
|
${keyframesContent.join('\n')}
|
|
1884
1962
|
}
|
|
1885
1963
|
`;
|
|
1886
1964
|
return {
|
|
1887
1965
|
keyframesName,
|
|
1888
|
-
keyframes
|
|
1966
|
+
keyframes: keyframesCss
|
|
1889
1967
|
};
|
|
1890
1968
|
};
|
|
1891
1969
|
|
|
@@ -2138,16 +2216,28 @@ const AnimationUtils = {
|
|
|
2138
2216
|
(manager || utilityClassManager).injectRule(keyframes);
|
|
2139
2217
|
}
|
|
2140
2218
|
result.names.push(keyframesName);
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
|
|
2219
|
+
// For scroll-driven animations (with timeline), use 'auto' duration
|
|
2220
|
+
// For time-based animations, parse the duration normally
|
|
2221
|
+
const hasTimeline = !!animation.timeline;
|
|
2222
|
+
if (hasTimeline) {
|
|
2223
|
+
// Scroll-driven animations should use 'auto' duration
|
|
2224
|
+
// unless explicitly specified
|
|
2225
|
+
result.durations.push(animation.duration || 'auto');
|
|
2226
|
+
// Don't accumulate time for scroll-driven animations
|
|
2227
|
+
result.delays.push(animation.delay || '0s');
|
|
2228
|
+
} else {
|
|
2229
|
+
const durationMs = this.parseDuration(animation.duration || '0s');
|
|
2230
|
+
const delayMs = this.parseDuration(animation.delay || '0s');
|
|
2231
|
+
const totalDelayMs = cumulativeTime + delayMs;
|
|
2232
|
+
cumulativeTime = totalDelayMs + durationMs;
|
|
2233
|
+
result.durations.push(this.formatDuration(durationMs));
|
|
2234
|
+
result.delays.push(this.formatDuration(totalDelayMs));
|
|
2235
|
+
}
|
|
2146
2236
|
result.timingFunctions.push(animation.timingFunction || 'ease');
|
|
2147
|
-
result.delays.push(this.formatDuration(totalDelayMs));
|
|
2148
2237
|
result.iterationCounts.push(animation.iterationCount !== undefined ? `${animation.iterationCount}` : '1');
|
|
2149
2238
|
result.directions.push(animation.direction || 'normal');
|
|
2150
|
-
|
|
2239
|
+
// Default to 'both' fillMode for scroll-driven animations, 'none' for time-based
|
|
2240
|
+
result.fillModes.push(animation.fillMode || (hasTimeline ? 'both' : 'none'));
|
|
2151
2241
|
result.playStates.push(animation.playState || 'running');
|
|
2152
2242
|
result.timelines.push(animation.timeline || '');
|
|
2153
2243
|
result.ranges.push(animation.range || '');
|
|
@@ -2876,6 +2966,50 @@ const AnalyticsProvider = _ref => {
|
|
|
2876
2966
|
}, children);
|
|
2877
2967
|
};
|
|
2878
2968
|
|
|
2969
|
+
/**
|
|
2970
|
+
* Computes a stable hash of style-relevant props.
|
|
2971
|
+
* This is used to determine if style extraction needs to be re-run.
|
|
2972
|
+
*/
|
|
2973
|
+
function hashStyleProps(props) {
|
|
2974
|
+
// Build a deterministic string representation of style-relevant props
|
|
2975
|
+
const parts = [];
|
|
2976
|
+
const sortedKeys = Object.keys(props).sort();
|
|
2977
|
+
for (const key of sortedKeys) {
|
|
2978
|
+
// Skip non-style props that don't affect CSS generation
|
|
2979
|
+
if (key === 'children' || key === 'ref' || key === 'key') continue;
|
|
2980
|
+
// Include style-relevant props
|
|
2981
|
+
if (isStyleProp(key) || key.startsWith('_') || key === 'on' || key === 'media' || key === 'animate' || key === 'css' || key === 'shadow' || key === 'blend' || key === 'widthHeight' || key === 'paddingHorizontal' || key === 'paddingVertical' || key === 'marginHorizontal' || key === 'marginVertical') {
|
|
2982
|
+
const value = props[key];
|
|
2983
|
+
if (value !== undefined) {
|
|
2984
|
+
// Use JSON.stringify for consistent serialization
|
|
2985
|
+
parts.push(`${key}:${JSON.stringify(value)}`);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
return hash(parts.join('|'));
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Custom hook that memoizes style extraction based on a stable hash of props.
|
|
2993
|
+
* Only recalculates when the hash of style-relevant props changes.
|
|
2994
|
+
*/
|
|
2995
|
+
function useStableStyleMemo(propsToProcess, getColor, mediaQueries, devices, manager, theme) {
|
|
2996
|
+
const cacheRef = React.useRef(null);
|
|
2997
|
+
// Compute hash of current props
|
|
2998
|
+
const currentHash = React.useMemo(() => {
|
|
2999
|
+
// Include theme in hash to bust cache on theme changes
|
|
3000
|
+
const themeHash = theme ? JSON.stringify(theme) : '';
|
|
3001
|
+
return hashStyleProps(propsToProcess) + '|' + hash(themeHash);
|
|
3002
|
+
}, [propsToProcess, theme]);
|
|
3003
|
+
// Only recompute classes if hash changed
|
|
3004
|
+
if (!cacheRef.current || cacheRef.current.hash !== currentHash) {
|
|
3005
|
+
const classes = extractUtilityClasses(propsToProcess, getColor, mediaQueries, devices, manager);
|
|
3006
|
+
cacheRef.current = {
|
|
3007
|
+
hash: currentHash,
|
|
3008
|
+
classes
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
return cacheRef.current.classes;
|
|
3012
|
+
}
|
|
2879
3013
|
const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
2880
3014
|
let {
|
|
2881
3015
|
as = 'div',
|
|
@@ -2921,15 +3055,11 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
2921
3055
|
const {
|
|
2922
3056
|
mediaQueries,
|
|
2923
3057
|
devices
|
|
2924
|
-
} =
|
|
3058
|
+
} = useBreakpointContext();
|
|
2925
3059
|
const {
|
|
2926
3060
|
manager
|
|
2927
3061
|
} = useStyleRegistry();
|
|
2928
3062
|
const [isVisible, setIsVisible] = React.useState(false);
|
|
2929
|
-
console.log({
|
|
2930
|
-
mediaQueries,
|
|
2931
|
-
devices
|
|
2932
|
-
});
|
|
2933
3063
|
React.useEffect(() => {
|
|
2934
3064
|
if (!animateIn) {
|
|
2935
3065
|
setIsVisible(true);
|
|
@@ -2970,15 +3100,16 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
2970
3100
|
}
|
|
2971
3101
|
};
|
|
2972
3102
|
}, [animateOut, manager]);
|
|
2973
|
-
|
|
2974
|
-
|
|
3103
|
+
// Prepare props for processing (apply view/scroll timeline if needed)
|
|
3104
|
+
const propsToProcess = React.useMemo(() => {
|
|
3105
|
+
const processed = {
|
|
2975
3106
|
...rest,
|
|
2976
3107
|
blend
|
|
2977
3108
|
};
|
|
2978
3109
|
// Apply view() timeline ONLY if animateOn='View' (not Both or Mount)
|
|
2979
|
-
if (animateOn === 'View' &&
|
|
2980
|
-
const animations = Array.isArray(
|
|
2981
|
-
|
|
3110
|
+
if (animateOn === 'View' && processed.animate) {
|
|
3111
|
+
const animations = Array.isArray(processed.animate) ? processed.animate : [processed.animate];
|
|
3112
|
+
processed.animate = animations.map(anim => {
|
|
2982
3113
|
// Only add timeline if not already specified
|
|
2983
3114
|
if (!anim.timeline) {
|
|
2984
3115
|
return {
|
|
@@ -2992,9 +3123,9 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
2992
3123
|
});
|
|
2993
3124
|
}
|
|
2994
3125
|
// Apply scroll() timeline if animateOn='Scroll'
|
|
2995
|
-
if (animateOn === 'Scroll' &&
|
|
2996
|
-
const animations = Array.isArray(
|
|
2997
|
-
|
|
3126
|
+
if (animateOn === 'Scroll' && processed.animate) {
|
|
3127
|
+
const animations = Array.isArray(processed.animate) ? processed.animate : [processed.animate];
|
|
3128
|
+
processed.animate = animations.map(anim => {
|
|
2998
3129
|
// Only add timeline if not already specified
|
|
2999
3130
|
if (!anim.timeline) {
|
|
3000
3131
|
return {
|
|
@@ -3006,10 +3137,10 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
3006
3137
|
return anim;
|
|
3007
3138
|
});
|
|
3008
3139
|
}
|
|
3009
|
-
return
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3140
|
+
return processed;
|
|
3141
|
+
}, [rest, blend, animateOn]);
|
|
3142
|
+
// Use hash-based memoization for style extraction
|
|
3143
|
+
const utilityClasses = useStableStyleMemo(propsToProcess, getColor, mediaQueries, devices, manager, theme);
|
|
3013
3144
|
const newProps = {
|
|
3014
3145
|
ref: setRef
|
|
3015
3146
|
};
|
|
@@ -4921,20 +5052,23 @@ const WindowSizeContext = /*#__PURE__*/React.createContext({
|
|
|
4921
5052
|
});
|
|
4922
5053
|
const WindowSizeProvider = _ref => {
|
|
4923
5054
|
let {
|
|
4924
|
-
children
|
|
5055
|
+
children,
|
|
5056
|
+
targetWindow
|
|
4925
5057
|
} = _ref;
|
|
5058
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
4926
5059
|
const [size, setSize] = React.useState({
|
|
4927
|
-
width:
|
|
4928
|
-
height:
|
|
5060
|
+
width: win?.innerWidth || 0,
|
|
5061
|
+
height: win?.innerHeight || 0
|
|
4929
5062
|
});
|
|
4930
5063
|
React.useEffect(() => {
|
|
5064
|
+
if (!win) return;
|
|
4931
5065
|
const handleResize = () => setSize({
|
|
4932
|
-
width:
|
|
4933
|
-
height:
|
|
5066
|
+
width: win.innerWidth,
|
|
5067
|
+
height: win.innerHeight
|
|
4934
5068
|
});
|
|
4935
|
-
|
|
4936
|
-
return () =>
|
|
4937
|
-
}, []);
|
|
5069
|
+
win.addEventListener('resize', handleResize);
|
|
5070
|
+
return () => win.removeEventListener('resize', handleResize);
|
|
5071
|
+
}, [win]);
|
|
4938
5072
|
return /*#__PURE__*/React__default.createElement(WindowSizeContext.Provider, {
|
|
4939
5073
|
value: size
|
|
4940
5074
|
}, children);
|
|
@@ -4966,10 +5100,16 @@ function useActive() {
|
|
|
4966
5100
|
return [ref, active];
|
|
4967
5101
|
}
|
|
4968
5102
|
|
|
4969
|
-
function useClickOutside() {
|
|
5103
|
+
function useClickOutside(options) {
|
|
4970
5104
|
const [clickedOutside, setClickedOutside] = React.useState(false);
|
|
4971
5105
|
const ref = React.useRef(null);
|
|
5106
|
+
const {
|
|
5107
|
+
targetWindow
|
|
5108
|
+
} = options || {};
|
|
4972
5109
|
React.useEffect(() => {
|
|
5110
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
5111
|
+
if (!win) return;
|
|
5112
|
+
const doc = win.document;
|
|
4973
5113
|
const handleClick = e => {
|
|
4974
5114
|
if (ref.current && !ref.current.contains(e.target)) {
|
|
4975
5115
|
setClickedOutside(true);
|
|
@@ -4977,11 +5117,11 @@ function useClickOutside() {
|
|
|
4977
5117
|
setClickedOutside(false);
|
|
4978
5118
|
}
|
|
4979
5119
|
};
|
|
4980
|
-
|
|
5120
|
+
doc.addEventListener('mousedown', handleClick);
|
|
4981
5121
|
return () => {
|
|
4982
|
-
|
|
5122
|
+
doc.removeEventListener('mousedown', handleClick);
|
|
4983
5123
|
};
|
|
4984
|
-
}, []);
|
|
5124
|
+
}, [targetWindow]);
|
|
4985
5125
|
return [ref, clickedOutside];
|
|
4986
5126
|
}
|
|
4987
5127
|
|
|
@@ -4998,7 +5138,8 @@ function useElementPosition(options) {
|
|
|
4998
5138
|
throttleMs = 500,
|
|
4999
5139
|
trackOnHover = true,
|
|
5000
5140
|
trackOnScroll = false,
|
|
5001
|
-
trackOnResize = false
|
|
5141
|
+
trackOnResize = false,
|
|
5142
|
+
targetWindow
|
|
5002
5143
|
} = options;
|
|
5003
5144
|
const elementRef = React.useRef(null);
|
|
5004
5145
|
const [relation, setRelation] = React.useState(null);
|
|
@@ -5009,9 +5150,10 @@ function useElementPosition(options) {
|
|
|
5009
5150
|
setRelation(currentRelation => currentRelation === null ? null : null);
|
|
5010
5151
|
return;
|
|
5011
5152
|
}
|
|
5153
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
5012
5154
|
const rect = element.getBoundingClientRect();
|
|
5013
|
-
const viewportHeight =
|
|
5014
|
-
const viewportWidth =
|
|
5155
|
+
const viewportHeight = win.innerHeight;
|
|
5156
|
+
const viewportWidth = win.innerWidth;
|
|
5015
5157
|
// 1. Determine element's general position in viewport
|
|
5016
5158
|
const elementCenterY = rect.top + rect.height / 2;
|
|
5017
5159
|
const elementCenterX = rect.left + rect.width / 2;
|
|
@@ -5040,7 +5182,7 @@ function useElementPosition(options) {
|
|
|
5040
5182
|
}
|
|
5041
5183
|
return newRelation;
|
|
5042
5184
|
});
|
|
5043
|
-
}, []); // This callback is stable
|
|
5185
|
+
}, [targetWindow]); // This callback is stable
|
|
5044
5186
|
const throttledUpdate = React.useCallback(() => {
|
|
5045
5187
|
if (throttleTimerRef.current) {
|
|
5046
5188
|
clearTimeout(throttleTimerRef.current);
|
|
@@ -5058,6 +5200,7 @@ function useElementPosition(options) {
|
|
|
5058
5200
|
}
|
|
5059
5201
|
const element = elementRef.current;
|
|
5060
5202
|
if (!element) return;
|
|
5203
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
5061
5204
|
const handler = throttledUpdate;
|
|
5062
5205
|
const immediateHandler = calculateRelation;
|
|
5063
5206
|
// Add event listeners based on configuration
|
|
@@ -5073,18 +5216,18 @@ function useElementPosition(options) {
|
|
|
5073
5216
|
}
|
|
5074
5217
|
// Scroll events - throttled
|
|
5075
5218
|
if (trackOnScroll) {
|
|
5076
|
-
|
|
5219
|
+
win.addEventListener('scroll', handler, {
|
|
5077
5220
|
passive: true
|
|
5078
5221
|
});
|
|
5079
5222
|
cleanupFunctions.push(() => {
|
|
5080
|
-
|
|
5223
|
+
win.removeEventListener('scroll', handler);
|
|
5081
5224
|
});
|
|
5082
5225
|
}
|
|
5083
5226
|
// Resize events - throttled
|
|
5084
5227
|
if (trackOnResize) {
|
|
5085
|
-
|
|
5228
|
+
win.addEventListener('resize', handler);
|
|
5086
5229
|
cleanupFunctions.push(() => {
|
|
5087
|
-
|
|
5230
|
+
win.removeEventListener('resize', handler);
|
|
5088
5231
|
});
|
|
5089
5232
|
}
|
|
5090
5233
|
return () => {
|
|
@@ -5093,7 +5236,7 @@ function useElementPosition(options) {
|
|
|
5093
5236
|
}
|
|
5094
5237
|
cleanupFunctions.forEach(cleanup => cleanup());
|
|
5095
5238
|
};
|
|
5096
|
-
}, [trackChanges, trackOnHover, trackOnScroll, trackOnResize, throttledUpdate, calculateRelation]);
|
|
5239
|
+
}, [trackChanges, trackOnHover, trackOnScroll, trackOnResize, throttledUpdate, calculateRelation, targetWindow]);
|
|
5097
5240
|
const manualUpdateRelation = React.useCallback(() => {
|
|
5098
5241
|
calculateRelation();
|
|
5099
5242
|
}, [calculateRelation]);
|
|
@@ -5168,21 +5311,84 @@ const useMount = callback => {
|
|
|
5168
5311
|
function useOnScreen(options) {
|
|
5169
5312
|
const ref = React.useRef(null);
|
|
5170
5313
|
const [isOnScreen, setOnScreen] = React.useState(false);
|
|
5314
|
+
const {
|
|
5315
|
+
targetWindow,
|
|
5316
|
+
...observerOptions
|
|
5317
|
+
} = options || {};
|
|
5171
5318
|
React.useEffect(() => {
|
|
5172
5319
|
const node = ref.current;
|
|
5173
5320
|
if (!node) return;
|
|
5321
|
+
// If targetWindow is provided and root is not explicitly set,
|
|
5322
|
+
// use the target window's document as the root
|
|
5323
|
+
const win = targetWindow || node.ownerDocument?.defaultView || window;
|
|
5324
|
+
const effectiveRoot = observerOptions.root !== undefined ? observerOptions.root : targetWindow ? win.document.documentElement : undefined;
|
|
5174
5325
|
const observer = new IntersectionObserver(_ref => {
|
|
5175
5326
|
let [entry] = _ref;
|
|
5176
5327
|
setOnScreen(entry.isIntersecting);
|
|
5177
|
-
},
|
|
5328
|
+
}, {
|
|
5329
|
+
...observerOptions,
|
|
5330
|
+
root: effectiveRoot
|
|
5331
|
+
});
|
|
5178
5332
|
observer.observe(node);
|
|
5179
5333
|
return () => {
|
|
5180
5334
|
observer.disconnect();
|
|
5181
5335
|
};
|
|
5182
|
-
}, [options]);
|
|
5336
|
+
}, [targetWindow, options]);
|
|
5183
5337
|
return [ref, isOnScreen];
|
|
5184
5338
|
}
|
|
5185
5339
|
|
|
5340
|
+
/**
|
|
5341
|
+
* Optimized hook for components that only need breakpoint information.
|
|
5342
|
+
* This hook will NOT cause re-renders on every window resize,
|
|
5343
|
+
* only when the breakpoint actually changes.
|
|
5344
|
+
*
|
|
5345
|
+
* Use this hook instead of useResponsive for better performance
|
|
5346
|
+
* when you only need to check breakpoints or device type.
|
|
5347
|
+
*
|
|
5348
|
+
* @example
|
|
5349
|
+
* const { screen, on, is, orientation } = useBreakpoint();
|
|
5350
|
+
* if (on('mobile')) { ... }
|
|
5351
|
+
* if (is('xs')) { ... }
|
|
5352
|
+
*/
|
|
5353
|
+
const useBreakpoint = () => {
|
|
5354
|
+
const context = useBreakpointContext();
|
|
5355
|
+
const {
|
|
5356
|
+
currentBreakpoint: screen,
|
|
5357
|
+
orientation,
|
|
5358
|
+
devices
|
|
5359
|
+
} = context;
|
|
5360
|
+
// Helper to check if current screen matches a breakpoint or device
|
|
5361
|
+
const on = s => devices[s] ? devices[s].includes(screen) : s === screen;
|
|
5362
|
+
return {
|
|
5363
|
+
...context,
|
|
5364
|
+
screen,
|
|
5365
|
+
orientation,
|
|
5366
|
+
on,
|
|
5367
|
+
is: on
|
|
5368
|
+
};
|
|
5369
|
+
};
|
|
5370
|
+
/**
|
|
5371
|
+
* Hook for components that need exact window dimensions.
|
|
5372
|
+
* This hook WILL cause re-renders on every window resize.
|
|
5373
|
+
* Use sparingly - prefer useBreakpoint when possible.
|
|
5374
|
+
*
|
|
5375
|
+
* @example
|
|
5376
|
+
* const { width, height } = useWindowDimensions();
|
|
5377
|
+
*/
|
|
5378
|
+
const useWindowDimensions = () => {
|
|
5379
|
+
return useWindowDimensionsContext();
|
|
5380
|
+
};
|
|
5381
|
+
/**
|
|
5382
|
+
* Combined hook that provides both breakpoint info and window dimensions.
|
|
5383
|
+
* This hook WILL cause re-renders on every window resize.
|
|
5384
|
+
*
|
|
5385
|
+
* For better performance, use:
|
|
5386
|
+
* - useBreakpoint() when you only need breakpoint/device info
|
|
5387
|
+
* - useWindowDimensions() when you only need exact dimensions
|
|
5388
|
+
*
|
|
5389
|
+
* @example
|
|
5390
|
+
* const { screen, on, currentWidth, currentHeight } = useResponsive();
|
|
5391
|
+
*/
|
|
5186
5392
|
const useResponsive = () => {
|
|
5187
5393
|
const context = useResponsiveContext();
|
|
5188
5394
|
const {
|
|
@@ -5198,7 +5404,6 @@ const useResponsive = () => {
|
|
|
5198
5404
|
on,
|
|
5199
5405
|
is: on
|
|
5200
5406
|
};
|
|
5201
|
-
// console.log('[useResponsive] Hook called, returning:', { screen, orientation, 'on(mobile)': on('mobile') });
|
|
5202
5407
|
return result;
|
|
5203
5408
|
};
|
|
5204
5409
|
|
|
@@ -5368,27 +5573,28 @@ const useScrollAnimation = function (ref, options) {
|
|
|
5368
5573
|
};
|
|
5369
5574
|
};
|
|
5370
5575
|
// Enhanced useSmoothScroll with error handling
|
|
5371
|
-
const useSmoothScroll =
|
|
5576
|
+
const useSmoothScroll = targetWindow => {
|
|
5372
5577
|
return React.useCallback(function (element, offset) {
|
|
5373
5578
|
if (offset === void 0) {
|
|
5374
5579
|
offset = 0;
|
|
5375
5580
|
}
|
|
5376
5581
|
if (!element) return;
|
|
5377
5582
|
try {
|
|
5378
|
-
const
|
|
5379
|
-
|
|
5380
|
-
|
|
5583
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
5584
|
+
const top = element.getBoundingClientRect().top + (win.scrollY || win.pageYOffset) - offset;
|
|
5585
|
+
if ('scrollBehavior' in win.document.documentElement.style) {
|
|
5586
|
+
win.scrollTo({
|
|
5381
5587
|
top,
|
|
5382
5588
|
behavior: 'smooth'
|
|
5383
5589
|
});
|
|
5384
5590
|
} else {
|
|
5385
5591
|
// Fallback for browsers that don't support smooth scrolling
|
|
5386
|
-
|
|
5592
|
+
win.scrollTo(0, top);
|
|
5387
5593
|
}
|
|
5388
5594
|
} catch (error) {
|
|
5389
5595
|
console.error('Error during smooth scroll:', error);
|
|
5390
5596
|
}
|
|
5391
|
-
}, []);
|
|
5597
|
+
}, [targetWindow]);
|
|
5392
5598
|
};
|
|
5393
5599
|
// Enhanced useInfiniteScroll with debouncing
|
|
5394
5600
|
const useInfiniteScroll = function (callback, options) {
|
|
@@ -5432,7 +5638,7 @@ const useInfiniteScroll = function (callback, options) {
|
|
|
5432
5638
|
sentinelRef: setSentinel
|
|
5433
5639
|
};
|
|
5434
5640
|
};
|
|
5435
|
-
const useScrollDirection = function (threshold) {
|
|
5641
|
+
const useScrollDirection = function (threshold, targetWindow) {
|
|
5436
5642
|
if (threshold === void 0) {
|
|
5437
5643
|
threshold = 5;
|
|
5438
5644
|
}
|
|
@@ -5441,12 +5647,15 @@ const useScrollDirection = function (threshold) {
|
|
|
5441
5647
|
const lastDirection = React.useRef('up');
|
|
5442
5648
|
const animationFrame = React.useRef();
|
|
5443
5649
|
const ticking = React.useRef(false);
|
|
5444
|
-
const updateDirection = () => {
|
|
5445
|
-
const
|
|
5650
|
+
const updateDirection = React.useCallback(() => {
|
|
5651
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
5652
|
+
if (!win) return;
|
|
5653
|
+
const doc = win.document.documentElement;
|
|
5654
|
+
const scrollY = win.scrollY || doc.scrollTop;
|
|
5446
5655
|
const direction = scrollY > lastScrollY.current ? 'down' : 'up';
|
|
5447
5656
|
const scrollDelta = Math.abs(scrollY - lastScrollY.current);
|
|
5448
5657
|
// Vérifier si on est au bas de la page
|
|
5449
|
-
const isAtBottom =
|
|
5658
|
+
const isAtBottom = win.innerHeight + scrollY >= doc.scrollHeight - 1;
|
|
5450
5659
|
// Logique principale
|
|
5451
5660
|
if (scrollDelta > threshold || direction === 'down' && isAtBottom) {
|
|
5452
5661
|
if (direction !== lastDirection.current) {
|
|
@@ -5457,8 +5666,10 @@ const useScrollDirection = function (threshold) {
|
|
|
5457
5666
|
// Mise à jour de la position avec un minimum de 0
|
|
5458
5667
|
lastScrollY.current = Math.max(scrollY, 0);
|
|
5459
5668
|
ticking.current = false;
|
|
5460
|
-
};
|
|
5669
|
+
}, [threshold, targetWindow]);
|
|
5461
5670
|
React.useEffect(() => {
|
|
5671
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
5672
|
+
if (!win) return;
|
|
5462
5673
|
const handleScroll = () => {
|
|
5463
5674
|
if (!ticking.current) {
|
|
5464
5675
|
animationFrame.current = requestAnimationFrame(() => {
|
|
@@ -5468,16 +5679,16 @@ const useScrollDirection = function (threshold) {
|
|
|
5468
5679
|
ticking.current = true;
|
|
5469
5680
|
}
|
|
5470
5681
|
};
|
|
5471
|
-
|
|
5682
|
+
win.addEventListener('scroll', handleScroll, {
|
|
5472
5683
|
passive: true
|
|
5473
5684
|
});
|
|
5474
5685
|
return () => {
|
|
5475
|
-
|
|
5686
|
+
win.removeEventListener('scroll', handleScroll);
|
|
5476
5687
|
if (animationFrame.current) {
|
|
5477
5688
|
cancelAnimationFrame(animationFrame.current);
|
|
5478
5689
|
}
|
|
5479
5690
|
};
|
|
5480
|
-
}, [
|
|
5691
|
+
}, [updateDirection, targetWindow]);
|
|
5481
5692
|
return scrollDirection;
|
|
5482
5693
|
};
|
|
5483
5694
|
|
|
@@ -5486,6 +5697,7 @@ const useWindowSize = () => React.useContext(WindowSizeContext);
|
|
|
5486
5697
|
function useInView(options) {
|
|
5487
5698
|
const {
|
|
5488
5699
|
triggerOnce = false,
|
|
5700
|
+
targetWindow,
|
|
5489
5701
|
...observerOptions
|
|
5490
5702
|
} = options || {};
|
|
5491
5703
|
const ref = React.useRef(null);
|
|
@@ -5493,6 +5705,10 @@ function useInView(options) {
|
|
|
5493
5705
|
React.useEffect(() => {
|
|
5494
5706
|
const element = ref.current;
|
|
5495
5707
|
if (!element) return;
|
|
5708
|
+
// If targetWindow is provided and root is not explicitly set,
|
|
5709
|
+
// use the target window's document as the root
|
|
5710
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
5711
|
+
const effectiveRoot = observerOptions.root !== undefined ? observerOptions.root : targetWindow ? win.document.documentElement : undefined;
|
|
5496
5712
|
const observer = new IntersectionObserver(_ref => {
|
|
5497
5713
|
let [entry] = _ref;
|
|
5498
5714
|
if (entry.isIntersecting) {
|
|
@@ -5505,18 +5721,121 @@ function useInView(options) {
|
|
|
5505
5721
|
// Only update to false if not using triggerOnce
|
|
5506
5722
|
setInView(false);
|
|
5507
5723
|
}
|
|
5508
|
-
},
|
|
5724
|
+
}, {
|
|
5725
|
+
...observerOptions,
|
|
5726
|
+
root: effectiveRoot
|
|
5727
|
+
});
|
|
5509
5728
|
observer.observe(element);
|
|
5510
5729
|
return () => {
|
|
5511
5730
|
observer.disconnect();
|
|
5512
5731
|
};
|
|
5513
|
-
}, [triggerOnce, ...Object.values(observerOptions || {})]);
|
|
5732
|
+
}, [triggerOnce, targetWindow, ...Object.values(observerOptions || {})]);
|
|
5514
5733
|
return {
|
|
5515
5734
|
ref,
|
|
5516
5735
|
inView
|
|
5517
5736
|
};
|
|
5518
5737
|
}
|
|
5519
5738
|
|
|
5739
|
+
/**
|
|
5740
|
+
* Hook to register an iframe's document with the style manager.
|
|
5741
|
+
* This ensures that all CSS utility classes are automatically injected into the iframe.
|
|
5742
|
+
*
|
|
5743
|
+
* @param iframeRef - Reference to the iframe element
|
|
5744
|
+
*
|
|
5745
|
+
* @example
|
|
5746
|
+
* ```tsx
|
|
5747
|
+
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
5748
|
+
* useIframeStyles(iframeRef);
|
|
5749
|
+
*
|
|
5750
|
+
* return <iframe ref={iframeRef} src="/content" />;
|
|
5751
|
+
* ```
|
|
5752
|
+
*/
|
|
5753
|
+
function useIframeStyles(iframeRef) {
|
|
5754
|
+
const {
|
|
5755
|
+
manager
|
|
5756
|
+
} = useStyleRegistry();
|
|
5757
|
+
const registeredDocRef = React.useRef(null);
|
|
5758
|
+
React.useEffect(() => {
|
|
5759
|
+
const iframe = iframeRef.current;
|
|
5760
|
+
if (!iframe) return;
|
|
5761
|
+
const registerDocument = () => {
|
|
5762
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
5763
|
+
if (!iframeDoc) return;
|
|
5764
|
+
// Only register if not already registered
|
|
5765
|
+
if (registeredDocRef.current !== iframeDoc) {
|
|
5766
|
+
manager.addDocument(iframeDoc);
|
|
5767
|
+
registeredDocRef.current = iframeDoc;
|
|
5768
|
+
}
|
|
5769
|
+
};
|
|
5770
|
+
// Try to register immediately if document is ready
|
|
5771
|
+
if (iframe.contentDocument) {
|
|
5772
|
+
registerDocument();
|
|
5773
|
+
}
|
|
5774
|
+
// Also register on load event (for external sources)
|
|
5775
|
+
iframe.addEventListener('load', registerDocument);
|
|
5776
|
+
return () => {
|
|
5777
|
+
iframe.removeEventListener('load', registerDocument);
|
|
5778
|
+
// Clean up when unmounting
|
|
5779
|
+
if (registeredDocRef.current) {
|
|
5780
|
+
manager.removeDocument(registeredDocRef.current);
|
|
5781
|
+
registeredDocRef.current = null;
|
|
5782
|
+
}
|
|
5783
|
+
};
|
|
5784
|
+
}, [manager, iframeRef]);
|
|
5785
|
+
}
|
|
5786
|
+
/**
|
|
5787
|
+
* Hook to get an iframe's window and document, and automatically register styles.
|
|
5788
|
+
* This is a convenience hook that combines iframe access with style registration.
|
|
5789
|
+
*
|
|
5790
|
+
* @param iframeRef - Reference to the iframe element
|
|
5791
|
+
* @returns Object containing iframeWindow, iframeDocument, and isLoaded flag
|
|
5792
|
+
*
|
|
5793
|
+
* @example
|
|
5794
|
+
* ```tsx
|
|
5795
|
+
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
5796
|
+
* const { iframeWindow, iframeDocument, isLoaded } = useIframe(iframeRef);
|
|
5797
|
+
*
|
|
5798
|
+
* return (
|
|
5799
|
+
* <>
|
|
5800
|
+
* <iframe ref={iframeRef} src="/content" />
|
|
5801
|
+
* {isLoaded && <div>Iframe loaded!</div>}
|
|
5802
|
+
* </>
|
|
5803
|
+
* );
|
|
5804
|
+
* ```
|
|
5805
|
+
*/
|
|
5806
|
+
function useIframe(iframeRef) {
|
|
5807
|
+
const [iframeWindow, setIframeWindow] = React.useState(null);
|
|
5808
|
+
const [iframeDocument, setIframeDocument] = React.useState(null);
|
|
5809
|
+
const [isLoaded, setIsLoaded] = React.useState(false);
|
|
5810
|
+
// Register styles
|
|
5811
|
+
useIframeStyles(iframeRef);
|
|
5812
|
+
React.useEffect(() => {
|
|
5813
|
+
const iframe = iframeRef.current;
|
|
5814
|
+
if (!iframe) return;
|
|
5815
|
+
const updateState = () => {
|
|
5816
|
+
const win = iframe.contentWindow;
|
|
5817
|
+
const doc = iframe.contentDocument || win?.document;
|
|
5818
|
+
if (win && doc) {
|
|
5819
|
+
setIframeWindow(win);
|
|
5820
|
+
setIframeDocument(doc);
|
|
5821
|
+
setIsLoaded(true);
|
|
5822
|
+
}
|
|
5823
|
+
};
|
|
5824
|
+
// Try immediately
|
|
5825
|
+
updateState();
|
|
5826
|
+
// Listen for load event
|
|
5827
|
+
iframe.addEventListener('load', updateState);
|
|
5828
|
+
return () => {
|
|
5829
|
+
iframe.removeEventListener('load', updateState);
|
|
5830
|
+
};
|
|
5831
|
+
}, [iframeRef]);
|
|
5832
|
+
return {
|
|
5833
|
+
iframeWindow,
|
|
5834
|
+
iframeDocument,
|
|
5835
|
+
isLoaded
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
|
|
5520
5839
|
/**
|
|
5521
5840
|
* View Animation Utilities
|
|
5522
5841
|
*
|
|
@@ -6091,6 +6410,7 @@ exports.AnalyticsContext = AnalyticsContext;
|
|
|
6091
6410
|
exports.AnalyticsProvider = AnalyticsProvider;
|
|
6092
6411
|
exports.Animation = Animation;
|
|
6093
6412
|
exports.AnimationUtils = AnimationUtils;
|
|
6413
|
+
exports.BreakpointContext = BreakpointContext;
|
|
6094
6414
|
exports.Button = Button;
|
|
6095
6415
|
exports.Center = Center;
|
|
6096
6416
|
exports.Div = Div;
|
|
@@ -6117,6 +6437,7 @@ exports.UtilityClassManager = UtilityClassManager;
|
|
|
6117
6437
|
exports.Vertical = Vertical;
|
|
6118
6438
|
exports.VerticalResponsive = VerticalResponsive;
|
|
6119
6439
|
exports.View = View;
|
|
6440
|
+
exports.WindowDimensionsContext = WindowDimensionsContext;
|
|
6120
6441
|
exports.WindowSizeContext = WindowSizeContext;
|
|
6121
6442
|
exports.WindowSizeProvider = WindowSizeProvider;
|
|
6122
6443
|
exports.animateOnView = animateOnView;
|
|
@@ -6156,10 +6477,14 @@ exports.slideRightOnView = slideRightOnView;
|
|
|
6156
6477
|
exports.slideUpOnView = slideUpOnView;
|
|
6157
6478
|
exports.useActive = useActive;
|
|
6158
6479
|
exports.useAnalytics = useAnalytics;
|
|
6480
|
+
exports.useBreakpoint = useBreakpoint;
|
|
6481
|
+
exports.useBreakpointContext = useBreakpointContext;
|
|
6159
6482
|
exports.useClickOutside = useClickOutside;
|
|
6160
6483
|
exports.useElementPosition = useElementPosition;
|
|
6161
6484
|
exports.useFocus = useFocus;
|
|
6162
6485
|
exports.useHover = useHover;
|
|
6486
|
+
exports.useIframe = useIframe;
|
|
6487
|
+
exports.useIframeStyles = useIframeStyles;
|
|
6163
6488
|
exports.useInView = useInView;
|
|
6164
6489
|
exports.useInfiniteScroll = useInfiniteScroll;
|
|
6165
6490
|
exports.useKeyPress = useKeyPress;
|
|
@@ -6174,6 +6499,8 @@ exports.useServerInsertedHTML = useServerInsertedHTML;
|
|
|
6174
6499
|
exports.useSmoothScroll = useSmoothScroll;
|
|
6175
6500
|
exports.useStyleRegistry = useStyleRegistry;
|
|
6176
6501
|
exports.useTheme = useTheme;
|
|
6502
|
+
exports.useWindowDimensions = useWindowDimensions;
|
|
6503
|
+
exports.useWindowDimensionsContext = useWindowDimensionsContext;
|
|
6177
6504
|
exports.useWindowSize = useWindowSize;
|
|
6178
6505
|
exports.utilityClassManager = utilityClassManager;
|
|
6179
6506
|
exports.viewAnimationPresets = viewAnimationPresets;
|