@ioca/react 1.5.18 → 1.5.19

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/lib/index.js CHANGED
@@ -135,12 +135,15 @@ function useMouseUp(listener, options) {
135
135
  }
136
136
  function useKeydown(listener, options) {
137
137
  initEventsOnce();
138
+ const listenerRef = useRef(listener);
139
+ listenerRef.current = listener;
138
140
  useEffect(() => {
139
141
  if (options?.disabled)
140
142
  return;
141
- KeydownEvents.add(listener);
143
+ const handler = (e) => listenerRef.current(e);
144
+ KeydownEvents.add(handler);
142
145
  return () => {
143
- KeydownEvents.delete(listener);
146
+ KeydownEvents.delete(handler);
144
147
  };
145
148
  }, []);
146
149
  }
@@ -1785,7 +1788,7 @@ const List$1 = forwardRef((props, ref) => {
1785
1788
  });
1786
1789
  List$1.Item = Item$4;
1787
1790
 
1788
- const Content$2 = forwardRef((props, ref) => {
1791
+ const Content$3 = forwardRef((props, ref) => {
1789
1792
  const { arrow, arrowProps = {}, className, children, ...restProps } = props;
1790
1793
  const arrowCSS = useMemo(() => {
1791
1794
  let { left = 0, top = 0, pos } = arrowProps;
@@ -2297,7 +2300,7 @@ function Popup(props) {
2297
2300
  window.removeEventListener("scroll", onScrollOrResize, true);
2298
2301
  };
2299
2302
  }, [show]);
2300
- return (jsxs(Fragment, { children: [triggerNode, show && (jsx(Content$2, { ref: contentRef, arrow: arrow && trigger !== "contextmenu", style: {
2303
+ return (jsxs(Fragment, { children: [triggerNode, show && (jsx(Content$3, { ref: contentRef, arrow: arrow && trigger !== "contextmenu", style: {
2301
2304
  ...style,
2302
2305
  position: "fixed",
2303
2306
  }, className: className, ...contentTouch, trigger: triggerRef.current, children: content }))] }));
@@ -2703,6 +2706,7 @@ const Editor = (props) => {
2703
2706
  const [memtionKeyword, setMemtionKeyword] = useState("");
2704
2707
  const [memtionActiveIndex, setMemtionActiveIndex] = useState(0);
2705
2708
  const [activeMemtionIndex, setActiveMemtionIndex] = useState(-1);
2709
+ const activeMemtionIndexRef = useRef(-1);
2706
2710
  const memtionOptions = useMemo(() => {
2707
2711
  if (activeMemtionIndex < 0 || !memtion?.length)
2708
2712
  return [];
@@ -2838,6 +2842,7 @@ const Editor = (props) => {
2838
2842
  memtionTriggerRangeRef.current =
2839
2843
  selectionRef.current?.cloneRange() ?? null;
2840
2844
  pendingMemtionRef.current = true;
2845
+ activeMemtionIndexRef.current = matchedIndex;
2841
2846
  setActiveMemtionIndex(matchedIndex);
2842
2847
  }
2843
2848
  }
@@ -2887,8 +2892,8 @@ const Editor = (props) => {
2887
2892
  setEditorValue(nextValue);
2888
2893
  }
2889
2894
  rememberSelection();
2890
- if (activeMemtionIndex >= 0 && (pendingMemtionRef.current || memtionVisible)) {
2891
- const active = memtion?.[activeMemtionIndex];
2895
+ if (activeMemtionIndexRef.current >= 0 && (pendingMemtionRef.current || memtionVisible)) {
2896
+ const active = memtion?.[activeMemtionIndexRef.current];
2892
2897
  if (!active) {
2893
2898
  hideMemtion();
2894
2899
  return;
@@ -3261,14 +3266,21 @@ Form.useForm = useForm;
3261
3266
  Form.Field = Field;
3262
3267
  Form.useConfig = useConfig;
3263
3268
 
3264
- function Content$1(props) {
3269
+ const Content$1 = (props) => {
3265
3270
  const { title, footer, hideCloseButton, footerLeft, okButtonProps, cancelButtonProps, children, onOk, onClose, } = props;
3266
3271
  const showHeader = title || !hideCloseButton;
3272
+ const [loading, setLoading] = useState(false);
3267
3273
  const handleOk = async () => {
3268
- const ret = await onOk?.();
3269
- if (ret === false)
3270
- return;
3271
- onClose?.();
3274
+ setLoading(true);
3275
+ try {
3276
+ const ret = await onOk?.();
3277
+ if (ret === false)
3278
+ return;
3279
+ onClose?.();
3280
+ }
3281
+ finally {
3282
+ setLoading(false);
3283
+ }
3272
3284
  };
3273
3285
  const renderFooter = useMemo(() => {
3274
3286
  if (footer || footer === null)
@@ -3276,19 +3288,94 @@ function Content$1(props) {
3276
3288
  const propsOk = Object.assign({
3277
3289
  children: "确定",
3278
3290
  onClick: handleOk,
3279
- }, okButtonProps);
3291
+ }, okButtonProps, { loading });
3280
3292
  const propsCancel = Object.assign({
3281
3293
  secondary: true,
3282
3294
  children: "关闭",
3283
3295
  onClick: onClose,
3284
3296
  }, cancelButtonProps);
3285
3297
  return (jsxs(Fragment, { children: [footerLeft, jsx(Button, { ...propsOk }), jsx(Button, { ...propsCancel })] }));
3286
- }, [footer, okButtonProps, cancelButtonProps]);
3298
+ }, [footer, okButtonProps, cancelButtonProps, loading]);
3287
3299
  return (jsxs(Fragment, { children: [showHeader && (jsxs("header", { className: 'i-modal-header', children: [title && jsx("b", { children: title }), jsx(Helpericon, { active: !hideCloseButton, className: 'i-modal-close', onClick: onClose })] })), jsx("div", { className: 'i-modal-content', children: children }), jsx("footer", { className: 'i-modal-footer', children: renderFooter })] }));
3288
- }
3300
+ };
3301
+ var Content$2 = memo(Content$1);
3289
3302
 
3290
3303
  const ModalContext = createContext(false);
3291
3304
 
3305
+ const CONTAINER_ID = "i-modal-container";
3306
+ const BACKDROP_ID = "i-modal-backdrop";
3307
+ let containerEl = null;
3308
+ let backdropEl = null;
3309
+ const stack = [];
3310
+ const listeners = new Set();
3311
+ function ensureContainer() {
3312
+ if (containerEl)
3313
+ return containerEl;
3314
+ containerEl = document.createElement("div");
3315
+ containerEl.id = CONTAINER_ID;
3316
+ containerEl.className = "i-modal-container";
3317
+ document.body.append(containerEl);
3318
+ backdropEl = document.createElement("div");
3319
+ backdropEl.id = BACKDROP_ID;
3320
+ backdropEl.className = "i-modal-backdrop";
3321
+ containerEl.prepend(backdropEl);
3322
+ return containerEl;
3323
+ }
3324
+ function syncBackdrop() {
3325
+ if (!backdropEl)
3326
+ return;
3327
+ const show = stack.some((e) => e.visible && !e.hideBackdrop);
3328
+ backdropEl.classList.toggle("i-modal-backdrop-active", show);
3329
+ }
3330
+ function notify$1() {
3331
+ listeners.forEach((fn) => fn());
3332
+ }
3333
+ function register(entry) {
3334
+ ensureContainer();
3335
+ stack.push(entry);
3336
+ syncBackdrop();
3337
+ notify$1();
3338
+ return () => {
3339
+ const idx = stack.findIndex((e) => e.mid === entry.mid);
3340
+ if (idx !== -1)
3341
+ stack.splice(idx, 1);
3342
+ syncBackdrop();
3343
+ notify$1();
3344
+ if (stack.length === 0) {
3345
+ backdropEl?.remove();
3346
+ backdropEl = null;
3347
+ containerEl?.remove();
3348
+ containerEl = null;
3349
+ }
3350
+ };
3351
+ }
3352
+ function updateVisible(mid, visible) {
3353
+ const entry = stack.find((e) => e.mid === mid);
3354
+ if (entry) {
3355
+ entry.visible = visible;
3356
+ syncBackdrop();
3357
+ notify$1();
3358
+ }
3359
+ }
3360
+ function isTop(mid) {
3361
+ const top = getTopVisible();
3362
+ return top?.mid === mid;
3363
+ }
3364
+ function getTopVisible() {
3365
+ for (let i = stack.length - 1; i >= 0; i--) {
3366
+ if (stack[i].visible)
3367
+ return stack[i];
3368
+ }
3369
+ return undefined;
3370
+ }
3371
+ function getContainer() {
3372
+ return ensureContainer();
3373
+ }
3374
+ function subscribe(fn) {
3375
+ listeners.add(fn);
3376
+ return () => listeners.delete(fn);
3377
+ }
3378
+
3292
3379
  function useModal() {
3293
3380
  const ref = useRef(null);
3294
3381
  const handleOpen = (props) => {
@@ -3316,26 +3403,41 @@ function useModal() {
3316
3403
  };
3317
3404
  }
3318
3405
 
3406
+ let midCounter = 0;
3319
3407
  function Modal(props) {
3320
3408
  const { visible, title, footer, okButtonProps, cancelButtonProps, closable = true, hideBackdrop, backdropClosable = true, hideCloseButton, disableEsc, width, height, customized, fixed, hideShadow, children, style, className, keepDOM, footerLeft, onClick, onVisibleChange, onClose, onOk, ...restProps } = props;
3409
+ const midRef = useRef(`modal-${++midCounter}`);
3410
+ const mid = midRef.current;
3321
3411
  const [show, setShow] = useState(visible);
3322
3412
  const [active, setActive] = useState(false);
3323
3413
  const [bounced, setBounced] = useState(false);
3324
3414
  const [mounted, setMounted] = useState(false);
3415
+ const [top, setTop] = useState(false);
3325
3416
  const toggable = useRef(true);
3326
- const handleShow = async () => {
3417
+ const layerRef = useRef(null);
3418
+ const handleShow = useCallback(() => {
3327
3419
  if (!toggable.current)
3328
3420
  return;
3329
- (!keepDOM || !show) && setShow(true);
3421
+ if (!keepDOM || !show)
3422
+ setShow(true);
3330
3423
  toggable.current = false;
3331
- const timer = setTimeout(() => {
3424
+ updateVisible(mid, true);
3425
+ const raf = requestAnimationFrame(() => {
3426
+ if (!layerRef.current) {
3427
+ requestAnimationFrame(() => {
3428
+ setActive(true);
3429
+ onVisibleChange?.(true);
3430
+ toggable.current = true;
3431
+ });
3432
+ return;
3433
+ }
3332
3434
  setActive(true);
3333
3435
  onVisibleChange?.(true);
3334
3436
  toggable.current = true;
3335
- }, 64);
3336
- return () => clearTimeout(timer);
3337
- };
3338
- const handleHide = () => {
3437
+ });
3438
+ return () => cancelAnimationFrame(raf);
3439
+ }, [keepDOM, show, onVisibleChange]);
3440
+ const handleHide = useCallback(() => {
3339
3441
  if (!toggable.current)
3340
3442
  return;
3341
3443
  toggable.current = false;
@@ -3348,28 +3450,53 @@ function Modal(props) {
3348
3450
  return () => clearTimeout(timer);
3349
3451
  }
3350
3452
  setActive(false);
3453
+ updateVisible(mid, false);
3351
3454
  const timer = setTimeout(() => {
3352
- !keepDOM && setShow(false);
3455
+ if (!keepDOM)
3456
+ setShow(false);
3353
3457
  toggable.current = true;
3354
3458
  onVisibleChange?.(false);
3355
3459
  onClose?.();
3356
3460
  }, 240);
3357
3461
  return () => clearTimeout(timer);
3358
- };
3462
+ }, [closable, keepDOM, onClose, onVisibleChange]);
3359
3463
  const handleBackdropClick = () => {
3360
3464
  backdropClosable && handleHide();
3361
3465
  };
3362
- useKeydown((e) => {
3363
- if (e.code !== "Escape" || !visible)
3364
- return;
3365
- handleHide();
3366
- }, { disabled: disableEsc });
3466
+ useEffect(() => {
3467
+ const unsub = subscribe(() => {
3468
+ setTop(isTop(mid));
3469
+ });
3470
+ return unsub;
3471
+ }, []);
3472
+ useEffect(() => {
3473
+ const unregister = register({
3474
+ mid,
3475
+ visible: !!visible,
3476
+ hideBackdrop: !!hideBackdrop,
3477
+ closable: !!closable,
3478
+ });
3479
+ return unregister;
3480
+ }, []);
3367
3481
  useEffect(() => {
3368
3482
  visible ? handleShow() : handleHide();
3369
3483
  }, [visible]);
3484
+ useEffect(() => {
3485
+ if (!show)
3486
+ return;
3487
+ const raf = requestAnimationFrame(() => {
3488
+ setActive(top);
3489
+ });
3490
+ return () => cancelAnimationFrame(raf);
3491
+ }, [top]);
3370
3492
  useEffect(() => {
3371
3493
  setMounted(true);
3372
3494
  }, []);
3495
+ useKeydown((e) => {
3496
+ if (e.code !== "Escape" || !visible || !top)
3497
+ return;
3498
+ handleHide();
3499
+ }, { disabled: disableEsc });
3373
3500
  const handleClick = () => {
3374
3501
  if (typeof document === "undefined")
3375
3502
  return;
@@ -3377,12 +3504,12 @@ function Modal(props) {
3377
3504
  };
3378
3505
  if (!show || !mounted)
3379
3506
  return null;
3380
- return createPortal(jsx("div", { className: classNames("i-modal-container", {
3381
- "i-modal-backdrop": !hideBackdrop,
3382
- "i-modal-customized": customized,
3507
+ return createPortal(jsx("div", { ref: layerRef, className: classNames("i-modal-layer", {
3383
3508
  "i-modal-active": active,
3509
+ "i-modal-customized": customized,
3510
+ "i-modal-hide-backdrop": hideBackdrop,
3384
3511
  fixed,
3385
- }, className), style: style, onClick: handleBackdropClick, "aria-modal": 'true', inert: !active, children: jsx("div", { className: classNames("i-modal", {
3512
+ }, className), style: style, onClick: handleBackdropClick, children: jsx("div", { className: classNames("i-modal", {
3386
3513
  bounced,
3387
3514
  shadow: !hideShadow,
3388
3515
  }), style: {
@@ -3392,7 +3519,7 @@ function Modal(props) {
3392
3519
  e.stopPropagation();
3393
3520
  handleClick();
3394
3521
  onClick?.(e);
3395
- }, role: 'dialog', "aria-labelledby": title ? "modal-title" : undefined, ...restProps, children: jsxs(ModalContext.Provider, { value: true, children: [customized && children, !customized && (jsx(Content$1, { title: title, hideCloseButton: hideCloseButton, footer: footer, okButtonProps: okButtonProps, cancelButtonProps: cancelButtonProps, children: children, footerLeft: footerLeft, onOk: onOk, onClose: handleHide }))] }) }) }), document?.body ?? null);
3522
+ }, role: "dialog", "aria-modal": top, "data-mid": mid, ...restProps, children: jsxs(ModalContext.Provider, { value: true, children: [customized && children, !customized && (jsx(Content$2, { title: title, hideCloseButton: hideCloseButton, footer: footer, okButtonProps: okButtonProps, cancelButtonProps: cancelButtonProps, children: children, footerLeft: footerLeft, onOk: onOk, onClose: handleHide }))] }) }) }), getContainer());
3396
3523
  }
3397
3524
  Modal.useModal = useModal;
3398
3525
 
@@ -4258,7 +4385,7 @@ const Textarea = (props) => {
4258
4385
  onKeyDown: handleKeydown,
4259
4386
  ...restProps,
4260
4387
  };
4261
- return (jsx(InputContainer, { label: label, labelInline: labelInline, className: className, style: { width, ...style }, tip: message ?? tip, status: status, children: jsx("div", { className: classNames("i-input-item", {
4388
+ return (jsx(InputContainer, { label: label, labelInline: labelInline, className: classNames("i-textarea-label", className), style: { width, ...style }, tip: message ?? tip, status: status, children: jsx("div", { className: classNames("i-input-item", {
4262
4389
  [`i-input-${status}`]: status !== "normal",
4263
4390
  "i-input-borderless": !border,
4264
4391
  }), children: jsx("textarea", { ...inputProps }) }) }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ioca/react",
3
- "version": "1.5.18",
3
+ "version": "1.5.19",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",