app-studio 0.6.58 → 0.6.60
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 +520 -121
- 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 +513 -122
- package/dist/app-studio.esm.js.map +1 -1
- package/dist/app-studio.umd.development.js +520 -121
- 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 +4 -2
- package/dist/providers/WindowSize.d.ts +5 -2
- 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
|
@@ -900,7 +900,8 @@ const ThemeContext = /*#__PURE__*/React.createContext({
|
|
|
900
900
|
getColor: () => '',
|
|
901
901
|
getColorHex: () => '',
|
|
902
902
|
getColorRGBA: () => '',
|
|
903
|
-
getColorScheme: () =>
|
|
903
|
+
getColorScheme: () => undefined,
|
|
904
|
+
getContrastColor: () => 'black',
|
|
904
905
|
theme: {},
|
|
905
906
|
colors: {
|
|
906
907
|
main: defaultLightColors,
|
|
@@ -1071,7 +1072,8 @@ const ThemeProvider = _ref => {
|
|
|
1071
1072
|
dark: darkOverride = {},
|
|
1072
1073
|
light: lightOverride = {},
|
|
1073
1074
|
children,
|
|
1074
|
-
strict = false
|
|
1075
|
+
strict = false,
|
|
1076
|
+
targetWindow
|
|
1075
1077
|
} = _ref;
|
|
1076
1078
|
const [themeMode, setThemeMode] = React.useState(initialMode);
|
|
1077
1079
|
const colorCache = React.useRef(new Map()).current;
|
|
@@ -1292,23 +1294,135 @@ const ThemeProvider = _ref => {
|
|
|
1292
1294
|
if (!override) colorCache.set(cacheKey, rgba);
|
|
1293
1295
|
return rgba;
|
|
1294
1296
|
}, [themeMode, colorCache, resolveColorTokenForMode]);
|
|
1295
|
-
const getColorScheme = React.useCallback(override => {
|
|
1296
|
-
|
|
1297
|
-
|
|
1297
|
+
const getColorScheme = React.useCallback((name, override) => {
|
|
1298
|
+
if (!name || typeof name !== 'string') return undefined;
|
|
1299
|
+
const effectiveMode = override?.themeMode ?? themeMode;
|
|
1300
|
+
const effectiveTheme = override?.theme ? deepMerge(mergedTheme, override.theme) : mergedTheme;
|
|
1301
|
+
// Resolve theme.* tokens to get the underlying color token
|
|
1302
|
+
let colorToken = name;
|
|
1303
|
+
if (name.startsWith(THEME_PREFIX)) {
|
|
1304
|
+
const themeKey = name.substring(THEME_PREFIX.length);
|
|
1305
|
+
const themeValue = effectiveTheme[themeKey];
|
|
1306
|
+
if (typeof themeValue === 'string') {
|
|
1307
|
+
colorToken = themeValue;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
// Handle light.* or dark.* prefixes
|
|
1311
|
+
if (colorToken.startsWith('light.') || colorToken.startsWith('dark.')) {
|
|
1312
|
+
const prefixLength = colorToken.startsWith('light.') ? 6 : 5;
|
|
1313
|
+
colorToken = `${COLOR_PREFIX}${colorToken.substring(prefixLength)}`;
|
|
1314
|
+
}
|
|
1315
|
+
// Extract color scheme from color.* tokens (e.g., color.blue.500 -> 'blue')
|
|
1316
|
+
if (colorToken.startsWith(COLOR_PREFIX)) {
|
|
1317
|
+
const keys = colorToken.substring(COLOR_PREFIX.length).split('.');
|
|
1318
|
+
if (keys.length >= 1) {
|
|
1319
|
+
return keys[0]; // Return the color scheme name (e.g., 'blue', 'pink')
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
// Handle hex or rgba colors by finding the closest match in the palette
|
|
1323
|
+
const normalizedInput = normalizeToHex(colorToken).toLowerCase();
|
|
1324
|
+
if (normalizedInput.startsWith('#')) {
|
|
1325
|
+
const colorsToUse = themeColors[effectiveMode];
|
|
1326
|
+
const palette = deepMerge(colorsToUse.palette, override?.colors?.palette || {});
|
|
1327
|
+
const main = deepMerge(colorsToUse.main, override?.colors?.main || {});
|
|
1328
|
+
// First check main colors for exact match
|
|
1329
|
+
for (const [colorName, colorValue] of Object.entries(main)) {
|
|
1330
|
+
if (typeof colorValue === 'string') {
|
|
1331
|
+
const normalizedPalette = normalizeToHex(colorValue).toLowerCase();
|
|
1332
|
+
if (normalizedPalette === normalizedInput) {
|
|
1333
|
+
return colorName;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
// Then check palette colors for exact match
|
|
1338
|
+
for (const [colorName, shades] of Object.entries(palette)) {
|
|
1339
|
+
if (typeof shades === 'object' && shades !== null) {
|
|
1340
|
+
for (const [, shadeValue] of Object.entries(shades)) {
|
|
1341
|
+
if (typeof shadeValue === 'string') {
|
|
1342
|
+
const normalizedPalette = normalizeToHex(shadeValue).toLowerCase();
|
|
1343
|
+
if (normalizedPalette === normalizedInput) {
|
|
1344
|
+
return colorName;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return undefined;
|
|
1352
|
+
}, [mergedTheme, themeMode, themeColors]);
|
|
1353
|
+
const getContrastColor = React.useCallback((name, override) => {
|
|
1354
|
+
if (!name || typeof name !== 'string') return 'black';
|
|
1355
|
+
const effectiveMode = override?.themeMode ?? themeMode;
|
|
1356
|
+
// First resolve the color to a hex value
|
|
1357
|
+
let hexColor;
|
|
1358
|
+
// Check if it's already a hex or rgb color
|
|
1359
|
+
if (name.startsWith('#') || name.startsWith('rgb')) {
|
|
1360
|
+
hexColor = normalizeToHex(name);
|
|
1361
|
+
} else {
|
|
1362
|
+
// Resolve the token to get the actual color value
|
|
1363
|
+
const resolved = resolveColorTokenForMode(name, effectiveMode, override);
|
|
1364
|
+
hexColor = normalizeToHex(resolved);
|
|
1365
|
+
}
|
|
1366
|
+
// If we couldn't get a valid hex, default to black
|
|
1367
|
+
if (!hexColor.startsWith('#') || hexColor.length < 7) {
|
|
1368
|
+
return 'black';
|
|
1369
|
+
}
|
|
1370
|
+
// Extract RGB values
|
|
1371
|
+
const hex = hexColor.slice(1);
|
|
1372
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
1373
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
1374
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
1375
|
+
// Calculate relative luminance using the sRGB formula
|
|
1376
|
+
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
1377
|
+
const toLinear = c => {
|
|
1378
|
+
const sRGB = c / 255;
|
|
1379
|
+
return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4);
|
|
1380
|
+
};
|
|
1381
|
+
const luminance = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
1382
|
+
// Use threshold of 0.179 (WCAG recommendation)
|
|
1383
|
+
// Return white for dark colors, black for light colors
|
|
1384
|
+
return luminance > 0.179 ? 'black' : 'white';
|
|
1385
|
+
}, [themeMode, resolveColorTokenForMode]);
|
|
1298
1386
|
// --- Memoize Context Value ---
|
|
1299
1387
|
const contextValue = React.useMemo(() => ({
|
|
1300
1388
|
getColor,
|
|
1301
1389
|
getColorHex,
|
|
1302
1390
|
getColorRGBA,
|
|
1303
1391
|
getColorScheme,
|
|
1392
|
+
getContrastColor,
|
|
1304
1393
|
theme: mergedTheme,
|
|
1305
1394
|
colors: currentColors,
|
|
1306
1395
|
themeMode,
|
|
1307
1396
|
setThemeMode
|
|
1308
|
-
}), [getColor, getColorHex, getColorRGBA, getColorScheme, mergedTheme, currentColors, themeMode]);
|
|
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]);
|
|
1309
1423
|
return /*#__PURE__*/React__default.createElement(ThemeContext.Provider, {
|
|
1310
1424
|
value: contextValue
|
|
1311
|
-
}, /*#__PURE__*/React__default.createElement("style", null,
|
|
1425
|
+
}, !targetWindow && /*#__PURE__*/React__default.createElement("style", null, cssVariables), /*#__PURE__*/React__default.createElement("div", {
|
|
1312
1426
|
"data-theme": themeMode,
|
|
1313
1427
|
style: {
|
|
1314
1428
|
backgroundColor: 'white',
|
|
@@ -1377,32 +1491,57 @@ const debounce = (func, wait) => {
|
|
|
1377
1491
|
};
|
|
1378
1492
|
// Helper to compute breakpoint from width
|
|
1379
1493
|
const getBreakpointFromWidth = (width, breakpoints) => {
|
|
1380
|
-
// console.log('[ResponsiveProvider] Computing breakpoint for width:', width);
|
|
1381
|
-
// console.log('[ResponsiveProvider] Breakpoints config:', breakpoints);
|
|
1382
1494
|
const sortedBreakpoints = Object.entries(breakpoints).sort((_ref, _ref2) => {
|
|
1383
1495
|
let [, a] = _ref;
|
|
1384
1496
|
let [, b] = _ref2;
|
|
1385
1497
|
return b - a;
|
|
1386
1498
|
}); // Sort descending by min value
|
|
1387
|
-
// console.log('[ResponsiveProvider] Sorted breakpoints:', sortedBreakpoints);
|
|
1388
1499
|
for (const [name, minWidth] of sortedBreakpoints) {
|
|
1389
1500
|
if (width >= minWidth) {
|
|
1390
|
-
// console.log(
|
|
1391
|
-
// '[ResponsiveProvider] ✓ Match found:',
|
|
1392
|
-
// name,
|
|
1393
|
-
// 'for width',
|
|
1394
|
-
// width,
|
|
1395
|
-
// '>= minWidth',
|
|
1396
|
-
// minWidth
|
|
1397
|
-
// );
|
|
1398
1501
|
return name;
|
|
1399
1502
|
}
|
|
1400
1503
|
}
|
|
1401
1504
|
const fallback = sortedBreakpoints[sortedBreakpoints.length - 1]?.[0] || 'xs';
|
|
1402
|
-
// console.log('[ResponsiveProvider] No match, using fallback:', fallback);
|
|
1403
1505
|
return fallback;
|
|
1404
1506
|
};
|
|
1405
|
-
//
|
|
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)
|
|
1406
1545
|
const ResponsiveContext = /*#__PURE__*/React.createContext({
|
|
1407
1546
|
breakpoints: defaultBreakpointsConfig,
|
|
1408
1547
|
devices: defaultDeviceConfig,
|
|
@@ -1413,56 +1552,63 @@ const ResponsiveContext = /*#__PURE__*/React.createContext({
|
|
|
1413
1552
|
currentDevice: 'mobile',
|
|
1414
1553
|
orientation: 'portrait'
|
|
1415
1554
|
});
|
|
1416
|
-
|
|
1555
|
+
/**
|
|
1556
|
+
* Legacy hook for backward compatibility.
|
|
1557
|
+
* Prefer useBreakpointContext for better performance.
|
|
1558
|
+
* @deprecated Use useBreakpointContext instead for better performance
|
|
1559
|
+
*/
|
|
1417
1560
|
const useResponsiveContext = () => React.useContext(ResponsiveContext);
|
|
1418
1561
|
const ResponsiveProvider = _ref3 => {
|
|
1419
1562
|
let {
|
|
1420
1563
|
breakpoints = defaultBreakpointsConfig,
|
|
1421
1564
|
devices = defaultDeviceConfig,
|
|
1422
|
-
children
|
|
1565
|
+
children,
|
|
1566
|
+
targetWindow
|
|
1423
1567
|
} = _ref3;
|
|
1568
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
1569
|
+
// Track current breakpoint - only updates when crossing thresholds
|
|
1424
1570
|
const [screen, setScreen] = React.useState(() => {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
return getBreakpointFromWidth(window.innerWidth, breakpoints);
|
|
1571
|
+
if (win) {
|
|
1572
|
+
return getBreakpointFromWidth(win.innerWidth, breakpoints);
|
|
1428
1573
|
}
|
|
1429
1574
|
return 'xs';
|
|
1430
1575
|
});
|
|
1576
|
+
// Track orientation - rarely changes
|
|
1431
1577
|
const [orientation, setOrientation] = React.useState('portrait');
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1578
|
+
// Track window dimensions - changes often
|
|
1579
|
+
const [dimensions, setDimensions] = React.useState({
|
|
1580
|
+
width: win?.innerWidth || 0,
|
|
1581
|
+
height: win?.innerHeight || 0
|
|
1435
1582
|
});
|
|
1583
|
+
// Use ref to track previous breakpoint to avoid unnecessary state updates
|
|
1584
|
+
const prevBreakpointRef = React.useRef(screen);
|
|
1436
1585
|
const mediaQueries = React.useMemo(() => getMediaQueries(breakpoints), [breakpoints]);
|
|
1437
1586
|
React.useEffect(() => {
|
|
1438
|
-
|
|
1587
|
+
if (!win) return;
|
|
1439
1588
|
// Set initial screen size immediately based on window width
|
|
1440
|
-
const initialScreen = getBreakpointFromWidth(
|
|
1441
|
-
// console.log(
|
|
1442
|
-
// '[ResponsiveProvider] Setting initial screen to:',
|
|
1443
|
-
// initialScreen
|
|
1444
|
-
// );
|
|
1589
|
+
const initialScreen = getBreakpointFromWidth(win.innerWidth, breakpoints);
|
|
1445
1590
|
setScreen(initialScreen);
|
|
1591
|
+
prevBreakpointRef.current = initialScreen;
|
|
1446
1592
|
const handleResize = () => {
|
|
1447
|
-
const newWidth =
|
|
1448
|
-
const newHeight =
|
|
1449
|
-
//
|
|
1450
|
-
|
|
1451
|
-
// height: newHeight,
|
|
1452
|
-
// });
|
|
1453
|
-
setSize({
|
|
1593
|
+
const newWidth = win.innerWidth;
|
|
1594
|
+
const newHeight = win.innerHeight;
|
|
1595
|
+
// Always update dimensions (WindowDimensionsContext will re-render)
|
|
1596
|
+
setDimensions({
|
|
1454
1597
|
width: newWidth,
|
|
1455
1598
|
height: newHeight
|
|
1456
1599
|
});
|
|
1457
|
-
//
|
|
1600
|
+
// Only update breakpoint if it actually changed
|
|
1601
|
+
// This prevents BreakpointContext from causing unnecessary re-renders
|
|
1458
1602
|
const newScreen = getBreakpointFromWidth(newWidth, breakpoints);
|
|
1459
|
-
|
|
1460
|
-
|
|
1603
|
+
if (newScreen !== prevBreakpointRef.current) {
|
|
1604
|
+
prevBreakpointRef.current = newScreen;
|
|
1605
|
+
setScreen(newScreen);
|
|
1606
|
+
}
|
|
1461
1607
|
};
|
|
1462
1608
|
const debouncedResize = debounce(handleResize, 100);
|
|
1463
|
-
|
|
1609
|
+
win.addEventListener('resize', debouncedResize);
|
|
1464
1610
|
// Set up orientation listener
|
|
1465
|
-
const orientationMql =
|
|
1611
|
+
const orientationMql = win.matchMedia('(orientation: landscape)');
|
|
1466
1612
|
const onOrientationChange = () => setOrientation(orientationMql.matches ? 'landscape' : 'portrait');
|
|
1467
1613
|
if (orientationMql.addEventListener) {
|
|
1468
1614
|
orientationMql.addEventListener('change', onOrientationChange);
|
|
@@ -1471,30 +1617,46 @@ const ResponsiveProvider = _ref3 => {
|
|
|
1471
1617
|
}
|
|
1472
1618
|
onOrientationChange();
|
|
1473
1619
|
return () => {
|
|
1474
|
-
|
|
1620
|
+
win.removeEventListener('resize', debouncedResize);
|
|
1475
1621
|
if (orientationMql.removeEventListener) {
|
|
1476
1622
|
orientationMql.removeEventListener('change', onOrientationChange);
|
|
1477
1623
|
} else {
|
|
1478
1624
|
orientationMql.removeListener(onOrientationChange);
|
|
1479
1625
|
}
|
|
1480
1626
|
};
|
|
1481
|
-
}, [breakpoints]);
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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)));
|
|
1498
1660
|
};
|
|
1499
1661
|
|
|
1500
1662
|
const Shadows = {
|
|
@@ -2788,6 +2950,50 @@ const AnalyticsProvider = _ref => {
|
|
|
2788
2950
|
}, children);
|
|
2789
2951
|
};
|
|
2790
2952
|
|
|
2953
|
+
/**
|
|
2954
|
+
* Computes a stable hash of style-relevant props.
|
|
2955
|
+
* This is used to determine if style extraction needs to be re-run.
|
|
2956
|
+
*/
|
|
2957
|
+
function hashStyleProps(props) {
|
|
2958
|
+
// Build a deterministic string representation of style-relevant props
|
|
2959
|
+
const parts = [];
|
|
2960
|
+
const sortedKeys = Object.keys(props).sort();
|
|
2961
|
+
for (const key of sortedKeys) {
|
|
2962
|
+
// Skip non-style props that don't affect CSS generation
|
|
2963
|
+
if (key === 'children' || key === 'ref' || key === 'key') continue;
|
|
2964
|
+
// Include style-relevant props
|
|
2965
|
+
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') {
|
|
2966
|
+
const value = props[key];
|
|
2967
|
+
if (value !== undefined) {
|
|
2968
|
+
// Use JSON.stringify for consistent serialization
|
|
2969
|
+
parts.push(`${key}:${JSON.stringify(value)}`);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
return hash(parts.join('|'));
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2976
|
+
* Custom hook that memoizes style extraction based on a stable hash of props.
|
|
2977
|
+
* Only recalculates when the hash of style-relevant props changes.
|
|
2978
|
+
*/
|
|
2979
|
+
function useStableStyleMemo(propsToProcess, getColor, mediaQueries, devices, manager, theme) {
|
|
2980
|
+
const cacheRef = React.useRef(null);
|
|
2981
|
+
// Compute hash of current props
|
|
2982
|
+
const currentHash = React.useMemo(() => {
|
|
2983
|
+
// Include theme in hash to bust cache on theme changes
|
|
2984
|
+
const themeHash = theme ? JSON.stringify(theme) : '';
|
|
2985
|
+
return hashStyleProps(propsToProcess) + '|' + hash(themeHash);
|
|
2986
|
+
}, [propsToProcess, theme]);
|
|
2987
|
+
// Only recompute classes if hash changed
|
|
2988
|
+
if (!cacheRef.current || cacheRef.current.hash !== currentHash) {
|
|
2989
|
+
const classes = extractUtilityClasses(propsToProcess, getColor, mediaQueries, devices, manager);
|
|
2990
|
+
cacheRef.current = {
|
|
2991
|
+
hash: currentHash,
|
|
2992
|
+
classes
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
return cacheRef.current.classes;
|
|
2996
|
+
}
|
|
2791
2997
|
const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((_ref, ref) => {
|
|
2792
2998
|
let {
|
|
2793
2999
|
as = 'div',
|
|
@@ -2833,15 +3039,11 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
2833
3039
|
const {
|
|
2834
3040
|
mediaQueries,
|
|
2835
3041
|
devices
|
|
2836
|
-
} =
|
|
3042
|
+
} = useBreakpointContext();
|
|
2837
3043
|
const {
|
|
2838
3044
|
manager
|
|
2839
3045
|
} = useStyleRegistry();
|
|
2840
3046
|
const [isVisible, setIsVisible] = React.useState(false);
|
|
2841
|
-
console.log({
|
|
2842
|
-
mediaQueries,
|
|
2843
|
-
devices
|
|
2844
|
-
});
|
|
2845
3047
|
React.useEffect(() => {
|
|
2846
3048
|
if (!animateIn) {
|
|
2847
3049
|
setIsVisible(true);
|
|
@@ -2882,15 +3084,16 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
2882
3084
|
}
|
|
2883
3085
|
};
|
|
2884
3086
|
}, [animateOut, manager]);
|
|
2885
|
-
|
|
2886
|
-
|
|
3087
|
+
// Prepare props for processing (apply view/scroll timeline if needed)
|
|
3088
|
+
const propsToProcess = React.useMemo(() => {
|
|
3089
|
+
const processed = {
|
|
2887
3090
|
...rest,
|
|
2888
3091
|
blend
|
|
2889
3092
|
};
|
|
2890
3093
|
// Apply view() timeline ONLY if animateOn='View' (not Both or Mount)
|
|
2891
|
-
if (animateOn === 'View' &&
|
|
2892
|
-
const animations = Array.isArray(
|
|
2893
|
-
|
|
3094
|
+
if (animateOn === 'View' && processed.animate) {
|
|
3095
|
+
const animations = Array.isArray(processed.animate) ? processed.animate : [processed.animate];
|
|
3096
|
+
processed.animate = animations.map(anim => {
|
|
2894
3097
|
// Only add timeline if not already specified
|
|
2895
3098
|
if (!anim.timeline) {
|
|
2896
3099
|
return {
|
|
@@ -2904,9 +3107,9 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
2904
3107
|
});
|
|
2905
3108
|
}
|
|
2906
3109
|
// Apply scroll() timeline if animateOn='Scroll'
|
|
2907
|
-
if (animateOn === 'Scroll' &&
|
|
2908
|
-
const animations = Array.isArray(
|
|
2909
|
-
|
|
3110
|
+
if (animateOn === 'Scroll' && processed.animate) {
|
|
3111
|
+
const animations = Array.isArray(processed.animate) ? processed.animate : [processed.animate];
|
|
3112
|
+
processed.animate = animations.map(anim => {
|
|
2910
3113
|
// Only add timeline if not already specified
|
|
2911
3114
|
if (!anim.timeline) {
|
|
2912
3115
|
return {
|
|
@@ -2918,10 +3121,10 @@ const Element = /*#__PURE__*/React__default.memo(/*#__PURE__*/React.forwardRef((
|
|
|
2918
3121
|
return anim;
|
|
2919
3122
|
});
|
|
2920
3123
|
}
|
|
2921
|
-
return
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
3124
|
+
return processed;
|
|
3125
|
+
}, [rest, blend, animateOn]);
|
|
3126
|
+
// Use hash-based memoization for style extraction
|
|
3127
|
+
const utilityClasses = useStableStyleMemo(propsToProcess, getColor, mediaQueries, devices, manager, theme);
|
|
2925
3128
|
const newProps = {
|
|
2926
3129
|
ref: setRef
|
|
2927
3130
|
};
|
|
@@ -4833,20 +5036,23 @@ const WindowSizeContext = /*#__PURE__*/React.createContext({
|
|
|
4833
5036
|
});
|
|
4834
5037
|
const WindowSizeProvider = _ref => {
|
|
4835
5038
|
let {
|
|
4836
|
-
children
|
|
5039
|
+
children,
|
|
5040
|
+
targetWindow
|
|
4837
5041
|
} = _ref;
|
|
5042
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
4838
5043
|
const [size, setSize] = React.useState({
|
|
4839
|
-
width:
|
|
4840
|
-
height:
|
|
5044
|
+
width: win?.innerWidth || 0,
|
|
5045
|
+
height: win?.innerHeight || 0
|
|
4841
5046
|
});
|
|
4842
5047
|
React.useEffect(() => {
|
|
5048
|
+
if (!win) return;
|
|
4843
5049
|
const handleResize = () => setSize({
|
|
4844
|
-
width:
|
|
4845
|
-
height:
|
|
5050
|
+
width: win.innerWidth,
|
|
5051
|
+
height: win.innerHeight
|
|
4846
5052
|
});
|
|
4847
|
-
|
|
4848
|
-
return () =>
|
|
4849
|
-
}, []);
|
|
5053
|
+
win.addEventListener('resize', handleResize);
|
|
5054
|
+
return () => win.removeEventListener('resize', handleResize);
|
|
5055
|
+
}, [win]);
|
|
4850
5056
|
return /*#__PURE__*/React__default.createElement(WindowSizeContext.Provider, {
|
|
4851
5057
|
value: size
|
|
4852
5058
|
}, children);
|
|
@@ -4878,10 +5084,16 @@ function useActive() {
|
|
|
4878
5084
|
return [ref, active];
|
|
4879
5085
|
}
|
|
4880
5086
|
|
|
4881
|
-
function useClickOutside() {
|
|
5087
|
+
function useClickOutside(options) {
|
|
4882
5088
|
const [clickedOutside, setClickedOutside] = React.useState(false);
|
|
4883
5089
|
const ref = React.useRef(null);
|
|
5090
|
+
const {
|
|
5091
|
+
targetWindow
|
|
5092
|
+
} = options || {};
|
|
4884
5093
|
React.useEffect(() => {
|
|
5094
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
5095
|
+
if (!win) return;
|
|
5096
|
+
const doc = win.document;
|
|
4885
5097
|
const handleClick = e => {
|
|
4886
5098
|
if (ref.current && !ref.current.contains(e.target)) {
|
|
4887
5099
|
setClickedOutside(true);
|
|
@@ -4889,11 +5101,11 @@ function useClickOutside() {
|
|
|
4889
5101
|
setClickedOutside(false);
|
|
4890
5102
|
}
|
|
4891
5103
|
};
|
|
4892
|
-
|
|
5104
|
+
doc.addEventListener('mousedown', handleClick);
|
|
4893
5105
|
return () => {
|
|
4894
|
-
|
|
5106
|
+
doc.removeEventListener('mousedown', handleClick);
|
|
4895
5107
|
};
|
|
4896
|
-
}, []);
|
|
5108
|
+
}, [targetWindow]);
|
|
4897
5109
|
return [ref, clickedOutside];
|
|
4898
5110
|
}
|
|
4899
5111
|
|
|
@@ -4910,7 +5122,8 @@ function useElementPosition(options) {
|
|
|
4910
5122
|
throttleMs = 500,
|
|
4911
5123
|
trackOnHover = true,
|
|
4912
5124
|
trackOnScroll = false,
|
|
4913
|
-
trackOnResize = false
|
|
5125
|
+
trackOnResize = false,
|
|
5126
|
+
targetWindow
|
|
4914
5127
|
} = options;
|
|
4915
5128
|
const elementRef = React.useRef(null);
|
|
4916
5129
|
const [relation, setRelation] = React.useState(null);
|
|
@@ -4921,9 +5134,10 @@ function useElementPosition(options) {
|
|
|
4921
5134
|
setRelation(currentRelation => currentRelation === null ? null : null);
|
|
4922
5135
|
return;
|
|
4923
5136
|
}
|
|
5137
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
4924
5138
|
const rect = element.getBoundingClientRect();
|
|
4925
|
-
const viewportHeight =
|
|
4926
|
-
const viewportWidth =
|
|
5139
|
+
const viewportHeight = win.innerHeight;
|
|
5140
|
+
const viewportWidth = win.innerWidth;
|
|
4927
5141
|
// 1. Determine element's general position in viewport
|
|
4928
5142
|
const elementCenterY = rect.top + rect.height / 2;
|
|
4929
5143
|
const elementCenterX = rect.left + rect.width / 2;
|
|
@@ -4952,7 +5166,7 @@ function useElementPosition(options) {
|
|
|
4952
5166
|
}
|
|
4953
5167
|
return newRelation;
|
|
4954
5168
|
});
|
|
4955
|
-
}, []); // This callback is stable
|
|
5169
|
+
}, [targetWindow]); // This callback is stable
|
|
4956
5170
|
const throttledUpdate = React.useCallback(() => {
|
|
4957
5171
|
if (throttleTimerRef.current) {
|
|
4958
5172
|
clearTimeout(throttleTimerRef.current);
|
|
@@ -4970,6 +5184,7 @@ function useElementPosition(options) {
|
|
|
4970
5184
|
}
|
|
4971
5185
|
const element = elementRef.current;
|
|
4972
5186
|
if (!element) return;
|
|
5187
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
4973
5188
|
const handler = throttledUpdate;
|
|
4974
5189
|
const immediateHandler = calculateRelation;
|
|
4975
5190
|
// Add event listeners based on configuration
|
|
@@ -4985,18 +5200,18 @@ function useElementPosition(options) {
|
|
|
4985
5200
|
}
|
|
4986
5201
|
// Scroll events - throttled
|
|
4987
5202
|
if (trackOnScroll) {
|
|
4988
|
-
|
|
5203
|
+
win.addEventListener('scroll', handler, {
|
|
4989
5204
|
passive: true
|
|
4990
5205
|
});
|
|
4991
5206
|
cleanupFunctions.push(() => {
|
|
4992
|
-
|
|
5207
|
+
win.removeEventListener('scroll', handler);
|
|
4993
5208
|
});
|
|
4994
5209
|
}
|
|
4995
5210
|
// Resize events - throttled
|
|
4996
5211
|
if (trackOnResize) {
|
|
4997
|
-
|
|
5212
|
+
win.addEventListener('resize', handler);
|
|
4998
5213
|
cleanupFunctions.push(() => {
|
|
4999
|
-
|
|
5214
|
+
win.removeEventListener('resize', handler);
|
|
5000
5215
|
});
|
|
5001
5216
|
}
|
|
5002
5217
|
return () => {
|
|
@@ -5005,7 +5220,7 @@ function useElementPosition(options) {
|
|
|
5005
5220
|
}
|
|
5006
5221
|
cleanupFunctions.forEach(cleanup => cleanup());
|
|
5007
5222
|
};
|
|
5008
|
-
}, [trackChanges, trackOnHover, trackOnScroll, trackOnResize, throttledUpdate, calculateRelation]);
|
|
5223
|
+
}, [trackChanges, trackOnHover, trackOnScroll, trackOnResize, throttledUpdate, calculateRelation, targetWindow]);
|
|
5009
5224
|
const manualUpdateRelation = React.useCallback(() => {
|
|
5010
5225
|
calculateRelation();
|
|
5011
5226
|
}, [calculateRelation]);
|
|
@@ -5080,21 +5295,84 @@ const useMount = callback => {
|
|
|
5080
5295
|
function useOnScreen(options) {
|
|
5081
5296
|
const ref = React.useRef(null);
|
|
5082
5297
|
const [isOnScreen, setOnScreen] = React.useState(false);
|
|
5298
|
+
const {
|
|
5299
|
+
targetWindow,
|
|
5300
|
+
...observerOptions
|
|
5301
|
+
} = options || {};
|
|
5083
5302
|
React.useEffect(() => {
|
|
5084
5303
|
const node = ref.current;
|
|
5085
5304
|
if (!node) return;
|
|
5305
|
+
// If targetWindow is provided and root is not explicitly set,
|
|
5306
|
+
// use the target window's document as the root
|
|
5307
|
+
const win = targetWindow || node.ownerDocument?.defaultView || window;
|
|
5308
|
+
const effectiveRoot = observerOptions.root !== undefined ? observerOptions.root : targetWindow ? win.document.documentElement : undefined;
|
|
5086
5309
|
const observer = new IntersectionObserver(_ref => {
|
|
5087
5310
|
let [entry] = _ref;
|
|
5088
5311
|
setOnScreen(entry.isIntersecting);
|
|
5089
|
-
},
|
|
5312
|
+
}, {
|
|
5313
|
+
...observerOptions,
|
|
5314
|
+
root: effectiveRoot
|
|
5315
|
+
});
|
|
5090
5316
|
observer.observe(node);
|
|
5091
5317
|
return () => {
|
|
5092
5318
|
observer.disconnect();
|
|
5093
5319
|
};
|
|
5094
|
-
}, [options]);
|
|
5320
|
+
}, [targetWindow, options]);
|
|
5095
5321
|
return [ref, isOnScreen];
|
|
5096
5322
|
}
|
|
5097
5323
|
|
|
5324
|
+
/**
|
|
5325
|
+
* Optimized hook for components that only need breakpoint information.
|
|
5326
|
+
* This hook will NOT cause re-renders on every window resize,
|
|
5327
|
+
* only when the breakpoint actually changes.
|
|
5328
|
+
*
|
|
5329
|
+
* Use this hook instead of useResponsive for better performance
|
|
5330
|
+
* when you only need to check breakpoints or device type.
|
|
5331
|
+
*
|
|
5332
|
+
* @example
|
|
5333
|
+
* const { screen, on, is, orientation } = useBreakpoint();
|
|
5334
|
+
* if (on('mobile')) { ... }
|
|
5335
|
+
* if (is('xs')) { ... }
|
|
5336
|
+
*/
|
|
5337
|
+
const useBreakpoint = () => {
|
|
5338
|
+
const context = useBreakpointContext();
|
|
5339
|
+
const {
|
|
5340
|
+
currentBreakpoint: screen,
|
|
5341
|
+
orientation,
|
|
5342
|
+
devices
|
|
5343
|
+
} = context;
|
|
5344
|
+
// Helper to check if current screen matches a breakpoint or device
|
|
5345
|
+
const on = s => devices[s] ? devices[s].includes(screen) : s === screen;
|
|
5346
|
+
return {
|
|
5347
|
+
...context,
|
|
5348
|
+
screen,
|
|
5349
|
+
orientation,
|
|
5350
|
+
on,
|
|
5351
|
+
is: on
|
|
5352
|
+
};
|
|
5353
|
+
};
|
|
5354
|
+
/**
|
|
5355
|
+
* Hook for components that need exact window dimensions.
|
|
5356
|
+
* This hook WILL cause re-renders on every window resize.
|
|
5357
|
+
* Use sparingly - prefer useBreakpoint when possible.
|
|
5358
|
+
*
|
|
5359
|
+
* @example
|
|
5360
|
+
* const { width, height } = useWindowDimensions();
|
|
5361
|
+
*/
|
|
5362
|
+
const useWindowDimensions = () => {
|
|
5363
|
+
return useWindowDimensionsContext();
|
|
5364
|
+
};
|
|
5365
|
+
/**
|
|
5366
|
+
* Combined hook that provides both breakpoint info and window dimensions.
|
|
5367
|
+
* This hook WILL cause re-renders on every window resize.
|
|
5368
|
+
*
|
|
5369
|
+
* For better performance, use:
|
|
5370
|
+
* - useBreakpoint() when you only need breakpoint/device info
|
|
5371
|
+
* - useWindowDimensions() when you only need exact dimensions
|
|
5372
|
+
*
|
|
5373
|
+
* @example
|
|
5374
|
+
* const { screen, on, currentWidth, currentHeight } = useResponsive();
|
|
5375
|
+
*/
|
|
5098
5376
|
const useResponsive = () => {
|
|
5099
5377
|
const context = useResponsiveContext();
|
|
5100
5378
|
const {
|
|
@@ -5110,7 +5388,6 @@ const useResponsive = () => {
|
|
|
5110
5388
|
on,
|
|
5111
5389
|
is: on
|
|
5112
5390
|
};
|
|
5113
|
-
// console.log('[useResponsive] Hook called, returning:', { screen, orientation, 'on(mobile)': on('mobile') });
|
|
5114
5391
|
return result;
|
|
5115
5392
|
};
|
|
5116
5393
|
|
|
@@ -5280,27 +5557,28 @@ const useScrollAnimation = function (ref, options) {
|
|
|
5280
5557
|
};
|
|
5281
5558
|
};
|
|
5282
5559
|
// Enhanced useSmoothScroll with error handling
|
|
5283
|
-
const useSmoothScroll =
|
|
5560
|
+
const useSmoothScroll = targetWindow => {
|
|
5284
5561
|
return React.useCallback(function (element, offset) {
|
|
5285
5562
|
if (offset === void 0) {
|
|
5286
5563
|
offset = 0;
|
|
5287
5564
|
}
|
|
5288
5565
|
if (!element) return;
|
|
5289
5566
|
try {
|
|
5290
|
-
const
|
|
5291
|
-
|
|
5292
|
-
|
|
5567
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
5568
|
+
const top = element.getBoundingClientRect().top + (win.scrollY || win.pageYOffset) - offset;
|
|
5569
|
+
if ('scrollBehavior' in win.document.documentElement.style) {
|
|
5570
|
+
win.scrollTo({
|
|
5293
5571
|
top,
|
|
5294
5572
|
behavior: 'smooth'
|
|
5295
5573
|
});
|
|
5296
5574
|
} else {
|
|
5297
5575
|
// Fallback for browsers that don't support smooth scrolling
|
|
5298
|
-
|
|
5576
|
+
win.scrollTo(0, top);
|
|
5299
5577
|
}
|
|
5300
5578
|
} catch (error) {
|
|
5301
5579
|
console.error('Error during smooth scroll:', error);
|
|
5302
5580
|
}
|
|
5303
|
-
}, []);
|
|
5581
|
+
}, [targetWindow]);
|
|
5304
5582
|
};
|
|
5305
5583
|
// Enhanced useInfiniteScroll with debouncing
|
|
5306
5584
|
const useInfiniteScroll = function (callback, options) {
|
|
@@ -5344,7 +5622,7 @@ const useInfiniteScroll = function (callback, options) {
|
|
|
5344
5622
|
sentinelRef: setSentinel
|
|
5345
5623
|
};
|
|
5346
5624
|
};
|
|
5347
|
-
const useScrollDirection = function (threshold) {
|
|
5625
|
+
const useScrollDirection = function (threshold, targetWindow) {
|
|
5348
5626
|
if (threshold === void 0) {
|
|
5349
5627
|
threshold = 5;
|
|
5350
5628
|
}
|
|
@@ -5353,12 +5631,15 @@ const useScrollDirection = function (threshold) {
|
|
|
5353
5631
|
const lastDirection = React.useRef('up');
|
|
5354
5632
|
const animationFrame = React.useRef();
|
|
5355
5633
|
const ticking = React.useRef(false);
|
|
5356
|
-
const updateDirection = () => {
|
|
5357
|
-
const
|
|
5634
|
+
const updateDirection = React.useCallback(() => {
|
|
5635
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
5636
|
+
if (!win) return;
|
|
5637
|
+
const doc = win.document.documentElement;
|
|
5638
|
+
const scrollY = win.scrollY || doc.scrollTop;
|
|
5358
5639
|
const direction = scrollY > lastScrollY.current ? 'down' : 'up';
|
|
5359
5640
|
const scrollDelta = Math.abs(scrollY - lastScrollY.current);
|
|
5360
5641
|
// Vérifier si on est au bas de la page
|
|
5361
|
-
const isAtBottom =
|
|
5642
|
+
const isAtBottom = win.innerHeight + scrollY >= doc.scrollHeight - 1;
|
|
5362
5643
|
// Logique principale
|
|
5363
5644
|
if (scrollDelta > threshold || direction === 'down' && isAtBottom) {
|
|
5364
5645
|
if (direction !== lastDirection.current) {
|
|
@@ -5369,8 +5650,10 @@ const useScrollDirection = function (threshold) {
|
|
|
5369
5650
|
// Mise à jour de la position avec un minimum de 0
|
|
5370
5651
|
lastScrollY.current = Math.max(scrollY, 0);
|
|
5371
5652
|
ticking.current = false;
|
|
5372
|
-
};
|
|
5653
|
+
}, [threshold, targetWindow]);
|
|
5373
5654
|
React.useEffect(() => {
|
|
5655
|
+
const win = targetWindow || (typeof window !== 'undefined' ? window : null);
|
|
5656
|
+
if (!win) return;
|
|
5374
5657
|
const handleScroll = () => {
|
|
5375
5658
|
if (!ticking.current) {
|
|
5376
5659
|
animationFrame.current = requestAnimationFrame(() => {
|
|
@@ -5380,16 +5663,16 @@ const useScrollDirection = function (threshold) {
|
|
|
5380
5663
|
ticking.current = true;
|
|
5381
5664
|
}
|
|
5382
5665
|
};
|
|
5383
|
-
|
|
5666
|
+
win.addEventListener('scroll', handleScroll, {
|
|
5384
5667
|
passive: true
|
|
5385
5668
|
});
|
|
5386
5669
|
return () => {
|
|
5387
|
-
|
|
5670
|
+
win.removeEventListener('scroll', handleScroll);
|
|
5388
5671
|
if (animationFrame.current) {
|
|
5389
5672
|
cancelAnimationFrame(animationFrame.current);
|
|
5390
5673
|
}
|
|
5391
5674
|
};
|
|
5392
|
-
}, [
|
|
5675
|
+
}, [updateDirection, targetWindow]);
|
|
5393
5676
|
return scrollDirection;
|
|
5394
5677
|
};
|
|
5395
5678
|
|
|
@@ -5398,6 +5681,7 @@ const useWindowSize = () => React.useContext(WindowSizeContext);
|
|
|
5398
5681
|
function useInView(options) {
|
|
5399
5682
|
const {
|
|
5400
5683
|
triggerOnce = false,
|
|
5684
|
+
targetWindow,
|
|
5401
5685
|
...observerOptions
|
|
5402
5686
|
} = options || {};
|
|
5403
5687
|
const ref = React.useRef(null);
|
|
@@ -5405,6 +5689,10 @@ function useInView(options) {
|
|
|
5405
5689
|
React.useEffect(() => {
|
|
5406
5690
|
const element = ref.current;
|
|
5407
5691
|
if (!element) return;
|
|
5692
|
+
// If targetWindow is provided and root is not explicitly set,
|
|
5693
|
+
// use the target window's document as the root
|
|
5694
|
+
const win = targetWindow || element.ownerDocument?.defaultView || window;
|
|
5695
|
+
const effectiveRoot = observerOptions.root !== undefined ? observerOptions.root : targetWindow ? win.document.documentElement : undefined;
|
|
5408
5696
|
const observer = new IntersectionObserver(_ref => {
|
|
5409
5697
|
let [entry] = _ref;
|
|
5410
5698
|
if (entry.isIntersecting) {
|
|
@@ -5417,18 +5705,121 @@ function useInView(options) {
|
|
|
5417
5705
|
// Only update to false if not using triggerOnce
|
|
5418
5706
|
setInView(false);
|
|
5419
5707
|
}
|
|
5420
|
-
},
|
|
5708
|
+
}, {
|
|
5709
|
+
...observerOptions,
|
|
5710
|
+
root: effectiveRoot
|
|
5711
|
+
});
|
|
5421
5712
|
observer.observe(element);
|
|
5422
5713
|
return () => {
|
|
5423
5714
|
observer.disconnect();
|
|
5424
5715
|
};
|
|
5425
|
-
}, [triggerOnce, ...Object.values(observerOptions || {})]);
|
|
5716
|
+
}, [triggerOnce, targetWindow, ...Object.values(observerOptions || {})]);
|
|
5426
5717
|
return {
|
|
5427
5718
|
ref,
|
|
5428
5719
|
inView
|
|
5429
5720
|
};
|
|
5430
5721
|
}
|
|
5431
5722
|
|
|
5723
|
+
/**
|
|
5724
|
+
* Hook to register an iframe's document with the style manager.
|
|
5725
|
+
* This ensures that all CSS utility classes are automatically injected into the iframe.
|
|
5726
|
+
*
|
|
5727
|
+
* @param iframeRef - Reference to the iframe element
|
|
5728
|
+
*
|
|
5729
|
+
* @example
|
|
5730
|
+
* ```tsx
|
|
5731
|
+
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
5732
|
+
* useIframeStyles(iframeRef);
|
|
5733
|
+
*
|
|
5734
|
+
* return <iframe ref={iframeRef} src="/content" />;
|
|
5735
|
+
* ```
|
|
5736
|
+
*/
|
|
5737
|
+
function useIframeStyles(iframeRef) {
|
|
5738
|
+
const {
|
|
5739
|
+
manager
|
|
5740
|
+
} = useStyleRegistry();
|
|
5741
|
+
const registeredDocRef = React.useRef(null);
|
|
5742
|
+
React.useEffect(() => {
|
|
5743
|
+
const iframe = iframeRef.current;
|
|
5744
|
+
if (!iframe) return;
|
|
5745
|
+
const registerDocument = () => {
|
|
5746
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
5747
|
+
if (!iframeDoc) return;
|
|
5748
|
+
// Only register if not already registered
|
|
5749
|
+
if (registeredDocRef.current !== iframeDoc) {
|
|
5750
|
+
manager.addDocument(iframeDoc);
|
|
5751
|
+
registeredDocRef.current = iframeDoc;
|
|
5752
|
+
}
|
|
5753
|
+
};
|
|
5754
|
+
// Try to register immediately if document is ready
|
|
5755
|
+
if (iframe.contentDocument) {
|
|
5756
|
+
registerDocument();
|
|
5757
|
+
}
|
|
5758
|
+
// Also register on load event (for external sources)
|
|
5759
|
+
iframe.addEventListener('load', registerDocument);
|
|
5760
|
+
return () => {
|
|
5761
|
+
iframe.removeEventListener('load', registerDocument);
|
|
5762
|
+
// Clean up when unmounting
|
|
5763
|
+
if (registeredDocRef.current) {
|
|
5764
|
+
manager.removeDocument(registeredDocRef.current);
|
|
5765
|
+
registeredDocRef.current = null;
|
|
5766
|
+
}
|
|
5767
|
+
};
|
|
5768
|
+
}, [manager, iframeRef]);
|
|
5769
|
+
}
|
|
5770
|
+
/**
|
|
5771
|
+
* Hook to get an iframe's window and document, and automatically register styles.
|
|
5772
|
+
* This is a convenience hook that combines iframe access with style registration.
|
|
5773
|
+
*
|
|
5774
|
+
* @param iframeRef - Reference to the iframe element
|
|
5775
|
+
* @returns Object containing iframeWindow, iframeDocument, and isLoaded flag
|
|
5776
|
+
*
|
|
5777
|
+
* @example
|
|
5778
|
+
* ```tsx
|
|
5779
|
+
* const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
5780
|
+
* const { iframeWindow, iframeDocument, isLoaded } = useIframe(iframeRef);
|
|
5781
|
+
*
|
|
5782
|
+
* return (
|
|
5783
|
+
* <>
|
|
5784
|
+
* <iframe ref={iframeRef} src="/content" />
|
|
5785
|
+
* {isLoaded && <div>Iframe loaded!</div>}
|
|
5786
|
+
* </>
|
|
5787
|
+
* );
|
|
5788
|
+
* ```
|
|
5789
|
+
*/
|
|
5790
|
+
function useIframe(iframeRef) {
|
|
5791
|
+
const [iframeWindow, setIframeWindow] = React.useState(null);
|
|
5792
|
+
const [iframeDocument, setIframeDocument] = React.useState(null);
|
|
5793
|
+
const [isLoaded, setIsLoaded] = React.useState(false);
|
|
5794
|
+
// Register styles
|
|
5795
|
+
useIframeStyles(iframeRef);
|
|
5796
|
+
React.useEffect(() => {
|
|
5797
|
+
const iframe = iframeRef.current;
|
|
5798
|
+
if (!iframe) return;
|
|
5799
|
+
const updateState = () => {
|
|
5800
|
+
const win = iframe.contentWindow;
|
|
5801
|
+
const doc = iframe.contentDocument || win?.document;
|
|
5802
|
+
if (win && doc) {
|
|
5803
|
+
setIframeWindow(win);
|
|
5804
|
+
setIframeDocument(doc);
|
|
5805
|
+
setIsLoaded(true);
|
|
5806
|
+
}
|
|
5807
|
+
};
|
|
5808
|
+
// Try immediately
|
|
5809
|
+
updateState();
|
|
5810
|
+
// Listen for load event
|
|
5811
|
+
iframe.addEventListener('load', updateState);
|
|
5812
|
+
return () => {
|
|
5813
|
+
iframe.removeEventListener('load', updateState);
|
|
5814
|
+
};
|
|
5815
|
+
}, [iframeRef]);
|
|
5816
|
+
return {
|
|
5817
|
+
iframeWindow,
|
|
5818
|
+
iframeDocument,
|
|
5819
|
+
isLoaded
|
|
5820
|
+
};
|
|
5821
|
+
}
|
|
5822
|
+
|
|
5432
5823
|
/**
|
|
5433
5824
|
* View Animation Utilities
|
|
5434
5825
|
*
|
|
@@ -6003,6 +6394,7 @@ exports.AnalyticsContext = AnalyticsContext;
|
|
|
6003
6394
|
exports.AnalyticsProvider = AnalyticsProvider;
|
|
6004
6395
|
exports.Animation = Animation;
|
|
6005
6396
|
exports.AnimationUtils = AnimationUtils;
|
|
6397
|
+
exports.BreakpointContext = BreakpointContext;
|
|
6006
6398
|
exports.Button = Button;
|
|
6007
6399
|
exports.Center = Center;
|
|
6008
6400
|
exports.Div = Div;
|
|
@@ -6029,6 +6421,7 @@ exports.UtilityClassManager = UtilityClassManager;
|
|
|
6029
6421
|
exports.Vertical = Vertical;
|
|
6030
6422
|
exports.VerticalResponsive = VerticalResponsive;
|
|
6031
6423
|
exports.View = View;
|
|
6424
|
+
exports.WindowDimensionsContext = WindowDimensionsContext;
|
|
6032
6425
|
exports.WindowSizeContext = WindowSizeContext;
|
|
6033
6426
|
exports.WindowSizeProvider = WindowSizeProvider;
|
|
6034
6427
|
exports.animateOnView = animateOnView;
|
|
@@ -6068,10 +6461,14 @@ exports.slideRightOnView = slideRightOnView;
|
|
|
6068
6461
|
exports.slideUpOnView = slideUpOnView;
|
|
6069
6462
|
exports.useActive = useActive;
|
|
6070
6463
|
exports.useAnalytics = useAnalytics;
|
|
6464
|
+
exports.useBreakpoint = useBreakpoint;
|
|
6465
|
+
exports.useBreakpointContext = useBreakpointContext;
|
|
6071
6466
|
exports.useClickOutside = useClickOutside;
|
|
6072
6467
|
exports.useElementPosition = useElementPosition;
|
|
6073
6468
|
exports.useFocus = useFocus;
|
|
6074
6469
|
exports.useHover = useHover;
|
|
6470
|
+
exports.useIframe = useIframe;
|
|
6471
|
+
exports.useIframeStyles = useIframeStyles;
|
|
6075
6472
|
exports.useInView = useInView;
|
|
6076
6473
|
exports.useInfiniteScroll = useInfiniteScroll;
|
|
6077
6474
|
exports.useKeyPress = useKeyPress;
|
|
@@ -6086,6 +6483,8 @@ exports.useServerInsertedHTML = useServerInsertedHTML;
|
|
|
6086
6483
|
exports.useSmoothScroll = useSmoothScroll;
|
|
6087
6484
|
exports.useStyleRegistry = useStyleRegistry;
|
|
6088
6485
|
exports.useTheme = useTheme;
|
|
6486
|
+
exports.useWindowDimensions = useWindowDimensions;
|
|
6487
|
+
exports.useWindowDimensionsContext = useWindowDimensionsContext;
|
|
6089
6488
|
exports.useWindowSize = useWindowSize;
|
|
6090
6489
|
exports.utilityClassManager = utilityClassManager;
|
|
6091
6490
|
exports.viewAnimationPresets = viewAnimationPresets;
|