@kanaries/graphic-walker 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/App.d.ts +0 -2
  2. package/dist/components/callout.d.ts +7 -0
  3. package/dist/components/sizeSetting.d.ts +1 -0
  4. package/dist/components/toolbar/components.d.ts +11 -0
  5. package/dist/components/toolbar/index.d.ts +15 -0
  6. package/dist/components/toolbar/toolbar-button.d.ts +7 -0
  7. package/dist/components/toolbar/toolbar-item.d.ts +40 -0
  8. package/dist/components/toolbar/toolbar-select-button.d.ts +18 -0
  9. package/dist/components/toolbar/toolbar-toggle-button.d.ts +8 -0
  10. package/dist/components/tooltip.d.ts +13 -0
  11. package/dist/fields/datasetFields/dimFields.d.ts +1 -1
  12. package/dist/fields/datasetFields/meaFields.d.ts +1 -1
  13. package/dist/fields/filterField/filterPill.d.ts +1 -1
  14. package/dist/fields/obComponents/obFContainer.d.ts +1 -1
  15. package/dist/fields/obComponents/obPill.d.ts +1 -1
  16. package/dist/graphic-walker.es.js +56487 -55304
  17. package/dist/graphic-walker.es.js.map +1 -1
  18. package/dist/graphic-walker.umd.js +501 -288
  19. package/dist/graphic-walker.umd.js.map +1 -1
  20. package/dist/index.d.ts +4 -0
  21. package/dist/style.css +0 -1
  22. package/dist/utils/throttle.d.ts +5 -0
  23. package/dist/visualSettings/menubar.d.ts +8 -0
  24. package/package.json +4 -2
  25. package/src/App.tsx +0 -4
  26. package/src/components/callout.tsx +58 -0
  27. package/src/components/sizeSetting.tsx +61 -49
  28. package/src/components/tabs/pureTab.tsx +7 -1
  29. package/src/components/toolbar/components.tsx +110 -0
  30. package/src/components/toolbar/index.tsx +57 -0
  31. package/src/components/toolbar/toolbar-button.tsx +28 -0
  32. package/src/components/toolbar/toolbar-item.tsx +218 -0
  33. package/src/components/toolbar/toolbar-select-button.tsx +196 -0
  34. package/src/components/toolbar/toolbar-toggle-button.tsx +70 -0
  35. package/src/components/tooltip.tsx +135 -0
  36. package/src/empty_sheet.css +9 -0
  37. package/src/fields/aestheticFields.tsx +1 -1
  38. package/src/fields/datasetFields/dimFields.tsx +3 -3
  39. package/src/fields/datasetFields/index.tsx +2 -2
  40. package/src/fields/datasetFields/meaFields.tsx +3 -3
  41. package/src/fields/fieldsContext.tsx +1 -1
  42. package/src/fields/filterField/filterPill.tsx +1 -1
  43. package/src/fields/filterField/index.tsx +1 -1
  44. package/src/fields/obComponents/obFContainer.tsx +1 -1
  45. package/src/fields/obComponents/obPill.tsx +1 -1
  46. package/src/fields/posFields/index.tsx +1 -1
  47. package/src/global.d.ts +7 -0
  48. package/src/index.tsx +47 -8
  49. package/src/store/visualSpecStore.ts +1 -1
  50. package/src/utils/throttle.ts +28 -0
  51. package/src/visualSettings/index.tsx +316 -321
  52. package/src/visualSettings/menubar.tsx +1 -1
