@canlooks/can-ui 0.0.111 → 0.0.113

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 (39) hide show
  1. package/dist/cjs/components/app/app.js +2 -2
  2. package/dist/cjs/components/image/image.js +1 -1
  3. package/dist/cjs/components/overlayBase/overlayBase.d.ts +1 -1
  4. package/dist/cjs/components/overlayBase/overlayBase.js +1 -1
  5. package/dist/cjs/components/pinchable/pinchable.js +14 -10
  6. package/dist/cjs/components/popper/popper.js +1 -1
  7. package/dist/cjs/components/snackbarBase/snackbarBase.d.ts +1 -1
  8. package/dist/cjs/components/snackbarBase/snackbarBase.js +1 -1
  9. package/dist/cjs/components/sortableItem/sortableItem.js +8 -5
  10. package/dist/cjs/components/sortableItem/sortableItem.style.d.ts +5 -0
  11. package/dist/cjs/components/sortableItem/sortableItem.style.js +18 -0
  12. package/dist/cjs/components/tree/treeDnd.js +3 -2
  13. package/dist/cjs/components/tree/treeDnd.style.d.ts +1 -0
  14. package/dist/cjs/components/tree/treeDnd.style.js +6 -1
  15. package/dist/cjs/components/tree/treeNode.js +12 -0
  16. package/dist/cjs/utils/hooks.d.ts +5 -1
  17. package/dist/cjs/utils/hooks.js +25 -6
  18. package/dist/cjs/utils/tree.d.ts +9 -0
  19. package/dist/cjs/utils/tree.js +56 -0
  20. package/dist/esm/components/app/app.js +4 -4
  21. package/dist/esm/components/image/image.js +1 -1
  22. package/dist/esm/components/overlayBase/overlayBase.d.ts +1 -1
  23. package/dist/esm/components/overlayBase/overlayBase.js +1 -1
  24. package/dist/esm/components/pinchable/pinchable.js +14 -10
  25. package/dist/esm/components/popper/popper.js +1 -1
  26. package/dist/esm/components/snackbarBase/snackbarBase.d.ts +1 -1
  27. package/dist/esm/components/snackbarBase/snackbarBase.js +1 -1
  28. package/dist/esm/components/sortableItem/sortableItem.js +10 -7
  29. package/dist/esm/components/sortableItem/sortableItem.style.d.ts +5 -0
  30. package/dist/esm/components/sortableItem/sortableItem.style.js +15 -0
  31. package/dist/esm/components/tree/treeDnd.js +5 -4
  32. package/dist/esm/components/tree/treeDnd.style.d.ts +1 -0
  33. package/dist/esm/components/tree/treeDnd.style.js +5 -0
  34. package/dist/esm/components/tree/treeNode.js +12 -0
  35. package/dist/esm/utils/hooks.d.ts +5 -1
  36. package/dist/esm/utils/hooks.js +24 -6
  37. package/dist/esm/utils/tree.d.ts +9 -0
  38. package/dist/esm/utils/tree.js +55 -0
  39. package/package.json +1 -1
@@ -27,12 +27,12 @@ exports.App = (({ theme, ...props }) => {
27
27
  return ((0, jsx_runtime_1.jsx)(globalEventDelegation_1.GlobalEventDelegation, { children: (0, jsx_runtime_1.jsx)(theme_1.ThemeProvider, { theme: theme, children: (0, jsx_runtime_1.jsx)(InnerApp, { ...props }) }) }));
28
28
  });
29
29
  function InnerApp({ component: Component = 'div', theme, children, fill = true, ...props }) {
30
- const appValue = (0, react_1.useMemo)(() => ({
30
+ const appValue = (0, utils_1.useExternalClass)(() => ({
31
31
  dialog: exports.App.dialog = new appDialog_1.AppDialogMethods(),
32
32
  message: exports.App.message = new appMessage_1.AppMessageMethods(),
33
33
  notification: exports.App.notification = new appNotification_1.AppNotificationMethods(),
34
34
  actionSheet: exports.App.actionSheet = new appActionSheet_1.AppActionSheetMethods()
35
- }), []);
35
+ }));
36
36
  return ((0, jsx_runtime_1.jsxs)(AppContext, { value: appValue, children: [Component
37
37
  ? (0, jsx_runtime_1.jsx)(Component, { ...props, css: [
38
38
  app_style_1.style,
@@ -47,6 +47,6 @@ exports.Image = (0, react_1.memo)(({ src, fallback, onLoad, onError, renderLoadi
47
47
  }, onLoad: loadHandler, onError: errorHandler }), loading.current &&
48
48
  (renderLoading ?? (0, jsx_runtime_1.jsx)(skeleton_1.Skeleton, { className: image_style_1.classes.skeleton })), !!renderedActions && !failed.current &&
49
49
  (0, jsx_runtime_1.jsx)("div", { className: image_style_1.classes.mask, children: renderedActions }), previewable && src &&
50
- (0, jsx_runtime_1.jsx)(gallery_1.Gallery, { src: [src], ...previewProps, open: previewOpen.current, onOpenChange: setPreviewOpen })] }));
50
+ (0, jsx_runtime_1.jsx)(gallery_1.Gallery, { src: [src], ...previewProps, open: previewOpen.current, onOpenChange: setPreviewOpen, onPointerDown: e => e.stopPropagation() })] }));
51
51
  });
52
52
  exports.Image.Gallery = gallery_1.Gallery;
@@ -24,4 +24,4 @@ export interface OverlayBaseProps extends DivProps {
24
24
  removeFocusOnOpen?: boolean;
25
25
  }
26
26
  export declare const overlayBaseTransitionDuration = 300;
