@udixio/ui-react 2.10.13 → 2.10.14
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/package.json +4 -1
- package/.eslintrc.mjs +0 -22
- package/.storybook/main.ts +0 -20
- package/.storybook/preview.ts +0 -1
- package/CHANGELOG.md +0 -1144
- package/postcss.config.mjs +0 -5
- package/src/index.css +0 -4
- package/src/index.ts +0 -1
- package/src/lib/components/AnchorPositioner.tsx +0 -185
- package/src/lib/components/Button.tsx +0 -208
- package/src/lib/components/Card.tsx +0 -47
- package/src/lib/components/Carousel.tsx +0 -437
- package/src/lib/components/CarouselItem.tsx +0 -61
- package/src/lib/components/Checkbox.tsx +0 -120
- package/src/lib/components/Chip.tsx +0 -341
- package/src/lib/components/Chips.tsx +0 -331
- package/src/lib/components/ContextMenu.tsx +0 -109
- package/src/lib/components/DatePicker.tsx +0 -432
- package/src/lib/components/Divider.tsx +0 -20
- package/src/lib/components/Fab.tsx +0 -127
- package/src/lib/components/FabMenu.tsx +0 -239
- package/src/lib/components/IconButton.tsx +0 -146
- package/src/lib/components/Menu.tsx +0 -88
- package/src/lib/components/MenuGroup.tsx +0 -34
- package/src/lib/components/MenuHeadline.tsx +0 -9
- package/src/lib/components/MenuItem.tsx +0 -215
- package/src/lib/components/NavigationRail.tsx +0 -186
- package/src/lib/components/NavigationRailItem.tsx +0 -227
- package/src/lib/components/ProgressIndicator.tsx +0 -214
- package/src/lib/components/SideSheet.tsx +0 -135
- package/src/lib/components/Slider.tsx +0 -374
- package/src/lib/components/Snackbar.tsx +0 -77
- package/src/lib/components/Switch.tsx +0 -107
- package/src/lib/components/Tab.tsx +0 -123
- package/src/lib/components/TabGroup.tsx +0 -66
- package/src/lib/components/TabGroupContext.tsx +0 -16
- package/src/lib/components/TabPanel.tsx +0 -27
- package/src/lib/components/TabPanels.tsx +0 -76
- package/src/lib/components/Tabs.tsx +0 -105
- package/src/lib/components/TextField.tsx +0 -586
- package/src/lib/components/Tooltip.tsx +0 -217
- package/src/lib/components/index.ts +0 -34
- package/src/lib/config/config.interface.ts +0 -9
- package/src/lib/config/define-config.ts +0 -16
- package/src/lib/config/index.ts +0 -2
- package/src/lib/effects/AnimateOnScroll.ts +0 -391
- package/src/lib/effects/State.tsx +0 -90
- package/src/lib/effects/SyncedFixedWrapper.tsx +0 -62
- package/src/lib/effects/ThemeProvider.tsx +0 -174
- package/src/lib/effects/block-scroll.effect.tsx +0 -313
- package/src/lib/effects/custom-scroll/custom-scroll.effect.tsx +0 -407
- package/src/lib/effects/custom-scroll/custom-scroll.interface.ts +0 -29
- package/src/lib/effects/custom-scroll/custom-scroll.style.ts +0 -32
- package/src/lib/effects/custom-scroll/index.ts +0 -3
- package/src/lib/effects/index.ts +0 -7
- package/src/lib/effects/ripple/RippleEffect.tsx +0 -116
- package/src/lib/effects/ripple/index.tsx +0 -1
- package/src/lib/effects/scrollDriven.ts +0 -239
- package/src/lib/effects/smooth-scroll.effect.tsx +0 -112
- package/src/lib/effects/theme.worker.ts +0 -97
- package/src/lib/hooks/index.ts +0 -10
- package/src/lib/hooks/useTooltipTrigger.ts +0 -270
- package/src/lib/icon/icon.tsx +0 -125
- package/src/lib/icon/index.ts +0 -1
- package/src/lib/index.ts +0 -8
- package/src/lib/interfaces/button.interface.ts +0 -65
- package/src/lib/interfaces/card.interface.ts +0 -11
- package/src/lib/interfaces/carousel-item.interface.ts +0 -12
- package/src/lib/interfaces/carousel.interface.ts +0 -41
- package/src/lib/interfaces/checkbox.interface.ts +0 -39
- package/src/lib/interfaces/chip.interface.ts +0 -97
- package/src/lib/interfaces/chips.interface.ts +0 -37
- package/src/lib/interfaces/date-picker.interface.ts +0 -79
- package/src/lib/interfaces/divider.interface.ts +0 -7
- package/src/lib/interfaces/fab-menu.interface.ts +0 -12
- package/src/lib/interfaces/fab.interface.ts +0 -27
- package/src/lib/interfaces/icon-button.interface.ts +0 -38
- package/src/lib/interfaces/index.ts +0 -26
- package/src/lib/interfaces/menu-group.interface.ts +0 -13
- package/src/lib/interfaces/menu-item.interface.ts +0 -29
- package/src/lib/interfaces/menu.interface.ts +0 -19
- package/src/lib/interfaces/navigation-rail-item.interface.ts +0 -39
- package/src/lib/interfaces/navigation-rail.interface.ts +0 -39
- package/src/lib/interfaces/progress-indicator.interface.ts +0 -41
- package/src/lib/interfaces/side-sheet.interface.tsx +0 -28
- package/src/lib/interfaces/slider.interface.ts +0 -27
- package/src/lib/interfaces/snackbar.interface.ts +0 -13
- package/src/lib/interfaces/switch.interface.ts +0 -14
- package/src/lib/interfaces/tab-group.interface.ts +0 -13
- package/src/lib/interfaces/tab-panels.interface.ts +0 -21
- package/src/lib/interfaces/tab.interface.ts +0 -31
- package/src/lib/interfaces/tabs.interface.ts +0 -22
- package/src/lib/interfaces/text-field.interface.ts +0 -61
- package/src/lib/interfaces/tooltip.interface.ts +0 -61
- package/src/lib/styles/button.style.ts +0 -136
- package/src/lib/styles/card.style.ts +0 -29
- package/src/lib/styles/carousel-item.style.ts +0 -24
- package/src/lib/styles/carousel.style.ts +0 -22
- package/src/lib/styles/checkbox.style.ts +0 -64
- package/src/lib/styles/chip.style.ts +0 -62
- package/src/lib/styles/chips.style.ts +0 -20
- package/src/lib/styles/date-picker.style.ts +0 -43
- package/src/lib/styles/divider.style.ts +0 -31
- package/src/lib/styles/fab-menu.style.ts +0 -29
- package/src/lib/styles/fab.style.ts +0 -49
- package/src/lib/styles/icon-button.style.ts +0 -168
- package/src/lib/styles/index.ts +0 -25
- package/src/lib/styles/menu-group.style.ts +0 -34
- package/src/lib/styles/menu-headline.style.ts +0 -20
- package/src/lib/styles/menu-item.style.ts +0 -45
- package/src/lib/styles/menu.style.ts +0 -32
- package/src/lib/styles/navigation-rail-item.style.ts +0 -56
- package/src/lib/styles/navigation-rail.style.ts +0 -36
- package/src/lib/styles/progress-indicator.style.ts +0 -72
- package/src/lib/styles/side-sheet.style.ts +0 -45
- package/src/lib/styles/slider.style.ts +0 -41
- package/src/lib/styles/snackbar.style.ts +0 -26
- package/src/lib/styles/switch.style.ts +0 -67
- package/src/lib/styles/tab-panels.style.ts +0 -35
- package/src/lib/styles/tab.style.ts +0 -78
- package/src/lib/styles/tabs.style.ts +0 -22
- package/src/lib/styles/text-field.style.ts +0 -115
- package/src/lib/styles/tooltip.style.ts +0 -48
- package/src/lib/utils/component-helper.ts +0 -134
- package/src/lib/utils/component.ts +0 -34
- package/src/lib/utils/index.ts +0 -7
- package/src/lib/utils/string.ts +0 -9
- package/src/lib/utils/styles/classnames.ts +0 -49
- package/src/lib/utils/styles/get-classname.ts +0 -96
- package/src/lib/utils/styles/index.ts +0 -4
- package/src/lib/utils/styles/use-classnames.ts +0 -25
- package/src/stories/action/button.stories.tsx +0 -86
- package/src/stories/action/fab.stories.tsx +0 -54
- package/src/stories/action/icon-button.stories.tsx +0 -134
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +0 -5
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +0 -15
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +0 -3
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +0 -12
- package/src/stories/assets/youtube.svg +0 -4
- package/src/stories/communication/ProgressIndicator.stories.tsx +0 -57
- package/src/stories/communication/SnackBar.stories.tsx +0 -32
- package/src/stories/communication/tool-tip.stories.tsx +0 -133
- package/src/stories/containment/card.stories.tsx +0 -42
- package/src/stories/containment/carousel.stories.tsx +0 -65
- package/src/stories/containment/divider.stories.tsx +0 -35
- package/src/stories/containment/slide-sheet.stories.tsx +0 -45
- package/src/stories/effect/smooth-scroll.stories.tsx +0 -54
- package/src/stories/navigation/navigation-rail/navigation-rail-item.stories.tsx +0 -65
- package/src/stories/navigation/navigation-rail/navigation-rail.stories.tsx +0 -122
- package/src/stories/navigation/tabs/tab.stories.tsx +0 -57
- package/src/stories/navigation/tabs/tabs.stories.tsx +0 -102
- package/src/stories/selection/slider.stories.tsx +0 -85
- package/src/stories/selection/switch.stories.tsx +0 -46
- package/src/stories/text-inputs/text-field.stories.tsx +0 -135
- package/src/tests/Button.spec.tsx +0 -67
- package/src/tests/useClassNames.spec.tsx +0 -82
- package/src/udixio.css +0 -120
- package/theme.config.ts +0 -7
- package/tsconfig.json +0 -16
- package/tsconfig.lib.json +0 -51
- package/tsconfig.spec.json +0 -37
- package/tsconfig.storybook.json +0 -38
- package/vite.config.ts +0 -96
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { RippleEffect } from './ripple';
|
|
2
|
-
import {
|
|
3
|
-
ClassNameComponent,
|
|
4
|
-
classNames,
|
|
5
|
-
createUseClassNames,
|
|
6
|
-
ReactProps,
|
|
7
|
-
} from '../utils';
|
|
8
|
-
import { useEffect, useRef, useState } from 'react';
|
|
9
|
-
|
|
10
|
-
export interface StateInterface {
|
|
11
|
-
type: 'div';
|
|
12
|
-
props: {
|
|
13
|
-
colorName: string;
|
|
14
|
-
stateClassName?:
|
|
15
|
-
| string
|
|
16
|
-
| 'state-ripple-group'
|
|
17
|
-
| 'state-group'
|
|
18
|
-
| 'state-layer';
|
|
19
|
-
className?: string;
|
|
20
|
-
style?: React.CSSProperties;
|
|
21
|
-
children?: React.ReactNode;
|
|
22
|
-
};
|
|
23
|
-
states: { isClient: boolean };
|
|
24
|
-
elements: ['stateLayer'];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const State = ({
|
|
28
|
-
style,
|
|
29
|
-
colorName,
|
|
30
|
-
stateClassName = 'state-ripple-group',
|
|
31
|
-
children,
|
|
32
|
-
className,
|
|
33
|
-
}: ReactProps<StateInterface>) => {
|
|
34
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
35
|
-
const groupStateRef = useRef<HTMLElement | null>(null);
|
|
36
|
-
|
|
37
|
-
const [isClient, setIsClient] = useState(false);
|
|
38
|
-
const styles = useStateStyle({
|
|
39
|
-
isClient,
|
|
40
|
-
stateClassName,
|
|
41
|
-
className,
|
|
42
|
-
colorName,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (ref.current && stateClassName !== 'state-layer') {
|
|
47
|
-
const groupName = !stateClassName.includes('[')
|
|
48
|
-
? 'group'
|
|
49
|
-
: 'group/' + stateClassName.split('[')[1].split(']')[0];
|
|
50
|
-
|
|
51
|
-
// On échappe le slash pour le sélecteur CSS
|
|
52
|
-
const safeGroupName = groupName.replace(/\//g, '\\/');
|
|
53
|
-
|
|
54
|
-
const furthestGroupState = ref.current.closest(
|
|
55
|
-
`.${safeGroupName}:not(.${safeGroupName} .${safeGroupName})`,
|
|
56
|
-
);
|
|
57
|
-
groupStateRef.current = furthestGroupState as HTMLElement | null;
|
|
58
|
-
}
|
|
59
|
-
setIsClient(true);
|
|
60
|
-
}, []);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div
|
|
64
|
-
ref={ref}
|
|
65
|
-
className={styles.stateLayer}
|
|
66
|
-
style={{
|
|
67
|
-
['--state-color' as any]: `var(--default-color, var(--color-${colorName}))`,
|
|
68
|
-
...style,
|
|
69
|
-
}}
|
|
70
|
-
>
|
|
71
|
-
{isClient && <RippleEffect triggerRef={groupStateRef} />}
|
|
72
|
-
{children}
|
|
73
|
-
</div>
|
|
74
|
-
);
|
|
75
|
-
};
|
|
76
|
-
// ... existing code ...
|
|
77
|
-
const cardConfig: ClassNameComponent<StateInterface> = ({
|
|
78
|
-
isClient,
|
|
79
|
-
stateClassName,
|
|
80
|
-
}) => ({
|
|
81
|
-
stateLayer: classNames([
|
|
82
|
-
'w-full top-0 left-0 h-full absolute pointer-events-none overflow-hidden',
|
|
83
|
-
stateClassName,
|
|
84
|
-
]),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
export const useStateStyle = createUseClassNames<StateInterface>(
|
|
88
|
-
'stateLayer',
|
|
89
|
-
cardConfig,
|
|
90
|
-
);
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ReactNode,
|
|
3
|
-
type RefObject,
|
|
4
|
-
useEffect,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
} from 'react';
|
|
8
|
-
import { createPortal } from 'react-dom';
|
|
9
|
-
|
|
10
|
-
type SyncedFixedWrapperProps = {
|
|
11
|
-
targetRef: RefObject<any>;
|
|
12
|
-
children: ReactNode;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const SyncedFixedWrapper = ({
|
|
16
|
-
targetRef,
|
|
17
|
-
children,
|
|
18
|
-
}: SyncedFixedWrapperProps) => {
|
|
19
|
-
const [style, setStyle] = useState<React.CSSProperties | null>(null);
|
|
20
|
-
const resizeObserver = useRef<ResizeObserver | null>(null);
|
|
21
|
-
|
|
22
|
-
const updatePosition = () => {
|
|
23
|
-
const target = targetRef.current;
|
|
24
|
-
if (!target) return;
|
|
25
|
-
|
|
26
|
-
const rect = target.getBoundingClientRect();
|
|
27
|
-
|
|
28
|
-
setStyle({
|
|
29
|
-
position: 'fixed',
|
|
30
|
-
top: rect.top,
|
|
31
|
-
left: rect.left,
|
|
32
|
-
width: rect.width,
|
|
33
|
-
height: rect.height,
|
|
34
|
-
pointerEvents: 'none', // si le wrapper ne doit pas capter les events
|
|
35
|
-
zIndex: 999, // personnalise si besoin
|
|
36
|
-
});
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
const target = targetRef.current;
|
|
41
|
-
if (!target) return;
|
|
42
|
-
|
|
43
|
-
updatePosition();
|
|
44
|
-
|
|
45
|
-
window.addEventListener('scroll', updatePosition, true);
|
|
46
|
-
window.addEventListener('resize', updatePosition);
|
|
47
|
-
|
|
48
|
-
// Observe resize of the target element
|
|
49
|
-
resizeObserver.current = new ResizeObserver(updatePosition);
|
|
50
|
-
resizeObserver.current.observe(target);
|
|
51
|
-
|
|
52
|
-
return () => {
|
|
53
|
-
window.removeEventListener('scroll', updatePosition, true);
|
|
54
|
-
window.removeEventListener('resize', updatePosition);
|
|
55
|
-
resizeObserver.current?.disconnect();
|
|
56
|
-
};
|
|
57
|
-
}, [targetRef]);
|
|
58
|
-
|
|
59
|
-
if (!style) return null;
|
|
60
|
-
|
|
61
|
-
return createPortal(<div style={style}>{children}</div>, document.body);
|
|
62
|
-
};
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type API,
|
|
3
|
-
type ConfigInterface,
|
|
4
|
-
ContextOptions,
|
|
5
|
-
FontPlugin,
|
|
6
|
-
loader,
|
|
7
|
-
serializeThemeContext,
|
|
8
|
-
} from '@udixio/theme';
|
|
9
|
-
import { useEffect, useRef, useState } from 'react';
|
|
10
|
-
import { TailwindPlugin } from '@udixio/tailwind';
|
|
11
|
-
import type {
|
|
12
|
-
WorkerInboundMessage,
|
|
13
|
-
WorkerOutboundMessage,
|
|
14
|
-
} from './theme.worker';
|
|
15
|
-
|
|
16
|
-
function isValidHexColor(hexColorString: string) {
|
|
17
|
-
const regex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
18
|
-
return regex.test(hexColorString);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const ThemeProvider = ({
|
|
22
|
-
config,
|
|
23
|
-
throttleDelay = 100,
|
|
24
|
-
onLoad,
|
|
25
|
-
loadTheme = false,
|
|
26
|
-
}: {
|
|
27
|
-
config: Readonly<ConfigInterface>;
|
|
28
|
-
onLoad?: (api: API) => void;
|
|
29
|
-
throttleDelay?: number;
|
|
30
|
-
loadTheme?: boolean;
|
|
31
|
-
}) => {
|
|
32
|
-
const [themeApi, setThemeApi] = useState<API | null>(null);
|
|
33
|
-
const [outputCss, setOutputCss] = useState<string | null>(null);
|
|
34
|
-
|
|
35
|
-
const workerRef = useRef<Worker | null>(null);
|
|
36
|
-
const generationRef = useRef(0);
|
|
37
|
-
const lastAppliedIdRef = useRef(0);
|
|
38
|
-
const themeApiRef = useRef<API | null>(null);
|
|
39
|
-
const firstLoadDoneRef = useRef(false);
|
|
40
|
-
const onLoadRef = useRef(onLoad);
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
onLoadRef.current = onLoad;
|
|
43
|
-
}, [onLoad]);
|
|
44
|
-
|
|
45
|
-
// Initialisation de l'API et du Worker
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
let cancelled = false;
|
|
48
|
-
|
|
49
|
-
(async () => {
|
|
50
|
-
const api = await loader(config, loadTheme);
|
|
51
|
-
if (cancelled) return;
|
|
52
|
-
|
|
53
|
-
themeApiRef.current = api;
|
|
54
|
-
setThemeApi(api);
|
|
55
|
-
|
|
56
|
-
const worker = new Worker(
|
|
57
|
-
new URL('./theme.worker.ts', import.meta.url),
|
|
58
|
-
{ type: 'module' },
|
|
59
|
-
);
|
|
60
|
-
workerRef.current = worker;
|
|
61
|
-
|
|
62
|
-
worker.onmessage = (e: MessageEvent<WorkerOutboundMessage>) => {
|
|
63
|
-
if (e.data.id > lastAppliedIdRef.current) {
|
|
64
|
-
lastAppliedIdRef.current = e.data.id;
|
|
65
|
-
firstLoadDoneRef.current = true;
|
|
66
|
-
setOutputCss(e.data.css);
|
|
67
|
-
onLoadRef.current?.(themeApiRef.current!);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
})();
|
|
71
|
-
|
|
72
|
-
return () => {
|
|
73
|
-
cancelled = true;
|
|
74
|
-
workerRef.current?.terminate();
|
|
75
|
-
workerRef.current = null;
|
|
76
|
-
};
|
|
77
|
-
}, []);
|
|
78
|
-
|
|
79
|
-
// Throttle avec exécution en tête (leading) et en fin (trailing)
|
|
80
|
-
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
81
|
-
const lastExecTimeRef = useRef<number>(0);
|
|
82
|
-
const lastArgsRef = useRef<Partial<ContextOptions> | null>(null);
|
|
83
|
-
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
if (!themeApi) return;
|
|
86
|
-
|
|
87
|
-
const ctx: Partial<ContextOptions> = {
|
|
88
|
-
...config,
|
|
89
|
-
sourceColor: config.sourceColor,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const now = Date.now();
|
|
93
|
-
const timeSinceLast = now - lastExecTimeRef.current;
|
|
94
|
-
|
|
95
|
-
const invoke = async (args: Partial<ContextOptions>) => {
|
|
96
|
-
await applyThemeChange(args);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
if (lastExecTimeRef.current === 0 || timeSinceLast >= throttleDelay) {
|
|
100
|
-
if (timeoutRef.current) {
|
|
101
|
-
clearTimeout(timeoutRef.current);
|
|
102
|
-
timeoutRef.current = null;
|
|
103
|
-
}
|
|
104
|
-
lastArgsRef.current = null;
|
|
105
|
-
lastExecTimeRef.current = now;
|
|
106
|
-
void invoke(ctx);
|
|
107
|
-
} else {
|
|
108
|
-
lastArgsRef.current = ctx;
|
|
109
|
-
if (!timeoutRef.current) {
|
|
110
|
-
const remaining = Math.max(0, throttleDelay - timeSinceLast);
|
|
111
|
-
timeoutRef.current = setTimeout(async () => {
|
|
112
|
-
timeoutRef.current = null;
|
|
113
|
-
const args = lastArgsRef.current;
|
|
114
|
-
lastArgsRef.current = null;
|
|
115
|
-
if (args) {
|
|
116
|
-
lastExecTimeRef.current = Date.now();
|
|
117
|
-
await invoke(args);
|
|
118
|
-
}
|
|
119
|
-
}, remaining);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return () => {};
|
|
124
|
-
}, [config, throttleDelay, themeApi]);
|
|
125
|
-
|
|
126
|
-
const applyThemeChange = async (ctx: Partial<ContextOptions>) => {
|
|
127
|
-
if (typeof ctx.sourceColor === 'string' && !isValidHexColor(ctx.sourceColor)) {
|
|
128
|
-
throw new Error('Invalid hex color');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const api = themeApiRef.current;
|
|
132
|
-
if (!api) return;
|
|
133
|
-
|
|
134
|
-
// Toujours évaluer sur le main thread (rapide)
|
|
135
|
-
api.context.update(ctx);
|
|
136
|
-
api.palettes.sync((ctx as any).palettes);
|
|
137
|
-
|
|
138
|
-
const worker = workerRef.current;
|
|
139
|
-
|
|
140
|
-
// Fallback synchrone : premier rendu ou Worker pas encore prêt
|
|
141
|
-
if (!worker || !firstLoadDoneRef.current) {
|
|
142
|
-
await api.load();
|
|
143
|
-
const css = api.plugins.getPlugin(TailwindPlugin).getInstance().outputCss;
|
|
144
|
-
setOutputCss(css);
|
|
145
|
-
firstLoadDoneRef.current = true;
|
|
146
|
-
onLoad?.(api);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Offload au Worker
|
|
151
|
-
const id = ++generationRef.current;
|
|
152
|
-
worker.postMessage({
|
|
153
|
-
id,
|
|
154
|
-
snapshot: serializeThemeContext(api),
|
|
155
|
-
tailwindOptions: api.plugins.getPlugin(TailwindPlugin).options,
|
|
156
|
-
fontOptions: api.plugins.getPlugin(FontPlugin).options,
|
|
157
|
-
} satisfies WorkerInboundMessage);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
return () => {
|
|
162
|
-
if (timeoutRef.current) {
|
|
163
|
-
clearTimeout(timeoutRef.current);
|
|
164
|
-
timeoutRef.current = null;
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}, []);
|
|
168
|
-
|
|
169
|
-
if (!outputCss) {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return <style dangerouslySetInnerHTML={{ __html: outputCss }} />;
|
|
174
|
-
};
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
type ScrollIntent =
|
|
4
|
-
| {
|
|
5
|
-
type: 'intent';
|
|
6
|
-
source: 'wheel' | 'touch' | 'keyboard';
|
|
7
|
-
deltaX: number;
|
|
8
|
-
deltaY: number;
|
|
9
|
-
originalEvent: Event;
|
|
10
|
-
}
|
|
11
|
-
| {
|
|
12
|
-
type: 'scrollbar';
|
|
13
|
-
scrollTop: number;
|
|
14
|
-
scrollLeft: number;
|
|
15
|
-
maxScrollTop: number;
|
|
16
|
-
maxScrollLeft: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type BlockScrollProps = {
|
|
20
|
-
onScroll?: (evt: ScrollIntent) => void; // log des intentions + du scroll via scrollbar
|
|
21
|
-
touch?: boolean;
|
|
22
|
-
el: HTMLElement;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @deprecated Potentially blocks scroll events unintentionally (wheel/touch/keyboard)
|
|
27
|
-
* and may interfere with internal scrollable areas (modals, lists, overflow containers).
|
|
28
|
-
* This API will be removed soon. Avoid using it and migrate to native behaviors
|
|
29
|
-
* or to local scroll handling at the component level.
|
|
30
|
-
*/
|
|
31
|
-
export const BlockScroll: React.FC<BlockScrollProps> = ({
|
|
32
|
-
onScroll,
|
|
33
|
-
el,
|
|
34
|
-
touch = true,
|
|
35
|
-
}) => {
|
|
36
|
-
const lastTouch = useRef<{ x: number; y: number } | null>(null);
|
|
37
|
-
const lastScrollTop = useRef<number>(0);
|
|
38
|
-
const lastScrollLeft = useRef<number>(0);
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (!el) return;
|
|
42
|
-
|
|
43
|
-
// Initialize last known scroll positions to block scrollbar-based scrolling
|
|
44
|
-
lastScrollTop.current = el.scrollTop;
|
|
45
|
-
lastScrollLeft.current = el.scrollLeft;
|
|
46
|
-
|
|
47
|
-
const emitIntent = (payload: Extract<ScrollIntent, { type: 'intent' }>) => {
|
|
48
|
-
// Log the desired deltaY for every scroll attempt (wheel/touch/keyboard)
|
|
49
|
-
onScroll?.(payload);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const findScrollableParent = (
|
|
53
|
-
node: HTMLElement | null,
|
|
54
|
-
): HTMLElement | null => {
|
|
55
|
-
let elNode: HTMLElement | null = node;
|
|
56
|
-
while (
|
|
57
|
-
elNode &&
|
|
58
|
-
elNode !== document.body &&
|
|
59
|
-
elNode !== document.documentElement
|
|
60
|
-
) {
|
|
61
|
-
const style = window.getComputedStyle(elNode);
|
|
62
|
-
const overflowY = style.overflowY || style.overflow;
|
|
63
|
-
const isScrollableY =
|
|
64
|
-
(overflowY === 'auto' || overflowY === 'scroll') &&
|
|
65
|
-
elNode.scrollHeight > elNode.clientHeight;
|
|
66
|
-
if (isScrollableY) return elNode;
|
|
67
|
-
elNode = elNode.parentElement;
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const onWheel = (e: WheelEvent) => {
|
|
73
|
-
// Auto-detect closest scrollable ancestor and allow native scroll when it can handle the intent
|
|
74
|
-
const target = e.target as HTMLElement | null;
|
|
75
|
-
|
|
76
|
-
const scrollableParent = findScrollableParent(target);
|
|
77
|
-
|
|
78
|
-
if (scrollableParent && scrollableParent !== el) {
|
|
79
|
-
const canScrollDown =
|
|
80
|
-
scrollableParent.scrollTop <
|
|
81
|
-
scrollableParent.scrollHeight - scrollableParent.clientHeight;
|
|
82
|
-
const canScrollUp = scrollableParent.scrollTop > 0;
|
|
83
|
-
|
|
84
|
-
// Wheel: positive deltaY => scroll down, negative => scroll up
|
|
85
|
-
if ((e.deltaY > 0 && canScrollDown) || (e.deltaY < 0 && canScrollUp)) {
|
|
86
|
-
// Let the native scrolling happen inside the scrollable element
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Otherwise, block native scroll and emit intent for global smooth scroll
|
|
92
|
-
e.preventDefault();
|
|
93
|
-
emitIntent({
|
|
94
|
-
type: 'intent',
|
|
95
|
-
source: 'wheel',
|
|
96
|
-
deltaX: e.deltaX,
|
|
97
|
-
deltaY: e.deltaY,
|
|
98
|
-
originalEvent: e,
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const onTouchStart = (e: TouchEvent) => {
|
|
103
|
-
if (!touch) return;
|
|
104
|
-
const t = e.touches[0];
|
|
105
|
-
if (t) lastTouch.current = { x: t.clientX, y: t.clientY };
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const onTouchMove = (e: TouchEvent) => {
|
|
109
|
-
if (!touch) return;
|
|
110
|
-
const t = e.touches[0];
|
|
111
|
-
if (!t || !lastTouch.current) return;
|
|
112
|
-
|
|
113
|
-
const dx = lastTouch.current.x - t.clientX;
|
|
114
|
-
const dy = lastTouch.current.y - t.clientY;
|
|
115
|
-
|
|
116
|
-
// Auto-detect closest scrollable ancestor for touch and allow native scroll when possible
|
|
117
|
-
const target = e.target as HTMLElement | null;
|
|
118
|
-
|
|
119
|
-
const scrollableParent = findScrollableParent(target);
|
|
120
|
-
|
|
121
|
-
if (scrollableParent && scrollableParent !== el) {
|
|
122
|
-
const canScrollDown =
|
|
123
|
-
scrollableParent.scrollTop <
|
|
124
|
-
scrollableParent.scrollHeight - scrollableParent.clientHeight;
|
|
125
|
-
const canScrollUp = scrollableParent.scrollTop > 0;
|
|
126
|
-
|
|
127
|
-
// Touch: dy > 0 means user moves finger up -> intent to scroll down
|
|
128
|
-
if ((dy > 0 && canScrollDown) || (dy < 0 && canScrollUp)) {
|
|
129
|
-
// Update last touch and allow native scroll inside the element
|
|
130
|
-
lastTouch.current = { x: t.clientX, y: t.clientY };
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Otherwise block and emit intent for global smooth scroll
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
lastTouch.current = { x: t.clientX, y: t.clientY };
|
|
138
|
-
emitIntent({
|
|
139
|
-
type: 'intent',
|
|
140
|
-
source: 'touch',
|
|
141
|
-
deltaX: dx,
|
|
142
|
-
deltaY: dy,
|
|
143
|
-
originalEvent: e,
|
|
144
|
-
});
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const onTouchEnd = () => {
|
|
148
|
-
if (!touch) return;
|
|
149
|
-
lastTouch.current = null;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const interactiveRoles = new Set([
|
|
153
|
-
'textbox',
|
|
154
|
-
'listbox',
|
|
155
|
-
'menu',
|
|
156
|
-
'menubar',
|
|
157
|
-
'grid',
|
|
158
|
-
'tree',
|
|
159
|
-
'tablist',
|
|
160
|
-
'toolbar',
|
|
161
|
-
'radiogroup',
|
|
162
|
-
'combobox',
|
|
163
|
-
'spinbutton',
|
|
164
|
-
'slider',
|
|
165
|
-
]);
|
|
166
|
-
|
|
167
|
-
const isEditableOrInteractive = (node: HTMLElement | null) => {
|
|
168
|
-
if (!node) return false;
|
|
169
|
-
const tag = node.tagName.toLowerCase();
|
|
170
|
-
if (tag === 'input' || tag === 'textarea' || tag === 'select')
|
|
171
|
-
return true;
|
|
172
|
-
if ((node as HTMLInputElement).isContentEditable) return true;
|
|
173
|
-
if (tag === 'button') return true;
|
|
174
|
-
if (tag === 'a' && (node as HTMLAnchorElement).href) return true;
|
|
175
|
-
const role = node.getAttribute('role');
|
|
176
|
-
if (role && interactiveRoles.has(role)) return true;
|
|
177
|
-
return false;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const hasInteractiveAncestor = (el: HTMLElement | null) => {
|
|
181
|
-
let n: HTMLElement | null = el;
|
|
182
|
-
while (n && n !== document.body && n !== document.documentElement) {
|
|
183
|
-
if (isEditableOrInteractive(n)) return true;
|
|
184
|
-
n = n.parentElement;
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const canAncestorScroll = (start: HTMLElement | null, dy: number) => {
|
|
190
|
-
let n: HTMLElement | null = start;
|
|
191
|
-
while (n && n !== el) {
|
|
192
|
-
const style = window.getComputedStyle(n);
|
|
193
|
-
const overflowY = style.overflowY || style.overflow;
|
|
194
|
-
const canScrollY =
|
|
195
|
-
(overflowY === 'auto' || overflowY === 'scroll') &&
|
|
196
|
-
n.scrollHeight > n.clientHeight;
|
|
197
|
-
if (canScrollY) {
|
|
198
|
-
const canDown = n.scrollTop < n.scrollHeight - n.clientHeight;
|
|
199
|
-
const canUp = n.scrollTop > 0;
|
|
200
|
-
if ((dy > 0 && canDown) || (dy < 0 && canUp)) return true;
|
|
201
|
-
}
|
|
202
|
-
n = n.parentElement;
|
|
203
|
-
}
|
|
204
|
-
return false;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const onKeyDown = (e: KeyboardEvent) => {
|
|
208
|
-
if (e.defaultPrevented) return;
|
|
209
|
-
|
|
210
|
-
// Garder les comportements natifs pour les éléments interactifs
|
|
211
|
-
const target = e.target as HTMLElement | null;
|
|
212
|
-
if (isEditableOrInteractive(target) || hasInteractiveAncestor(target)) {
|
|
213
|
-
return; // ne pas empêcher
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const line = 40;
|
|
217
|
-
const page = el.clientHeight * 0.9;
|
|
218
|
-
let dy = 0;
|
|
219
|
-
|
|
220
|
-
switch (e.key) {
|
|
221
|
-
case 'ArrowDown':
|
|
222
|
-
dy = line;
|
|
223
|
-
break;
|
|
224
|
-
case 'ArrowUp':
|
|
225
|
-
dy = -line;
|
|
226
|
-
break;
|
|
227
|
-
case 'PageDown':
|
|
228
|
-
dy = page;
|
|
229
|
-
break;
|
|
230
|
-
case 'PageUp':
|
|
231
|
-
dy = -page;
|
|
232
|
-
break;
|
|
233
|
-
case 'Home':
|
|
234
|
-
dy = Number.NEGATIVE_INFINITY;
|
|
235
|
-
break;
|
|
236
|
-
case 'End':
|
|
237
|
-
dy = Number.POSITIVE_INFINITY;
|
|
238
|
-
break;
|
|
239
|
-
case ' ': {
|
|
240
|
-
// Espace: laisser passer sur boutons/inputs/etc. déjà filtrés ci-dessus
|
|
241
|
-
dy = e.shiftKey ? -page : page;
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
default:
|
|
245
|
-
return; // ne pas gérer, laisser natif
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Si un ancêtre (≠ el) peut scroller dans ce sens, laisser le natif
|
|
249
|
-
if (canAncestorScroll(target, dy)) return;
|
|
250
|
-
|
|
251
|
-
// Ne gérer que si focus est sur body/html ou dans el
|
|
252
|
-
const ae = document.activeElement as HTMLElement | null;
|
|
253
|
-
const focusInsideEl =
|
|
254
|
-
ae &&
|
|
255
|
-
(ae === document.body ||
|
|
256
|
-
ae === document.documentElement ||
|
|
257
|
-
el.contains(ae));
|
|
258
|
-
if (!focusInsideEl) return;
|
|
259
|
-
|
|
260
|
-
// OK on prend en charge: empêcher le natif et émettre l’intention
|
|
261
|
-
e.preventDefault();
|
|
262
|
-
emitIntent({
|
|
263
|
-
type: 'intent',
|
|
264
|
-
source: 'keyboard',
|
|
265
|
-
deltaX: 0,
|
|
266
|
-
deltaY: dy,
|
|
267
|
-
originalEvent: e,
|
|
268
|
-
});
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// const onScrollEvent = (e) => {
|
|
272
|
-
// const currentScrollTop = e.target.scrollTop;
|
|
273
|
-
// const currentScrollLeft = e.target.scrollLeft;
|
|
274
|
-
//
|
|
275
|
-
// // Check if scroll position changed from last known position
|
|
276
|
-
// if (
|
|
277
|
-
// currentScrollTop !== lastScrollTop.current ||
|
|
278
|
-
// currentScrollLeft !== lastScrollLeft.current
|
|
279
|
-
// ) {
|
|
280
|
-
// console.log('onScrollllllllllll', e, document);
|
|
281
|
-
// onScroll?.({
|
|
282
|
-
// type: 'scrollbar',
|
|
283
|
-
// scrollTop: currentScrollTop,
|
|
284
|
-
// scrollLeft: currentScrollLeft,
|
|
285
|
-
// maxScrollTop: e.target.scrollHeight - e.target.clientHeight,
|
|
286
|
-
// maxScrollLeft: e.target.scrollWidth - e.target.clientWidth,
|
|
287
|
-
// });
|
|
288
|
-
// }
|
|
289
|
-
//
|
|
290
|
-
// // Update last known scroll positions
|
|
291
|
-
// lastScrollTop.current = currentScrollTop;
|
|
292
|
-
// lastScrollLeft.current = currentScrollLeft;
|
|
293
|
-
//
|
|
294
|
-
// document.querySelector('html')?.scrollTo({ top: 0 });
|
|
295
|
-
// };
|
|
296
|
-
|
|
297
|
-
el.addEventListener('wheel', onWheel, { passive: false });
|
|
298
|
-
el.addEventListener('touchstart', onTouchStart, { passive: true });
|
|
299
|
-
el.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
300
|
-
el.addEventListener('touchend', onTouchEnd, { passive: true });
|
|
301
|
-
el.addEventListener('keydown', onKeyDown);
|
|
302
|
-
// el.addEventListener('scroll', onScrollEvent, { passive: true });
|
|
303
|
-
|
|
304
|
-
return () => {
|
|
305
|
-
el.removeEventListener('wheel', onWheel as EventListener);
|
|
306
|
-
el.removeEventListener('touchstart', onTouchStart as EventListener);
|
|
307
|
-
el.removeEventListener('touchmove', onTouchMove as EventListener);
|
|
308
|
-
el.removeEventListener('touchend', onTouchEnd as EventListener);
|
|
309
|
-
el.removeEventListener('keydown', onKeyDown as EventListener);
|
|
310
|
-
// el.removeEventListener('scroll', onScrollEvent as EventListener);
|
|
311
|
-
};
|
|
312
|
-
}, [onScroll]);
|
|
313
|
-
};
|