@@ -0,0 +1,218 @@
1
+ import { ChevronDownIcon, Cog6ToothIcon, Cog8ToothIcon } from "@heroicons/react/24/solid";
2
+ import React, { HTMLAttributes, memo, ReactNode, useEffect, useMemo, useRef } from "react";
3
+ import styled from "styled-components";
4
+ import ToolbarButton, { ToolbarButtonItem } from "./toolbar-button";
5
+ import ToolbarToggleButton, { ToolbarToggleButtonItem } from "./toolbar-toggle-button";
6
+ import ToolbarSelectButton, { ToolbarSelectButtonItem } from "./toolbar-select-button";
7
+ import { ToolbarContainer, ToolbarItemContainerElement, ToolbarSplitter, useHandlers } from "./components";
8
+ import Toolbar, { ToolbarProps } from ".";
9
+ import Tooltip from "../tooltip";
10
+ import Callout from "../callout";
11
+
12
+
13
+ const ToolbarSplit = styled.div<{ open: boolean }>`
14
+ flex-grow: 1;
15
+ flex-shrink: 1;
16
+ display: inline-block;
17
+ height: var(--height);
18
+ position: relative;
19
+ margin-right: 4px;
20
+ > svg {
21
+ position: absolute;
22
+ width: calc(var(--icon-size) * 0.6);
23
+ height: calc(var(--icon-size) * 0.6);
24
+ left: 50%;
25
+ top: 50%;
26
+ transform: translate(-50%, ${({ open }) => open ? '-20%' : '-50%'});
27
+ transition: transform 120ms;
28
+ }
29
+ :hover > svg, :focus > svg {
30
+ transform: translate(-50%, -20%);
31
+ }
32
+ `;
33
+
34
+ const FormContainer = styled(ToolbarContainer)`
35
+ width: max-content;
36
+ height: max-content;
37
+ background-color: #fff;
38
+ `;
39
+
40
+ export interface IToolbarItem {
41
+ key: string;
42
+ icon: (props: React.ComponentProps<'svg'> & {
43
+ title?: string;
44
+ titleId?: string;
45
+ }) => JSX.Element;
46
+ label: string;
47
+ /** @default false */
48
+ disabled?: boolean;
49
+ menu?: ToolbarProps;
50
+ form?: JSX.Element;
51
+ }
52
+
53
+ export const ToolbarItemSplitter = '-';
54
+
55
+ export type ToolbarItemProps = (
56
+ | ToolbarButtonItem
57
+ | ToolbarToggleButtonItem
58
+ | ToolbarSelectButtonItem
59
+ | typeof ToolbarItemSplitter
60
+ );
61
+
62
+ export interface IToolbarProps<P extends Exclude<ToolbarItemProps, typeof ToolbarItemSplitter> = Exclude<ToolbarItemProps, typeof ToolbarItemSplitter>> {
63
+ item: P;
64
+ styles?: ToolbarProps['styles'];
65
+ openedKey: string | null;
66
+ setOpenedKey: (key: string | null) => void;
67
+ renderSlot: (node: ReactNode) => void;
68
+ }
69
+
70
+ let idFlag = 0;
71
+
72
+ export const ToolbarItemContainer = memo<{
73
+ props: IToolbarProps;
74
+ handlers: ReturnType<typeof useHandlers> | null;
75
+ children: unknown;
76
+ } & HTMLAttributes<HTMLDivElement>>(function ToolbarItemContainer (
77
+ {
78
+ props: {
79
+ item: { key, label, disabled = false, menu, form },
80
+ styles, openedKey, setOpenedKey, renderSlot,
81
+ },
82
+ handlers,
83
+ children,
84
+ ...props
85
+ }
86
+ ) {
87
+ const id = useMemo(() => `toolbar-item-${idFlag++}`, []);
88
+ const splitOnly = Boolean(form || menu) && handlers === null;
89
+
90
+ const opened = Boolean(form || menu) && key === openedKey && !disabled;
91
+ const openedRef = useRef(opened);
92
+ openedRef.current = opened;
93
+
94
+ const splitHandlers = useHandlers(() => {
95
+ setOpenedKey(opened ? null : key);
96
+ }, disabled ?? false, [' '], false);
97
+
98
+ useEffect(() => {
99
+ if (opened) {
100
+ const close = (e?: unknown) => {
101
+ if (!openedRef.current) {
102
+ return;
103
+ }
104
+ if (!e) {
105
+ setOpenedKey(null);
106
+ } else if (e instanceof KeyboardEvent && e.key === 'Escape') {
107
+ setOpenedKey(null);
108
+ } else if (e instanceof MouseEvent) {
109
+ setTimeout(() => {
110
+ if (openedRef.current) {
111
+ setOpenedKey(null);
112
+ }
113
+ }, 100);
114
+ }
115
+ };
116
+
117
+ document.addEventListener('mousedown', close);
118
+ document.addEventListener('keydown', close);
119
+
120
+ return () => {
121
+ document.removeEventListener('mousedown', close);
122
+ document.removeEventListener('keydown', close);
123
+ };
124
+ }
125
+ }, [setOpenedKey, opened]);
126
+
127
+ useEffect(() => {
128
+ if (opened && menu) {
129
+ renderSlot(<Toolbar {...menu} />);
130
+ return () => renderSlot(null);
131
+ }
132
+ }, [opened, menu, renderSlot]);
133
+
134
+ return (
135
+ <>
136
+ <Tooltip content={label}>
137
+ <ToolbarItemContainerElement
138
+ role="button" tabIndex={disabled ? undefined : 0} aria-label={label} aria-disabled={disabled ?? false}
139
+ split={Boolean(form || menu)}
140
+ style={styles?.item}
141
+ className={opened ? 'open' : undefined}
142
+ aria-haspopup={splitOnly ? 'menu' : 'false'}
143
+ {...(splitOnly ? splitHandlers : handlers)}
144
+ {...props}
145
+ id={id}
146
+ >
147
+ {children}
148
+ {form ? (
149
+ splitOnly ? (
150
+ <ToolbarSplit
151
+ open={opened}
152
+ {...splitHandlers}
153
+ >
154
+ <Cog6ToothIcon style={styles?.splitIcon}/>
155
+ </ToolbarSplit>
156
+ ) : (
157
+ <ToolbarSplit
158
+ open={opened}
159
+ role="button"
160
+ tabIndex={disabled ? undefined : 0}
161
+ {...splitHandlers}
162
+ >
163
+ <Cog6ToothIcon style={styles?.splitIcon}/>
164
+ </ToolbarSplit>
165
+ )
166
+ ) : (
167
+ menu && (
168
+ splitOnly ? (
169
+ <ToolbarSplit
170
+ open={opened}
171
+ {...splitHandlers}
172
+ >
173
+ <ChevronDownIcon style={styles?.splitIcon} />
174
+ </ToolbarSplit>
175
+ ) : (
176
+ <ToolbarSplit
177
+ role="button" tabIndex={disabled ? undefined : 0} aria-label={label} aria-disabled={disabled} aria-haspopup="menu"
178
+ open={opened}
179
+ {...splitHandlers}
180
+ >
181
+ <Cog8ToothIcon style={styles?.splitIcon} />
182
+ </ToolbarSplit>
183
+ )
184
+ )
185
+ )}
186
+ </ToolbarItemContainerElement>
187
+ </Tooltip>
188
+ {opened && form && (
189
+ <Callout target={`#${id}`}>
190
+ <FormContainer onMouseDown={e => e.stopPropagation()}>
191
+ {form}
192
+ </FormContainer>
193
+ </Callout>
194
+ )}
195
+ </>
196
+ );
197
+ });
198
+
199
+ const ToolbarItem = memo<{
200
+ item: ToolbarItemProps;
201
+ styles?: ToolbarProps['styles'];
202
+ openedKey: string | null;
203
+ setOpenedKey: (key: string | null) => void;
204
+ renderSlot: (node: ReactNode) => void;
205
+ }>(function ToolbarItem ({ item, styles, openedKey, setOpenedKey, renderSlot }) {
206
+ if (item === ToolbarItemSplitter) {
207
+ return <ToolbarSplitter />;
208
+ }
209
+ if ('checked' in item) {
210
+ return <ToolbarToggleButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
211
+ } else if ('options' in item) {
212
+ return <ToolbarSelectButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
213
+ }
214
+ return <ToolbarButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
215
+ });
216
+
217
+
218
+ export default ToolbarItem;
@@ -0,0 +1,196 @@
1
+ import React, { memo, useEffect, useRef } from "react";
2
+ import styled from "styled-components";
3
+ import produce from "immer";
4
+ import { IToolbarItem, IToolbarProps, ToolbarItemContainer } from "./toolbar-item";
5
+ import { ToolbarContainer, useHandlers, ToolbarItemContainerElement } from "./components";
6
+ import Callout from "../callout";
7
+
8
+
9
+ const OptionGroup = styled(ToolbarContainer)`
10
+ flex-direction: column;
11
+ width: max-content;
12
+ height: max-content;
13
+ --aside: 8px;
14
+ --background-color: #f7f7f7;
15
+ --background-color-hover: #fefefe;
16
+ --color: #777;
17
+ --color-hover: #555;
18
+ --blue: #282958;
19
+ background-color: var(--background-color);
20
+ `;
21
+
22
+ const Option = styled(ToolbarItemContainerElement)`
23
+ width: unset;
24
+ height: var(--height);
25
+ position: relative;
26
+ font-size: 95%;
27
+ padding-left: var(--aside);
28
+ padding-right: 1em;
29
+ align-items: center;
30
+ &[aria-selected="true"] {
31
+ ::before {
32
+ display: block;
33
+ position: absolute;
34
+ content: "";
35
+ left: calc(var(--aside) / 2);
36
+ width: calc(var(--aside) / 2);
37
+ top: calc(var(--height) / 8);
38
+ bottom: calc(var(--height) / 8);
39
+ background-color: var(--blue);
40
+ }
41
+ }
42
+ > label {
43
+ user-select: none;
44
+ pointer-events: none;
45
+ }
46
+ :hover, &[aria-selected="true"] {
47
+ color: var(--color-hover);
48
+ }
49
+ `;
50
+
51
+ const TriggerFlag = styled.span`
52
+ pointer-events: none;
53
+ position: absolute;
54
+ bottom: 0;
55
+ left: 50%;
56
+ `;
57
+
58
+ export interface ToolbarSelectButtonItem<T extends string = string> extends IToolbarItem {
59
+ options: {
60
+ key: T;
61
+ icon: (props: React.SVGProps<SVGSVGElement> & {
62
+ title?: string | undefined;
63
+ titleId?: string | undefined;
64
+ }) => JSX.Element;
65
+ label: string;
66
+ /** @default false */
67
+ disabled?: boolean;
68
+ }[];
69
+ value: T;
70
+ onSelect: (value: T) => void;
71
+ }
72
+
73
+ const ToolbarSelectButton = memo<IToolbarProps<ToolbarSelectButtonItem>>(function ToolbarSelectButton(props) {
74
+ const { item, styles, openedKey, setOpenedKey } = props;
75
+ const { key, icon: Icon, disabled, options, value, onSelect } = item;
76
+ const id = `${key}::button`;
77
+
78
+ const opened = openedKey === id;
79
+ const handlers = useHandlers(() => {
80
+ setOpenedKey(opened ? null : id);
81
+ }, disabled ?? false);
82
+
83
+ const openedRef = useRef(opened);
84
+ openedRef.current = opened;
85
+
86
+ useEffect(() => {
87
+ if (opened) {
88
+ const close = (e?: unknown) => {
89
+ if (!openedRef.current) {
90
+ return;
91
+ }
92
+ if (!e) {
93
+ setOpenedKey(null);
94
+ } else if (e instanceof KeyboardEvent && e.key === 'Escape') {
95
+ setOpenedKey(null);
96
+ } else if (e instanceof MouseEvent) {
97
+ setTimeout(() => {
98
+ if (openedRef.current) {
99
+ setOpenedKey(null);
100
+ }
101
+ }, 100);
102
+ }
103
+ };
104
+
105
+ document.addEventListener('mousedown', close);
106
+ document.addEventListener('keydown', close);
107
+
108
+ return () => {
109
+ document.removeEventListener('mousedown', close);
110
+ document.removeEventListener('keydown', close);
111
+ };
112
+ }
113
+ }, [setOpenedKey, opened]);
114
+
115
+ const currentOption = options.find(opt => opt.key === value);
116
+ const CurrentIcon = currentOption?.icon;
117
+
118
+ return (
119
+ <>
120
+ <ToolbarItemContainer
121
+ props={produce(props, draft => {
122
+ if (currentOption) {
123
+ draft.item.label = `${draft.item.label}: ${currentOption.label}`;
124
+ }
125
+ })}
126
+ handlers={handlers}
127
+ aria-haspopup="listbox"
128
+ >
129
+ <Icon style={styles?.icon} />
130
+ {CurrentIcon && (
131
+ <CurrentIcon
132
+ style={{
133
+ ...styles?.icon,
134
+ position: 'absolute',
135
+ left: 'calc(var(--height) - var(--icon-size) * 1.2)',
136
+ bottom: 'calc((var(--height) - var(--icon-size)) * 0.1)',
137
+ width: 'calc(var(--icon-size) * 0.6)',
138
+ height: 'calc(var(--icon-size) * 0.6)',
139
+ margin: 'calc((var(--height) - var(--icon-size)) * 0.2)',
140
+ filter: 'drop-shadow(0 0 0.5px var(--background-color)) '.repeat(4),
141
+ pointerEvents: 'none',
142
+ color: '#1d1e38',
143
+ }}
144
+ />
145
+ )}
146
+ <TriggerFlag aria-hidden id={id} />
147
+ </ToolbarItemContainer>
148
+ {opened && (
149
+ <Callout target={`#${id}`}>
150
+ <OptionGroup role="listbox" aria-activedescendant={`${id}::${value}`} aria-describedby={id} aria-disabled={disabled} onMouseDown={e => e.stopPropagation()}>
151
+ {options.map((option, idx, arr) => {
152
+ const selected = option.key === value;
153
+ const OptionIcon = option.icon;
154
+ const optionId = `${id}::${value}`;
155
+ const prev = arr[(idx + arr.length - 1) % arr.length];
156
+ const next = arr[(idx + 1) % arr.length];
157
+ return (
158
+ <Option
159
+ key={option.key}
160
+ id={optionId}
161
+ role="option"
162
+ aria-disabled={option.disabled ?? false}
163
+ aria-selected={selected}
164
+ split={false}
165
+ tabIndex={0}
166
+ onClick={() => {
167
+ onSelect(option.key);
168
+ setOpenedKey(null);
169
+ }}
170
+ onKeyDown={e => {
171
+ if (e.key === 'ArrowDown') {
172
+ onSelect(next.key);
173
+ } else if (e.key === 'ArrowUp') {
174
+ onSelect(prev.key);
175
+ }
176
+ }}
177
+ ref={e => {
178
+ if (e && selected) {
179
+ e.focus();
180
+ }
181
+ }}
182
+ >
183
+ <OptionIcon style={styles?.icon} />
184
+ <label className="text-xs">{option.label}</label>
185
+ </Option>
186
+ );
187
+ })}
188
+ </OptionGroup>
189
+ </Callout>
190
+ )}
191
+ </>
192
+ );
193
+ });
194
+
195
+
196
+ export default ToolbarSelectButton;
@@ -0,0 +1,70 @@
1
+ import React, { memo } from "react";
2
+ import styled from "styled-components";
3
+ import { IToolbarItem, IToolbarProps, ToolbarItemContainer } from "./toolbar-item";
4
+ import { useHandlers } from "./components";
5
+
6
+
7
+ const ToggleContainer = styled.div<{ checked: boolean }>`
8
+ flex-grow: 0;
9
+ flex-shrink: 0;
10
+ width: calc(var(--icon-size) + 12px);
11
+ height: calc(var(--icon-size) + 12px);
12
+ margin: calc((var(--height) - var(--icon-size) - 12px) / 2);
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ overflow: hidden;
17
+ box-shadow: ${({ checked }) => checked ? 'inset 0px 1px 6px 2px rgba(0, 0, 0, 0.33)' : 'inset 0px 1px 4px 1px rgba(136, 136, 136, 0.16)'};
18
+ border-radius: 4px;
19
+ position: relative;
20
+ background-color: #F7F7F722;
21
+ > svg {
22
+ width: var(--icon-size);
23
+ height: var(--icon-size);
24
+ position: absolute;
25
+ color: ${({ checked }) => checked ? '#EDEFF4' : 'var(--color)'};
26
+ --shadow-color: ${({ checked }) => checked ? '#2956bf66' : '#9ba1ab66' || '#52576366'};
27
+ transition: color 120ms;
28
+ }
29
+ ::before {
30
+ display: block;
31
+ content: "";
32
+ position: absolute;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ background-color: var(--blue-dark);
38
+ transform: ${({ checked }) => checked ? 'translate(0)' : 'translateX(-100%)'};
39
+ transition: transform 80ms;
40
+ }
41
+ `;
42
+
43
+ export interface ToolbarToggleButtonItem extends IToolbarItem {
44
+ checked: boolean;
45
+ onChange: (checked: boolean) => void;
46
+ }
47
+
48
+ const ToolbarToggleButton = memo<IToolbarProps<ToolbarToggleButtonItem>>(function ToolbarToggleButton(props) {
49
+ const { item, styles } = props;
50
+ const { icon: Icon, label, disabled, checked, onChange } = item;
51
+ const handlers = useHandlers(() => onChange(!checked), disabled ?? false);
52
+
53
+ return (
54
+ <>
55
+ <ToolbarItemContainer
56
+ props={props}
57
+ handlers={handlers}
58
+ role="checkbox"
59
+ aria-checked={checked}
60
+ >
61
+ <ToggleContainer checked={checked}>
62
+ <Icon style={styles?.icon} />
63
+ </ToggleContainer>
64
+ </ToolbarItemContainer>
65
+ </>
66
+ );
67
+ });
68
+
69
+
70
+ export default ToolbarToggleButton;
@@ -0,0 +1,135 @@
1
+ import React, { memo, useContext, useEffect, useMemo, useRef, useState } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import styled from "styled-components";
4
+ import { ShadowDomContext } from "..";
5
+
6
+
7
+ export interface TooltipProps {
8
+ children: JSX.Element;
9
+ content: string | JSX.Element | JSX.Element[];
10
+ /** @default 250 */
11
+ showDelay?: number;
12
+ /** @default 250 */
13
+ hideDelay?: number;
14
+ /** @default 3_000 */
15
+ autoHide?: number;
16
+ }
17
+
18
+ const attrName = 'data-tooltip-host-id';
19
+ let flag = 0;
20
+
21
+ const Bubble = styled.div`
22
+ border-radius: 1px;
23
+ transform: translate(-50%, -100%);
24
+ filter: drop-shadow(0 1.6px 1.2px rgba(0, 0, 0, 0.15)) drop-shadow(0 -1px 1px rgba(0, 0, 0, 0.12));
25
+ user-select: none;
26
+ ::before {
27
+ content: "";
28
+ display: block;
29
+ position: absolute;
30
+ bottom: 0;
31
+ left: 50%;
32
+ width: 8px;
33
+ height: 8px;
34
+ transform: translate(-50%, 50%) rotate(45deg);
35
+ background-color: #fff;
36
+ border-radius: 1px;
37
+ }
38
+ `;
39
+
40
+ const Tooltip = memo<TooltipProps>(function Tooltip ({ children, content, autoHide = 3_000, showDelay = 250, hideDelay = 250 }) {
41
+ const hostId = useMemo(() => flag++, []);
42
+ const [pos, setPos] = useState<[number, number]>([0, 0]);
43
+ const [show, setShow] = useState(false);
44
+ const [hover, setHover] = useState(false);
45
+ const shadowDomMeta = useContext(ShadowDomContext);
46
+ const { root } = shadowDomMeta;
47
+ const element = typeof children === 'object' ? { ...children as any } : children;
48
+ if ('props' in element) {
49
+ element.props = {
50
+ ...element.props,
51
+ [attrName]: hostId,
52
+ };
53
+ }
54
+
55
+ const autoHideRef = useRef(autoHide);
56
+ autoHideRef.current = autoHide;
57
+ const showDelayRef = useRef(showDelay);
58
+ showDelayRef.current = showDelay;
59
+ const hideDelayRef = useRef(hideDelay);
60
+ hideDelayRef.current = hideDelay;
61
+
62
+ useEffect(() => {
63
+ const item = root?.querySelector(`[${attrName}="${hostId}"]`) as HTMLElement | null;
64
+ if (item) {
65
+ let showTimer: NodeJS.Timeout | null = null;
66
+ let hideTimer: NodeJS.Timeout | null = null;
67
+ let autoHideTimer: NodeJS.Timeout | null = null;
68
+ const resetTimers = () => {
69
+ for (const timer of [showTimer, hideTimer, autoHideTimer]) {
70
+ if (timer) {
71
+ clearTimeout(timer);
72
+ }
73
+ }
74
+ };
75
+ const handleMouseOver = () => {
76
+ resetTimers();
77
+ showTimer = setTimeout(() => {
78
+ const rect = item.getBoundingClientRect();
79
+ setPos([rect.x + rect.width / 2, rect.y]);
80
+ setShow(true);
81
+ autoHideTimer = setTimeout(() => {
82
+ handleMouseOut();
83
+ }, autoHideRef.current);
84
+ }, showDelayRef.current);
85
+ };
86
+ const handleMouseMove = () => {
87
+ for (const timer of [hideTimer, autoHideTimer]) {
88
+ if (timer) {
89
+ clearTimeout(timer);
90
+ }
91
+ }
92
+ autoHideTimer = setTimeout(() => {
93
+ handleMouseOut();
94
+ }, autoHideRef.current);
95
+ };
96
+ const handleMouseOut = () => {
97
+ resetTimers();
98
+ hideTimer = setTimeout(() => {
99
+ setShow(false);
100
+ }, hideDelayRef.current);
101
+ };
102
+ item.addEventListener('mouseover', handleMouseOver);
103
+ item.addEventListener('mousemove', handleMouseMove);
104
+ item.addEventListener('mouseout', handleMouseOut);
105
+ return () => {
106
+ item.removeEventListener('mouseover', handleMouseOver);
107
+ item.removeEventListener('mousemove', handleMouseMove);
108
+ item.removeEventListener('mouseout', handleMouseOut);
109
+ if (autoHideTimer) {
110
+ clearTimeout(autoHideTimer);
111
+ }
112
+ };
113
+ }
114
+ }, [root, hostId]);
115
+
116
+ return (
117
+ <>
118
+ {element}
119
+ {(show || hover) && root && createPortal(
120
+ <Bubble
121
+ className="fixed text-xs p-1 px-3 text-gray-500 bg-white z-50"
122
+ onMouseOver={() => setHover(true)}
123
+ onMouseOut={() => setHover(false)}
124
+ style={{ left: pos[0], top: pos[1] - 4 }}
125
+ >
126
+ {content}
127
+ </Bubble>,
128
+ root
129
+ )}
130
+ </>
131
+ );
132
+ });
133
+
134
+
135
+ export default Tooltip;
@@ -0,0 +1,9 @@
1
+ /*
2
+ * This file is empty and importing it causes no side effect
3
+ * expect creating an empty <style> tag in the <head>.
4
+ * GraphicWalker starts using encapsuled css styles since v0.2.10
5
+ * so it's no more necessary to explicitly import a styled file
6
+ * from the production directory.
7
+ * This file will be temporarily kept to avoid breaking compiling
8
+ * while trying to import the out-dated style file.
9
+ */
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Droppable } from "react-beautiful-dnd";
2
+ import { Droppable } from "@kanaries/react-beautiful-dnd";
3
3
  import { DRAGGABLE_STATE_KEYS } from './fieldsContext';