27
- export declare function OverlayBase({ container, effectContainer, forceRender, open, onMaskClick, singleLayer, onOpened, onClosed, maskProps, removeFocusOnOpen, ...props }: OverlayBaseProps): false | React.ReactPortal;
27
+ export declare function OverlayBase({ container, effectContainer, forceRender, open, onMaskClick, singleLayer, onOpened, onClosed, maskProps, removeFocusOnOpen, ...props }: OverlayBaseProps): false | React.ReactPortal | null;
@@ -37,7 +37,7 @@ function OverlayBase({ container, effectContainer, forceRender, open, onMaskClic
37
37
  onClosed?.();
38
38
  forceRender === false && setShouldRender(false);
39
39
  };
40
- return shouldRender.current && (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ...props, css: overlayBase_style_1.style, className: (0, utils_1.clsx)(overlayBase_style_1.classes.root, props.className), "data-open": open, "data-custom-container": containerEl.current !== document.body, children: [(0, jsx_runtime_1.jsx)(transitionBase_1.Fade, { timeout: exports.overlayBaseTransitionDuration, ...(0, utils_1.mergeComponentProps)(maskProps, {
40
+ return shouldRender.current && containerEl.current && (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { ...props, css: overlayBase_style_1.style, className: (0, utils_1.clsx)(overlayBase_style_1.classes.root, props.className), "data-open": open, "data-custom-container": containerEl.current !== document.body, children: [(0, jsx_runtime_1.jsx)(transitionBase_1.Fade, { timeout: exports.overlayBaseTransitionDuration, ...(0, utils_1.mergeComponentProps)(maskProps, {
41
41
  in: open,
42
42
  className: overlayBase_style_1.classes.mask,
43
43
  onClick,
@@ -15,12 +15,12 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
15
15
  zoomFn(innerScale.current * .5);
16
16
  };
17
17
  wrapperRef.current.zoomTo = zoomFn;
18
- wrapperRef.current.rotate =
19
- wrapperRef.current.rotateLeft = () => {
20
- rotateFn(innerRotate.current - 90);
21
- };
18
+ wrapperRef.current.rotate = rotateFn;
19
+ wrapperRef.current.rotateLeft = () => {
20
+ rotateFn(innerRotate.current - 90);
21
+ };
22
22
  wrapperRef.current.rotateRight = () => {
23
- rotateFn(innerRotate.current + 90);
23
+ rotateFn(innerRotate.current - 90);
24
24
  };
25
25
  wrapperRef.current.reset = resetAll;
26
26
  }
@@ -134,7 +134,7 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
134
134
  resetZoom(false);
135
135
  setInnerRotate(0);
136
136
  };
137
- const doubleClickHandler = (e) => {
137
+ const onDoubleClick = (e) => {
138
138
  childrenProps.onDoubleClick?.(e);
139
139
  if (innerScale.current < 1.5) {
140
140
  zoomFn(3, e.clientX, e.clientY);
@@ -143,12 +143,16 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
143
143
  resetZoom();
144
144
  }
145
145
  };
146
- const wheelHandler = (e) => {
146
+ const onWheel = (e) => {
147
147
  childrenProps.onMouseWheel?.(e);
148
148
  e.deltaY > 0
149
149
  ? zoomFn(innerScale.current * .8, e.clientX, e.clientY)
150
150
  : zoomFn(innerScale.current * 1.2, e.clientX, e.clientY);
151
151
  };
152
+ const onTransitionEnd = (e) => {
153
+ e.currentTarget.dataset.transition = 'false';
154
+ setInnerRotate(innerRotate.current % 360);
155
+ };
152
156
  return ((0, jsx_runtime_1.jsx)(Component, { ...props, ref: wrapperRef, css: pinchable_style_1.style, className: (0, utils_1.clsx)(pinchable_style_1.classes.root, props.className), ...pinchableHandles, children: (0, react_1.cloneElement)(children, {
153
157
  ref: (0, utils_1.cloneRef)(contentRef, childrenProps.ref),
154
158
  className: (0, utils_1.clsx)(pinchable_style_1.classes.content, childrenProps.className),
@@ -160,8 +164,8 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
160
164
  transformOrigin: 'center'
161
165
  },
162
166
  ...draggableHandles,
163
- onDoubleClick: doubleClickHandler,
164
- onWheel: wheelHandler,
165
- onTransitionEnd: e => e.currentTarget.dataset.transition = 'false'
167
+ onDoubleClick,
168
+ onWheel,
169
+ onTransitionEnd
166
170
  }) }));
167
171
  });
@@ -537,7 +537,7 @@ function Popper({ ref, popperRef, anchorElement, container = document.body, effe
537
537
  ? (0, react_1.cloneElement)(children, {
538
538
  ref: childRef
539
539
  })
540
- : children, renderedOnce.current && (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(clickAway_1.ClickAway, { disabled: !clickable && !enterable && !contextMenuable,
540
+ : children, renderedOnce.current && containerEl.current && (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(clickAway_1.ClickAway, { disabled: !clickable && !enterable && !contextMenuable,
541
541
  // 右键菜单点击anchor需要关闭弹框
542
542
  targets: () => contextMenuEvent.current ? void 0 : getAnchorElement(), onClickAway: onClickAway, children: (0, jsx_runtime_1.jsx)("div", { ...props, ref: innerPopperRef, css: popper_style_1.style, className: (0, utils_1.clsx)(popper_style_1.classes.root, props.className), style: {
543
543
  ...popperBounding,
@@ -30,7 +30,7 @@ export declare const SnackbarBase: React.MemoExoticComponent<({ methods, useTo,
30
30
  max?: number;
31
31
  container?: DefineElement<HTMLElement>;
32
32
  effectContainer?: DefineElement<HTMLElement>;
33
- }) => React.ReactPortal>;
33
+ }) => React.ReactPortal | null>;
34
34
  interface SnackbarBaseItemProps extends Omit<SnackbarBaseProps, 'duration' | 'onAutoClose'> {
35
35
  id: string;
36
36
  type: keyof SnackbarBaseMethods;
@@ -86,7 +86,7 @@ exports.SnackbarBase = (0, react_2.memo)(({ methods, useTo, max = useTo === 'mes
86
86
  methods.error = defineMethod('error');
87
87
  const css = (0, snackbarBase_style_1.style)();
88
88
  const containerEl = (0, utils_1.useContainer)(container, effectContainer);
89
- return (0, react_dom_1.createPortal)(stacks.flatMap((stack, i) => stack
89
+ return containerEl.current && (0, react_dom_1.createPortal)(stacks.flatMap((stack, i) => stack
90
90
  ? (0, jsx_runtime_1.jsx)(react_transition_group_1.TransitionGroup, { css: css, className: snackbarBase_style_1.classes.root, "data-place": i, "data-use-to": useTo, children: stack.map(p => (0, react_1.createElement)(exports.SnackbarBaseItem, { ...p, key: p.id })) }, i)
91
91
  : []), containerEl.current);
92
92
  });
@@ -6,6 +6,9 @@ const react_1 = require("react");
6
6
  const sortable_1 = require("@dnd-kit/sortable");
7
7
  const utilities_1 = require("@dnd-kit/utilities");
8
8
  const utils_1 = require("../../utils");
9
+ const react_2 = require("@emotion/react");
10
+ const treeDnd_style_1 = require("../tree/treeDnd.style");
11
+ const sortableItem_style_1 = require("./sortableItem.style");
9
12
  exports.SortableItem = (({ component: Component = 'div', id, disabled, sortableArguments, ...props }) => {
10
13
  const { attributes, isDragging, listeners, setNodeRef, transform, transition } = (0, sortable_1.useSortable)({
11
14
  ...sortableArguments,
@@ -30,9 +33,9 @@ exports.SortableItem = (({ component: Component = 'div', id, disabled, sortableA
30
33
  addEventListener('pointerup', onPointerUp);
31
34
  }, []);
32
35
  (0, react_1.useEffect)(() => removeListener, []);
33
- return ((0, jsx_runtime_1.jsx)(Component, { ...attributes, ...listeners, ...props, ref: (0, utils_1.cloneRef)(setNodeRef, props.ref), style: {
34
- transform: utilities_1.CSS.Transform.toString(transform),
35
- transition,
36
- ...props.style
37
- }, onTouchStart: disabled ? void 0 : onTouchStart, "data-dragging": isDragging }));
36
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Component, { ...attributes, ...listeners, ...props, ref: (0, utils_1.cloneRef)(setNodeRef, props.ref), css: sortableItem_style_1.style, className: (0, utils_1.clsx)(sortableItem_style_1.classes.root, props.className), style: {
37
+ transform: utilities_1.CSS.Transform.toString(transform),
38
+ transition,
39
+ ...props.style
40
+ }, onTouchStart: disabled ? void 0 : onTouchStart, "data-dragging": isDragging }), isDragging && (0, jsx_runtime_1.jsx)(react_2.Global, { styles: treeDnd_style_1.globalGrabbingStyle })] }));
38
41
  });
@@ -0,0 +1,5 @@
1
+ export declare const classes: {
2
+ root: string;
3
+ };
4
+ export declare const style: import("@emotion/react").SerializedStyles;
5
+ export declare const globalGrabbingStyle: import("@emotion/react").SerializedStyles;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.globalGrabbingStyle = exports.style = exports.classes = void 0;
4
+ const react_1 = require("@emotion/react");
5
+ const utils_1 = require("../../utils");
6
+ exports.classes = (0, utils_1.defineInnerClasses)('sortable-item');
7
+ exports.style = (0, react_1.css) `
8
+ cursor: grab;
9
+
10
+ &:active {
11
+ cursor: grabbing;
12
+ }
13
+ `;
14
+ exports.globalGrabbingStyle = (0, react_1.css) `
15
+ * {
16
+ cursor: grabbing;
17
+ }
18
+ `;
@@ -5,6 +5,7 @@ exports.useTreeDndContext = useTreeDndContext;
5
5
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
6
6
  const react_1 = require("react");
7
7
  const treeDnd_style_1 = require("./treeDnd.style");
8
+ const react_2 = require("@emotion/react");
8
9
  exports.TreeDndContext = (0, react_1.createContext)({});
9
10
  function useTreeDndContext() {
10
11
  return (0, react_1.useContext)(exports.TreeDndContext);
@@ -13,8 +14,8 @@ exports.TreeDnd = (0, react_1.memo)(({ sortable, showDragHandle, onSort, childre
13
14
  const dragging = (0, react_1.useState)(void 0);
14
15
  const overing = (0, react_1.useRef)(void 0);
15
16
  const placement = (0, react_1.useRef)(void 0);
16
- return ((0, jsx_runtime_1.jsx)(exports.TreeDndContext, { value: {
17
+ return ((0, jsx_runtime_1.jsxs)(exports.TreeDndContext, { value: {
17
18
  sortable, showDragHandle, onSort,
18
19
  dragging, overing, placement
19
- }, children: (0, jsx_runtime_1.jsx)("div", { className: treeDnd_style_1.treeDndClasses.levelBlock, children: children }) }));
20
+ }, children: [(0, jsx_runtime_1.jsx)("div", { className: treeDnd_style_1.treeDndClasses.levelBlock, children: children }), !!dragging[0] && (0, jsx_runtime_1.jsx)(react_2.Global, { styles: treeDnd_style_1.globalGrabbingStyle })] }));
20
21
  });
@@ -13,3 +13,4 @@ export declare const treeDndClasses: {
13
13
  export declare function useStyle({ indent }: {
14
14
  indent: number;
15
15
  }): import("@emotion/react").SerializedStyles;
16
+ export declare const globalGrabbingStyle: import("@emotion/react").SerializedStyles;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.treeDndClasses = void 0;
3
+ exports.globalGrabbingStyle = exports.treeDndClasses = void 0;
4
4
  exports.useStyle = useStyle;
5
5
  const tslib_1 = require("tslib");
6
6
  const utils_1 = require("../../utils");
@@ -191,3 +191,8 @@ function useStyle({ indent }) {
191
191
  }
192
192
  `, [indent]);
193
193
  }
194
+ exports.globalGrabbingStyle = (0, react_1.css) `
195
+ * {
196
+ cursor: grabbing;
197
+ }
198
+ `;
@@ -64,6 +64,7 @@ exports.TreeNode = (0, react_1.memo)(({ id, label, prefix, suffix, disabled, _le
64
64
  const pointerLeavePredecessor = (e) => {
65
65
  inactiveBlock(e.currentTarget);
66
66
  };
67
+ const overingTimeout = (0, react_1.useRef)(void 0);
67
68
  const activeBlock = (el, _placement, upLevel = 0) => {
68
69
  if (_placement === 'parent') {
69
70
  let i = upLevel;
@@ -93,6 +94,13 @@ exports.TreeNode = (0, react_1.memo)(({ id, label, prefix, suffix, disabled, _le
93
94
  return true;
94
95
  }
95
96
  });
97
+ // 如果有children且为折叠状态,over在该节点上一定时间后,需要展开该节点
98
+ if (hasChildren && !currentExpanded) {
99
+ overingTimeout.current ||= setTimeout(() => {
100
+ overingTimeout.current = void 0;
101
+ toggleExpanded(id);
102
+ }, 600);
103
+ }
96
104
  };
97
105
  const inactiveBlock = (el) => {
98
106
  if (el) {
@@ -105,6 +113,10 @@ exports.TreeNode = (0, react_1.memo)(({ id, label, prefix, suffix, disabled, _le
105
113
  el.dataset.active = 'false';
106
114
  });
107
115
  overing.current = void 0;
116
+ if (overingTimeout.current) {
117
+ clearTimeout(overingTimeout.current);
118
+ overingTimeout.current = void 0;
119
+ }
108
120
  };
109
121
  /**
110
122
  * ---------------------------------------------------------------------
@@ -55,4 +55,8 @@ export declare function useLoading<A extends any[], R>(fn: (...args: A) => R | P
55
55
  * @param effectContainer
56
56
  * @param defaultContainer 默认为`document.body`
57
57
  */
58
- export declare function useContainer<T extends HTMLElement | null>(container?: DefineElement<T>, effectContainer?: DefineElement<T>, defaultContainer?: T): RefObject<T>;
58
+ export declare function useContainer<T extends HTMLElement | null>(container?: DefineElement<T>, effectContainer?: DefineElement<T>, defaultContainer?: DefineElement<T>): RefObject<T | null>;
59
+ /**
60
+ * 使用外部类,该方法可避免`StrictMode`下,React渲染行为与外部类实例生命周期不同步的问题
61
+ */
62
+ export declare function useExternalClass<T>(setup: () => T, cleanup?: (instance: T) => void): T;
@@ -9,6 +9,7 @@ exports.useUnmounted = useUnmounted;
9
9
  exports.useControlled = useControlled;
10
10
  exports.useLoading = useLoading;
11
11
  exports.useContainer = useContainer;
12
+ exports.useExternalClass = useExternalClass;
12
13
  const react_1 = require("react");
13
14
  const utils_1 = require("./utils");
14
15
  /**
@@ -130,18 +131,36 @@ function useLoading(fn, referredLoading = false) {
130
131
  * @param effectContainer
131
132
  * @param defaultContainer 默认为`document.body`
132
133
  */
133
- function useContainer(container, effectContainer, defaultContainer = document.body) {
134
+ function useContainer(container, effectContainer, defaultContainer) {
134
135
  const [containerEl, setContainerEl] = useDerivedState(prev => {
135
136
  if (container) {
136
137
  return typeof container === 'function' ? container() : container;
137
138
  }
138
- return prev || defaultContainer;
139
- }, [container, defaultContainer]);
139
+ return prev || null;
140
+ }, [container]);
140
141
  (0, react_1.useEffect)(() => {
141
- if (effectContainer) {
142
- const el = typeof effectContainer === 'function' ? effectContainer() : effectContainer;
142
+ const _container = effectContainer || defaultContainer || document.body;
143
+ if (_container) {
144
+ const el = typeof _container === 'function' ? _container() : _container;
143
145
  setContainerEl(el);
144
146
  }
145
- }, []);
147
+ }, [effectContainer, defaultContainer]);
146
148
  return containerEl;
147
149
  }
150
+ /**
151
+ * 使用外部类,该方法可避免`StrictMode`下,React渲染行为与外部类实例生命周期不同步的问题
152
+ */
153
+ function useExternalClass(setup, cleanup) {
154
+ const mountTimes = (0, react_1.useRef)(0);
155
+ const prevInstance = (0, react_1.useRef)(void 0);
156
+ const [instance] = (0, react_1.useState)(() => {
157
+ if (!mountTimes.current++) {
158
+ prevInstance.current = setup();
159
+ }
160
+ return prevInstance.current;
161
+ });
162
+ (0, react_1.useEffect)(() => () => {
163
+ !--mountTimes.current && cleanup?.(instance);
164
+ }, []);
165
+ return instance;
166
+ }
@@ -1,4 +1,5 @@
1
1
  import { Id, Obj } from '../types';
2
+ import { NodeType, SortInfo } from '../components/tree';
2
3
  /**
3
4
  * ------------------------------------------------------------------------
4
5
  * 统一处理树形搜索
@@ -25,3 +26,11 @@ export declare function useTreeSearch<N extends Obj, V extends Id = Id>({ nodes,
25
26
  setInnerSearchValue: import("react").Dispatch<import("react").SetStateAction<string>>;
26
27
  filteredTreeData: N[] | undefined;
27
28
  };
29
+ /**
30
+ * 得到排序后的树结构
31
+ */
32
+ export declare function sortTreeNodes<N extends NodeType<V>, V extends Id = Id>(props: {
33
+ nodes: N[];
34
+ primaryKey?: keyof N;
35
+ childrenKey?: keyof N;
36
+ }, sortInfo: SortInfo<V>): N[];
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useTreeSearch = useTreeSearch;
4
+ exports.sortTreeNodes = sortTreeNodes;
4
5
  const react_1 = require("react");
5
6
  const hooks_1 = require("./hooks");
6
7
  function useTreeSearch({ nodes, primaryKey = 'id', labelKey = 'label', childrenKey = 'children', searchTokenKey = 'searchToken', defaultExpanded = [], expanded, onExpandedChange, defaultSearchValue = '', searchValue, onSearchChange }) {
@@ -61,3 +62,58 @@ function useTreeSearch({ nodes, primaryKey = 'id', labelKey = 'label', childrenK
61
62
  filteredTreeData
62
63
  };
63
64
  }
65
+ /**
66
+ * 得到排序后的树结构
67
+ */
68
+ function sortTreeNodes(props, sortInfo) {
69
+ let { nodes, primaryKey = 'id', childrenKey = 'children' } = props;
70
+ const { source, destination, placement } = sortInfo;
71
+ nodes = structuredClone(nodes);
72
+ const pickUp = (nodes) => {
73
+ if (!nodes?.length) {
74
+ return null;
75
+ }
76
+ for (let i = 0, { length } = nodes; i < length; i++) {
77
+ const node = nodes[i];
78
+ if (node[primaryKey] === source) {
79
+ nodes.splice(i, 1);
80
+ return node;
81
+ }
82
+ const foundInChildren = pickUp(node[childrenKey]);
83
+ if (foundInChildren) {
84
+ return foundInChildren;
85
+ }
86
+ }
87
+ return null;
88
+ };
89
+ pickUp(nodes);
90
+ const putDown = (nodes) => {
91
+ if (!nodes?.length) {
92
+ return false;
93
+ }
94
+ for (let i = 0, { length } = nodes; i < length; i++) {
95
+ const node = nodes[i];
96
+ if (node[primaryKey] === destination) {
97
+ switch (placement) {
98
+ case 'before':
99
+ nodes.splice(i, 0, node);
100
+ return true;
101
+ case 'after':
102
+ nodes.splice(i + 1, 0, node);
103
+ return true;
104
+ default:
105
+ node.children ||= [];
106
+ node.children.push(node);
107
+ return true;
108
+ }
109
+ }
110
+ const foundInChildren = pickUp(node[childrenKey]);
111
+ if (foundInChildren) {
112
+ return foundInChildren;
113
+ }
114
+ }
115
+ return false;
116
+ };
117
+ putDown(nodes);
118
+ return nodes;
119
+ }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
- import { createContext, useContext, useMemo } from 'react';
3
- import { clsx, defineCss } from '../../utils';
2
+ import { createContext, useContext } from 'react';
3
+ import { clsx, defineCss, useExternalClass } from '../../utils';
4
4
  import { classes, style } from './app.style';
5
5
  import { ThemeProvider } from '../theme';
6
6
  import { AppDialog, AppDialogMethods } from './appDialog';
@@ -22,12 +22,12 @@ export const App = (({ theme, ...props }) => {
22
22
  return (_jsx(GlobalEventDelegation, { children: _jsx(ThemeProvider, { theme: theme, children: _jsx(InnerApp, { ...props }) }) }));
23
23
  });
24
24
  export function InnerApp({ component: Component = 'div', theme, children, fill = true, ...props }) {
25
- const appValue = useMemo(() => ({
25
+ const appValue = useExternalClass(() => ({
26
26
  dialog: App.dialog = new AppDialogMethods(),
27
27
  message: App.message = new AppMessageMethods(),
28
28
  notification: App.notification = new AppNotificationMethods(),
29
29
  actionSheet: App.actionSheet = new AppActionSheetMethods()
30
- }), []);
30
+ }));
31
31
  return (_jsxs(AppContext, { value: appValue, children: [Component
32
32
  ? _jsx(Component, { ...props, css: [
33
33
  style,
@@ -44,6 +44,6 @@ export const Image = memo(({ src, fallback, onLoad, onError, renderLoading, alt
44
44
  }, onLoad: loadHandler, onError: errorHandler }), loading.current &&
45
45
  (renderLoading ?? _jsx(Skeleton, { className: classes.skeleton })), !!renderedActions && !failed.current &&
46
46
  _jsx("div", { className: classes.mask, children: renderedActions }), previewable && src &&
47
- _jsx(Gallery, { src: [src], ...previewProps, open: previewOpen.current, onOpenChange: setPreviewOpen })] }));
47
+ _jsx(Gallery, { src: [src], ...previewProps, open: previewOpen.current, onOpenChange: setPreviewOpen, onPointerDown: e => e.stopPropagation() })] }));
48
48
  });
49
49
  Image.Gallery = Gallery;
@@ -24,4 +24,4 @@ export interface OverlayBaseProps extends DivProps {
24
24
  removeFocusOnOpen?: boolean;
25
25
  }
26
26
  export declare const overlayBaseTransitionDuration = 300;
27
- export declare function OverlayBase({ container, effectContainer, forceRender, open, onMaskClick, singleLayer, onOpened, onClosed, maskProps, removeFocusOnOpen, ...props }: OverlayBaseProps): false | React.ReactPortal;
27
+ export declare function OverlayBase({ container, effectContainer, forceRender, open, onMaskClick, singleLayer, onOpened, onClosed, maskProps, removeFocusOnOpen, ...props }: OverlayBaseProps): false | React.ReactPortal | null;
@@ -33,7 +33,7 @@ export function OverlayBase({ container, effectContainer, forceRender, open, onM
33
33
  onClosed?.();
34
34
  forceRender === false && setShouldRender(false);
35
35
  };
36
- return shouldRender.current && createPortal(_jsxs("div", { ...props, css: style, className: clsx(classes.root, props.className), "data-open": open, "data-custom-container": containerEl.current !== document.body, children: [_jsx(Fade, { timeout: overlayBaseTransitionDuration, ...mergeComponentProps(maskProps, {
36
+ return shouldRender.current && containerEl.current && createPortal(_jsxs("div", { ...props, css: style, className: clsx(classes.root, props.className), "data-open": open, "data-custom-container": containerEl.current !== document.body, children: [_jsx(Fade, { timeout: overlayBaseTransitionDuration, ...mergeComponentProps(maskProps, {
37
37
  in: open,
38
38
  className: classes.mask,
39
39
  onClick,
@@ -12,12 +12,12 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
12
12
  zoomFn(innerScale.current * .5);
13
13
  };
14
14
  wrapperRef.current.zoomTo = zoomFn;
15
- wrapperRef.current.rotate =
16
- wrapperRef.current.rotateLeft = () => {
17
- rotateFn(innerRotate.current - 90);
18
- };
15
+ wrapperRef.current.rotate = rotateFn;
16
+ wrapperRef.current.rotateLeft = () => {
17
+ rotateFn(innerRotate.current - 90);
18
+ };
19
19
  wrapperRef.current.rotateRight = () => {
20
- rotateFn(innerRotate.current + 90);
20
+ rotateFn(innerRotate.current - 90);
21
21
  };
22
22
  wrapperRef.current.reset = resetAll;
23
23
  }
@@ -131,7 +131,7 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
131
131
  resetZoom(false);
132
132
  setInnerRotate(0);
133
133
  };
134
- const doubleClickHandler = (e) => {
134
+ const onDoubleClick = (e) => {
135
135
  childrenProps.onDoubleClick?.(e);
136
136
  if (innerScale.current < 1.5) {
137
137
  zoomFn(3, e.clientX, e.clientY);
@@ -140,12 +140,16 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
140
140
  resetZoom();
141
141
  }
142
142
  };
143
- const wheelHandler = (e) => {
143
+ const onWheel = (e) => {
144
144
  childrenProps.onMouseWheel?.(e);
145
145
  e.deltaY > 0
146
146
  ? zoomFn(innerScale.current * .8, e.clientX, e.clientY)
147
147
  : zoomFn(innerScale.current * 1.2, e.clientX, e.clientY);
148
148
  };
149
+ const onTransitionEnd = (e) => {
150
+ e.currentTarget.dataset.transition = 'false';
151
+ setInnerRotate(innerRotate.current % 360);
152
+ };
149
153
  return (_jsx(Component, { ...props, ref: wrapperRef, css: style, className: clsx(classes.root, props.className), ...pinchableHandles, children: cloneElement(children, {
150
154
  ref: cloneRef(contentRef, childrenProps.ref),
151
155
  className: clsx(classes.content, childrenProps.className),
@@ -157,8 +161,8 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
157
161
  transformOrigin: 'center'
158
162
  },
159
163
  ...draggableHandles,
160
- onDoubleClick: doubleClickHandler,
161
- onWheel: wheelHandler,
162
- onTransitionEnd: e => e.currentTarget.dataset.transition = 'false'
164
+ onDoubleClick,
165
+ onWheel,
166
+ onTransitionEnd
163
167
  }) }));
164
168
  });
@@ -534,7 +534,7 @@ export function Popper({ ref, popperRef, anchorElement, container = document.bod
534
534
  ? cloneElement(children, {
535
535
  ref: childRef
536
536
  })
537
- : children, renderedOnce.current && createPortal(_jsx(ClickAway, { disabled: !clickable && !enterable && !contextMenuable,
537
+ : children, renderedOnce.current && containerEl.current && createPortal(_jsx(ClickAway, { disabled: !clickable && !enterable && !contextMenuable,
538
538
  // 右键菜单点击anchor需要关闭弹框
539
539
  targets: () => contextMenuEvent.current ? void 0 : getAnchorElement(), onClickAway: onClickAway, children: _jsx("div", { ...props, ref: innerPopperRef, css: style, className: clsx(classes.root, props.className), style: {
540
540
  ...popperBounding,
@@ -30,7 +30,7 @@ export declare const SnackbarBase: React.MemoExoticComponent<({ methods, useTo,
30
30
  max?: number;
31
31
  container?: DefineElement<HTMLElement>;
32
32
  effectContainer?: DefineElement<HTMLElement>;
33
- }) => React.ReactPortal>;
33
+ }) => React.ReactPortal | null>;
34
34
  interface SnackbarBaseItemProps extends Omit<SnackbarBaseProps, 'duration' | 'onAutoClose'> {
35
35
  id: string;
36
36
  type: keyof SnackbarBaseMethods;
@@ -82,7 +82,7 @@ export const SnackbarBase = memo(({ methods, useTo, max = useTo === 'message' ?
82
82
  methods.error = defineMethod('error');
83
83
  const css = style();
84
84
  const containerEl = useContainer(container, effectContainer);
85
- return createPortal(stacks.flatMap((stack, i) => stack
85
+ return containerEl.current && createPortal(stacks.flatMap((stack, i) => stack
86
86
  ? _jsx(TransitionGroup, { css: css, className: classes.root, "data-place": i, "data-use-to": useTo, children: stack.map(p => _createElement(SnackbarBaseItem, { ...p, key: p.id })) }, i)
87
87
  : []), containerEl.current);
88
88
  });
@@ -1,8 +1,11 @@
1
- import { jsx as _jsx } from "@emotion/react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef } from 'react';
3
3
  import { useSortable } from '@dnd-kit/sortable';
4
4
  import { CSS } from '@dnd-kit/utilities';
5
- import { cloneRef, useSync } from '../../utils';
5
+ import { cloneRef, clsx, useSync } from '../../utils';
6
+ import { Global } from '@emotion/react';
7
+ import { globalGrabbingStyle } from '../tree/treeDnd.style';
8
+ import { classes, style } from './sortableItem.style';
6
9
  export const SortableItem = (({ component: Component = 'div', id, disabled, sortableArguments, ...props }) => {
7
10
  const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({
8
11
  ...sortableArguments,
@@ -27,9 +30,9 @@ export const SortableItem = (({ component: Component = 'div', id, disabled, sort
27
30
  addEventListener('pointerup', onPointerUp);
28
31
  }, []);
29
32
  useEffect(() => removeListener, []);
30
- return (_jsx(Component, { ...attributes, ...listeners, ...props, ref: cloneRef(setNodeRef, props.ref), style: {
31
- transform: CSS.Transform.toString(transform),
32
- transition,
33
- ...props.style
34
- }, onTouchStart: disabled ? void 0 : onTouchStart, "data-dragging": isDragging }));
33
+ return (_jsxs(_Fragment, { children: [_jsx(Component, { ...attributes, ...listeners, ...props, ref: cloneRef(setNodeRef, props.ref), css: style, className: clsx(classes.root, props.className), style: {
34
+ transform: CSS.Transform.toString(transform),
35
+ transition,
36
+ ...props.style
37
+ }, onTouchStart: disabled ? void 0 : onTouchStart, "data-dragging": isDragging }), isDragging && _jsx(Global, { styles: globalGrabbingStyle })] }));
35
38
  });
@@ -0,0 +1,5 @@
1
+ export declare const classes: {
2
+ root: string;
3
+ };
4
+ export declare const style: import("@emotion/react").SerializedStyles;
5
+ export declare const globalGrabbingStyle: import("@emotion/react").SerializedStyles;
@@ -0,0 +1,15 @@
1
+ import { css } from '@emotion/react';
2
+ import { defineInnerClasses } from '../../utils';
3
+ export const classes = defineInnerClasses('sortable-item');
4
+ export const style = css `
5
+ cursor: grab;
6
+
7
+ &:active {
8
+ cursor: grabbing;
9
+ }
10
+ `;
11
+ export const globalGrabbingStyle = css `
12
+ * {
13
+ cursor: grabbing;
14
+ }
15
+ `;
@@ -1,6 +1,7 @@
1
- import { jsx as _jsx } from "@emotion/react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import { createContext, memo, useContext, useRef, useState } from 'react';
3
- import { treeDndClasses } from './treeDnd.style';
3
+ import { globalGrabbingStyle, treeDndClasses } from './treeDnd.style';
4
+ import { Global } from '@emotion/react';
4
5
  export const TreeDndContext = createContext({});
5
6
  export function useTreeDndContext() {
6
7
  return useContext(TreeDndContext);
@@ -9,8 +10,8 @@ export const TreeDnd = memo(({ sortable, showDragHandle, onSort, children }) =>
9
10
  const dragging = useState(void 0);
10
11
  const overing = useRef(void 0);
11
12
  const placement = useRef(void 0);
12
- return (_jsx(TreeDndContext, { value: {
13
+ return (_jsxs(TreeDndContext, { value: {
13
14
  sortable, showDragHandle, onSort,
14
15
  dragging, overing, placement
15
- }, children: _jsx("div", { className: treeDndClasses.levelBlock, children: children }) }));
16
+ }, children: [_jsx("div", { className: treeDndClasses.levelBlock, children: children }), !!dragging[0] && _jsx(Global, { styles: globalGrabbingStyle })] }));
16
17
  });
@@ -13,3 +13,4 @@ export declare const treeDndClasses: {
13
13
  export declare function useStyle({ indent }: {
14
14
  indent: number;
15
15
  }): import("@emotion/react").SerializedStyles;
16
+ export declare const globalGrabbingStyle: import("@emotion/react").SerializedStyles;
@@ -186,3 +186,8 @@ export function useStyle({ indent }) {
186
186
  }
187
187
  `, [indent]);
188
188
  }
189
+ export const globalGrabbingStyle = css `
190
+ * {
191
+ cursor: grabbing;
192
+ }
193
+ `;
@@ -61,6 +61,7 @@ export const TreeNode = memo(({ id, label, prefix, suffix, disabled, _level = 0,
61
61
  const pointerLeavePredecessor = (e) => {
62
62
  inactiveBlock(e.currentTarget);
63
63
  };
64
+ const overingTimeout = useRef(void 0);
64
65
  const activeBlock = (el, _placement, upLevel = 0) => {
65
66
  if (_placement === 'parent') {
66
67
  let i = upLevel;
@@ -90,6 +91,13 @@ export const TreeNode = memo(({ id, label, prefix, suffix, disabled, _level = 0,
90
91
  return true;
91
92
  }
92
93
  });
94
+ // 如果有children且为折叠状态,over在该节点上一定时间后,需要展开该节点
95
+ if (hasChildren && !currentExpanded) {
96
+ overingTimeout.current ||= setTimeout(() => {
97
+ overingTimeout.current = void 0;
98
+ toggleExpanded(id);
99
+ }, 600);
100
+ }
93
101
  };
94
102
  const inactiveBlock = (el) => {
95
103
  if (el) {
@@ -102,6 +110,10 @@ export const TreeNode = memo(({ id, label, prefix, suffix, disabled, _level = 0,
102
110
  el.dataset.active = 'false';
103
111
  });
104
112
  overing.current = void 0;
113
+ if (overingTimeout.current) {
114
+ clearTimeout(overingTimeout.current);
115
+ overingTimeout.current = void 0;
116
+ }
105
117
  };
106
118
  /**
107
119
  * ---------------------------------------------------------------------
@@ -55,4 +55,8 @@ export declare function useLoading<A extends any[], R>(fn: (...args: A) => R | P
55
55
  * @param effectContainer
56
56
  * @param defaultContainer 默认为`document.body`
57
57
  */
58
- export declare function useContainer<T extends HTMLElement | null>(container?: DefineElement<T>, effectContainer?: DefineElement<T>, defaultContainer?: T): RefObject<T>;
58
+ export declare function useContainer<T extends HTMLElement | null>(container?: DefineElement<T>, effectContainer?: DefineElement<T>, defaultContainer?: DefineElement<T>): RefObject<T | null>;
59
+ /**
60
+ * 使用外部类,该方法可避免`StrictMode`下,React渲染行为与外部类实例生命周期不同步的问题
61
+ */
62
+ export declare function useExternalClass<T>(setup: () => T, cleanup?: (instance: T) => void): T;
@@ -119,18 +119,36 @@ export function useLoading(fn, referredLoading = false) {
119
119
  * @param effectContainer
120
120
  * @param defaultContainer 默认为`document.body`
121
121
  */
122
- export function useContainer(container, effectContainer, defaultContainer = document.body) {
122
+ export function useContainer(container, effectContainer, defaultContainer) {
123
123
  const [containerEl, setContainerEl] = useDerivedState(prev => {
124
124
  if (container) {
125
125
  return typeof container === 'function' ? container() : container;
126
126
  }
127
- return prev || defaultContainer;
128
- }, [container, defaultContainer]);
127
+ return prev || null;
128
+ }, [container]);
129
129
  useEffect(() => {
130
- if (effectContainer) {
131
- const el = typeof effectContainer === 'function' ? effectContainer() : effectContainer;
130
+ const _container = effectContainer || defaultContainer || document.body;
131
+ if (_container) {
132
+ const el = typeof _container === 'function' ? _container() : _container;
132
133
  setContainerEl(el);
133
134
  }
134
- }, []);
135
+ }, [effectContainer, defaultContainer]);
135
136
  return containerEl;
136
137
  }
138
+ /**
139
+ * 使用外部类,该方法可避免`StrictMode`下,React渲染行为与外部类实例生命周期不同步的问题
140
+ */
141
+ export function useExternalClass(setup, cleanup) {
142
+ const mountTimes = useRef(0);
143
+ const prevInstance = useRef(void 0);
144
+ const [instance] = useState(() => {
145
+ if (!mountTimes.current++) {
146
+ prevInstance.current = setup();
147
+ }
148
+ return prevInstance.current;
149
+ });
150
+ useEffect(() => () => {
151
+ !--mountTimes.current && cleanup?.(instance);
152
+ }, []);
153
+ return instance;
154
+ }
@@ -1,4 +1,5 @@
1
1
  import { Id, Obj } from '../types';
2
+ import { NodeType, SortInfo } from '../components/tree';
2
3
  /**
3
4
  * ------------------------------------------------------------------------
4
5
  * 统一处理树形搜索
@@ -25,3 +26,11 @@ export declare function useTreeSearch<N extends Obj, V extends Id = Id>({ nodes,
25
26
  setInnerSearchValue: import("react").Dispatch<import("react").SetStateAction<string>>;
26
27
  filteredTreeData: N[] | undefined;
27
28
  };
29
+ /**
30
+ * 得到排序后的树结构
31
+ */
32
+ export declare function sortTreeNodes<N extends NodeType<V>, V extends Id = Id>(props: {
33
+ nodes: N[];
34
+ primaryKey?: keyof N;
35
+ childrenKey?: keyof N;
36
+ }, sortInfo: SortInfo<V>): N[];
@@ -58,3 +58,58 @@ export function useTreeSearch({ nodes, primaryKey = 'id', labelKey = 'label', ch
58
58
  filteredTreeData
59
59
  };
60
60
  }
61
+ /**
62
+ * 得到排序后的树结构
63
+ */
64
+ export function sortTreeNodes(props, sortInfo) {
65
+ let { nodes, primaryKey = 'id', childrenKey = 'children' } = props;
66
+ const { source, destination, placement } = sortInfo;
67
+ nodes = structuredClone(nodes);
68
+ const pickUp = (nodes) => {
69
+ if (!nodes?.length) {
70
+ return null;
71
+ }
72
+ for (let i = 0, { length } = nodes; i < length; i++) {
73
+ const node = nodes[i];
74
+ if (node[primaryKey] === source) {
75
+ nodes.splice(i, 1);
76
+ return node;
77
+ }
78
+ const foundInChildren = pickUp(node[childrenKey]);
79
+ if (foundInChildren) {
80
+ return foundInChildren;
81
+ }
82
+ }
83
+ return null;
84
+ };
85
+ pickUp(nodes);
86
+ const putDown = (nodes) => {
87
+ if (!nodes?.length) {
88
+ return false;
89
+ }
90
+ for (let i = 0, { length } = nodes; i < length; i++) {
91
+ const node = nodes[i];
92
+ if (node[primaryKey] === destination) {
93
+ switch (placement) {
94
+ case 'before':
95
+ nodes.splice(i, 0, node);
96
+ return true;
97
+ case 'after':
98
+ nodes.splice(i + 1, 0, node);
99
+ return true;
100
+ default:
101
+ node.children ||= [];
102
+ node.children.push(node);
103
+ return true;
104
+ }
105
+ }
106
+ const foundInChildren = pickUp(node[childrenKey]);
107
+ if (foundInChildren) {
108
+ return foundInChildren;
109
+ }
110
+ }
111
+ return false;
112
+ };
113
+ putDown(nodes);
114
+ return nodes;
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canlooks/can-ui",
3
- "version": "0.0.111",
3
+ "version": "0.0.113",
4
4
  "author": "C.CanLiang <canlooks@gmail.com>",
5
5
  "description": "My ui framework",
6
6
  "license": "MIT",