4
4
  import { AestheticFieldContainer } from './components'
5
5
  import OBFieldContainer from './obComponents/obFContainer';
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Draggable, DroppableProvided } from 'react-beautiful-dnd';
2
+ import { Draggable, DroppableProvided } from '@kanaries/react-beautiful-dnd';
3
3
  import { observer } from 'mobx-react-lite';
4
4
  import { useGlobalStore } from '../../store';
5
5
  import DataTypeIcon from '../../components/dataTypeIcon';
@@ -19,7 +19,7 @@ const DimFields: React.FC<Props> = props => {
19
19
  return (
20
20
  <>
21
21
  <FieldPill
22
- className="pt-0.5 pb-0.5 pl-2 pr-2 m-1 text-xs hover:bg-blue-100 rounded-full truncate"
22
+ className="pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 rounded-full truncate border border-transparent"
23
23
  ref={provided.innerRef}
24
24
  isDragging={snapshot.isDragging}
25
25
  {...provided.draggableProps}
@@ -28,7 +28,7 @@ const DimFields: React.FC<Props> = props => {
28
28
  <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} /> {f.name}&nbsp;
29
29
  </FieldPill>
30
30
  {
31
- <FieldPill className={`pt-0.5 pb-0.5 pl-2 pr-2 m-1 text-xs hover:bg-blue-100 rounded-full border-blue-400 border truncate ${snapshot.isDragging ? '' : 'hidden'}`}
31
+ <FieldPill className={`pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 rounded-full border-blue-400 border truncate ${snapshot.isDragging ? '' : 'hidden'}`}
32
32
  isDragging={snapshot.isDragging}
33
33
  >
34
34
  <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} /> {f.name}&nbsp;
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Droppable } from "react-beautiful-dnd";
2
+ import { Droppable } from "@kanaries/react-beautiful-dnd";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import styled from 'styled-components';
5
5
  import { NestContainer } from "../../components/container";
@@ -16,7 +16,7 @@ const DatasetFields: React.FC = (props) => {
16
16
  const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.DatasetFields" });
17
17
 
18
18
  return (
19
- <DSContainer className="flex md:flex-col" style={{ paddingBlock: 0 }}>
19
+ <DSContainer className="flex md:flex-col" style={{ paddingBlock: 0, paddingInline: '0.6em' }}>
20
20
  <h4 className="text-xs mb-2 flex-grow-0 cursor-default select-none mt-2">{t("field_list")}</h4>
21
21
  <div className="pd-1 overflow-y-auto" style={{ maxHeight: "380px", minHeight: '100px' }}>
22
22
  <Droppable droppableId="dimensions" direction="vertical">