@kne/react-modal 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -164,6 +164,49 @@ const useLockParentScroll = (enabled, getScrollElement) => {
164
164
  }, [enabled, getScrollElement]);
165
165
  };
166
166
 
167
+ const usePatchElement = () => {
168
+ const [elements, setElements] = react.useState([]);
169
+ const patchElement = react.useCallback(element => {
170
+ setElements(originElements => [...originElements, element]);
171
+ return () => {
172
+ setElements(originElements => originElements.filter(ele => ele !== element));
173
+ };
174
+ }, []);
175
+ return [elements, patchElement];
176
+ };
177
+
178
+ const ModalLayerContext = /*#__PURE__*/react.createContext(null);
179
+ const useModalLayer = () => react.useContext(ModalLayerContext);
180
+
181
+ /**
182
+ * 挂在 Modal 内容树内(antd ZIndexContext 之下),
183
+ * 使就近 useModal 打开的内层成为本层 React 子树,从而自动叠层。
184
+ */
185
+ const ModalLayerProvider = ({
186
+ children
187
+ }) => {
188
+ const [elements, patchElement] = usePatchElement();
189
+ const value = react.useMemo(() => ({
190
+ patchElement
191
+ }), [patchElement]);
192
+ return /*#__PURE__*/jsxRuntime.jsxs(ModalLayerContext.Provider, {
193
+ value: value,
194
+ children: [children, elements]
195
+ });
196
+ };
197
+
198
+ /** 根级命令式打开(页面首次弹层);不包 ModalLayerContext。 */
199
+ const rootModalPatchRef = {
200
+ current: null
201
+ };
202
+ const ModalContextHolder = () => {
203
+ const [elements, patchElement] = usePatchElement();
204
+ rootModalPatchRef.current = patchElement;
205
+ return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
206
+ children: elements
207
+ });
208
+ };
209
+
167
210
  const ModalLocaleRoot = withLocale(({
168
211
  children
169
212
  }) => children);
@@ -182,10 +225,10 @@ const wrapCustomGetContainer$2 = customGetContainer => {
182
225
  }
183
226
  return () => customGetContainer;
184
227
  };
185
- const VIEWPORT_EXAMPLE_SELECTORS$3 = ['.example-driver-device-scroll'];
228
+ const VIEWPORT_EXAMPLE_SELECTORS$4 = ['.example-driver-device-scroll'];
186
229
  const viewportPopupMountOptions$3 = {
187
230
  cover: 'viewport',
188
- exampleSelectors: VIEWPORT_EXAMPLE_SELECTORS$3
231
+ exampleSelectors: VIEWPORT_EXAMPLE_SELECTORS$4
189
232
  };
190
233
  const resolveModalGetContainer = ({
191
234
  customGetContainer,
@@ -209,6 +252,7 @@ const resolveModalGetContainer = ({
209
252
  };
210
253
  };
211
254
  const sizeStyleVars$1 = (size, hasFooter) => {
255
+ // body 最低高度随 size:small 基准 300,default/large 基准 500(再扣 title/footer chrome)
212
256
  const minBase = size === 'small' ? 300 : 500;
213
257
  const chrome = 48 + (hasFooter ? 58 : 0);
214
258
  const minHeight = `${Math.max(0, minBase - chrome)}px`;
@@ -254,11 +298,15 @@ const ModalOuter = ({
254
298
  });
255
299
  const bodyClassName = classnames__default["default"](modalStyle['modal-body'], 'modal-body');
256
300
  // 移动端全屏:body 用 flex 吃满 title/footer 之间剩余高度,footer 才能贴底;
257
- // 勿写死 --kne-modal-body-height(高度受限类会把该值压矮,footer 悬在中间)
301
+ // 桌面默认:height:auto + max-height,短内容不撑满;bodyScroll=false 仍写死满高供 Tabs/分栏
258
302
  const bodyStyle = isMobile ? {
259
303
  flex: '1 1 auto',
260
304
  minHeight: 0,
261
305
  height: 'auto'
306
+ } : bodyScroll !== false ? {
307
+ height: 'auto',
308
+ maxHeight: 'var(--kne-modal-body-max-height)',
309
+ minHeight: 'var(--kne-modal-body-min-height)'
262
310
  } : {
263
311
  height: 'var(--kne-modal-body-height)'
264
312
  };
@@ -288,10 +336,7 @@ const ModalOuter = ({
288
336
  children: bodyInner
289
337
  }) : /*#__PURE__*/jsxRuntime.jsx("div", {
290
338
  className: classnames__default["default"](bodyClassName, modalStyle['body-scroll-off']),
291
- style: isMobile ? {
292
- flex: '1 1 auto',
293
- minHeight: 0
294
- } : undefined,
339
+ style: bodyStyle,
295
340
  children: bodyInner
296
341
  }), footer === null && footerButtons === undefined ? null : /*#__PURE__*/jsxRuntime.jsx(Footer, {
297
342
  footer: footer,
@@ -419,8 +464,11 @@ const computedCommonProps = ({
419
464
  } : null)
420
465
  }
421
466
  },
467
+ // LayerProvider 须在 antd Modal 内容树内(ZIndexContext 下),内层命令式才能自动 +100
422
468
  children: /*#__PURE__*/jsxRuntime.jsx(ModalLocaleRoot, {
423
- children: runChildren({})
469
+ children: /*#__PURE__*/jsxRuntime.jsx(ModalLayerProvider, {
470
+ children: runChildren({})
471
+ })
424
472
  })
425
473
  };
426
474
  };
@@ -430,18 +478,23 @@ const Modal = withLocale(({
430
478
  open,
431
479
  mobileFullscreen = true,
432
480
  bodyScroll = true,
481
+ isMobile: isMobileProp,
482
+ fixedModeClass: fixedModeClassProp,
433
483
  ...props
434
484
  }) => {
435
485
  const hostRef = react.useRef(null);
436
486
  const {
437
- isMobile,
438
- fixedModeClass,
487
+ isMobile: detectedIsMobile,
488
+ fixedModeClass: detectedFixedModeClass,
439
489
  getPopupContainer,
440
490
  anchorRef
441
491
  } = responsiveUtils.usePopupMount({
442
492
  ...viewportPopupMountOptions$3,
443
493
  getPopupContainer: wrapCustomGetContainer$2(getContainer)
444
494
  });
495
+ // useModal 打开时可能已按 activeElement 解析出示例手机框,优先用调用方传入的 isMobile / fixedModeClass
496
+ const isMobile = isMobileProp ?? detectedIsMobile;
497
+ const fixedModeClass = fixedModeClassProp ?? detectedFixedModeClass;
445
498
  const getScrollElement = responsiveUtils.useScrollElement();
446
499
  useLockParentScroll(!!open, getScrollElement);
447
500
  const setAnchorRef = node => {
@@ -472,84 +525,143 @@ const Modal = withLocale(({
472
525
  })]
473
526
  });
474
527
  });
475
- const useModal = () => {
476
- const {
477
- modal
478
- } = antd.App.useApp();
528
+
529
+ let hookModalUuid = 0;
530
+
531
+ /** 与 Modal / useDrawer 一致:示例手机框挂载根 */
532
+ const VIEWPORT_EXAMPLE_SELECTORS$3 = ['.example-driver-device-scroll'];
533
+ const HookModal = /*#__PURE__*/react.forwardRef(({
534
+ config,
535
+ afterClose: hookAfterClose
536
+ }, ref) => {
537
+ const [open, setOpen] = react.useState(true);
538
+ const [innerConfig, setInnerConfig] = react.useState(config);
539
+ const close = () => {
540
+ setOpen(false);
541
+ };
542
+ react.useImperativeHandle(ref, () => ({
543
+ destroy: close,
544
+ update: newConfig => {
545
+ setInnerConfig(origin => Object.assign({}, origin, typeof newConfig === 'function' ? newConfig(origin) : newConfig));
546
+ }
547
+ }));
479
548
  const {
480
- resolveMount,
481
- getPopupContainer
482
- } = responsiveUtils.usePopupMount(viewportPopupMountOptions$3);
549
+ afterClose: userAfterClose,
550
+ onClose: userOnClose,
551
+ onConfirm: userOnConfirm,
552
+ onCancel: userOnCancel,
553
+ ...restConfig
554
+ } = innerConfig;
555
+ return /*#__PURE__*/jsxRuntime.jsx(Modal, {
556
+ ...restConfig,
557
+ open: open,
558
+ onClose: () => {
559
+ userOnClose?.();
560
+ close();
561
+ },
562
+ onCancel: function () {
563
+ userOnCancel?.(...[].slice.call(arguments));
564
+ close();
565
+ },
566
+ onConfirm: function () {
567
+ try {
568
+ const _arguments = arguments;
569
+ return Promise.resolve(Promise.resolve(userOnConfirm?.(...[].slice.call(_arguments)))).then(function (res) {
570
+ if (res !== false) {
571
+ close();
572
+ }
573
+ return res;
574
+ });
575
+ } catch (e) {
576
+ return Promise.reject(e);
577
+ }
578
+ },
579
+ afterClose: function () {
580
+ var args = [].slice.call(arguments);
581
+ hookAfterClose?.(...args);
582
+ userAfterClose?.(...args);
583
+ }
584
+ });
585
+ });
586
+
587
+ /**
588
+ * 命令式内容弹窗:就近 patch 到 ModalLayerProvider(多层)或根 ModalContextHolder。
589
+ * 不走 App.modal.info / ConfirmDialog,层级交给 antd ZIndexContext。
590
+ *
591
+ * 打开时用 document.activeElement 解析示例手机框(与 useDrawer 一致),
592
+ * 使 useFormModal 在手机预览内也能挂进 `.example-driver-device-scroll`,
593
+ * 而不是落到 Global Holder 所在的外层 body。
594
+ */
595
+ const useModal = () => {
596
+ const layer = react.useContext(ModalLayerContext);
483
597
  const getScrollElement = responsiveUtils.useScrollElement();
484
- return props => {
485
- const anchor = typeof document !== 'undefined' ? document.activeElement : null;
486
- const {
487
- isMobile,
488
- fixedModeClass
489
- } = resolveMount(anchor);
490
- const unlock = lockParentScroll(getScrollElement);
598
+ const [actionQueue, setActionQueue] = react.useState([]);
599
+ react.useEffect(() => {
600
+ if (actionQueue.length) {
601
+ const cloneQueue = [...actionQueue];
602
+ cloneQueue.forEach(action => action());
603
+ setActionQueue([]);
604
+ }
605
+ }, [actionQueue]);
606
+ return react.useCallback(props => {
607
+ hookModalUuid += 1;
608
+ const modalRef = /*#__PURE__*/react.createRef();
491
609
  const api = {};
492
- const {
493
- afterClose: userAfterClose,
494
- getContainer: customGetContainer,
495
- onClose: userOnClose,
496
- onConfirm: userOnConfirm,
497
- onCancel: userOnCancel,
498
- ...restProps
499
- } = props;
500
- const closeModal = () => {
501
- api.close?.();
502
- };
503
- const {
504
- children,
505
- getContainer,
506
- afterClose,
507
- ...otherProps
508
- } = computedCommonProps({
509
- ...restProps,
510
- isMobile,
511
- fixedModeClass,
512
- onClose: () => {
513
- userOnClose?.();
514
- closeModal();
515
- },
516
- onCancel: function () {
517
- userOnCancel?.(...[].slice.call(arguments));
518
- closeModal();
519
- },
520
- onConfirm: function () {
521
- try {
522
- const _arguments = arguments;
523
- return Promise.resolve(Promise.resolve(userOnConfirm?.(...[].slice.call(_arguments)))).then(function (res) {
524
- if (res !== false) {
525
- closeModal();
526
- }
527
- return res;
528
- });
529
- } catch (e) {
530
- return Promise.reject(e);
610
+ let closeFunc;
611
+ let unlock = () => {};
612
+ const resolvePatch = () => layer?.patchElement || rootModalPatchRef.current;
613
+ const runOpen = () => {
614
+ const patch = resolvePatch();
615
+ if (typeof patch !== 'function') {
616
+ throw new Error('useModal requires <ModalContextHolder /> mounted inside <App> before calling useModal()');
617
+ }
618
+ unlock = lockParentScroll(getScrollElement);
619
+ const {
620
+ afterClose: userAfterClose,
621
+ getContainer: customGetContainer,
622
+ ...restProps
623
+ } = props;
624
+
625
+ // Holder 在 Global 外层;用点击焦点定位示例手机框(对齐 useDrawer / FileModal)
626
+ const openAnchor = typeof document !== 'undefined' ? document.activeElement : null;
627
+ const exampleMount = responsiveUtils.findExamplePhoneMountNode(openAnchor, VIEWPORT_EXAMPLE_SELECTORS$3);
628
+ const examplePhoneProps = exampleMount && !customGetContainer ? {
629
+ getContainer: () => exampleMount,
630
+ isMobile: true,
631
+ fixedModeClass: 'kne-is-boundary'
632
+ } : customGetContainer ? {
633
+ getContainer: customGetContainer
634
+ } : {};
635
+ const modalEl = /*#__PURE__*/jsxRuntime.jsx(HookModal, {
636
+ ref: modalRef,
637
+ config: {
638
+ ...restProps,
639
+ ...examplePhoneProps,
640
+ afterClose: function () {
641
+ unlock();
642
+ userAfterClose?.(...[].slice.call(arguments));
643
+ }
644
+ },
645
+ afterClose: () => {
646
+ closeFunc && closeFunc();
531
647
  }
532
- },
533
- afterClose: function () {
534
- unlock();
535
- userAfterClose?.(...[].slice.call(arguments));
648
+ }, `react-modal-${hookModalUuid}`);
649
+ closeFunc = patch(modalEl);
650
+ };
651
+ if (resolvePatch()) {
652
+ runOpen();
653
+ } else {
654
+ setActionQueue(prev => [...prev, runOpen]);
655
+ }
656
+ api.close = () => {
657
+ if (modalRef.current) {
658
+ modalRef.current.destroy();
659
+ } else {
660
+ setActionQueue(prev => [...prev, () => modalRef.current?.destroy()]);
536
661
  }
537
- });
538
- const {
539
- destroy
540
- } = modal.info({
541
- ...otherProps,
542
- afterClose,
543
- content: children,
544
- getContainer: resolveModalGetContainer({
545
- customGetContainer: customGetContainer ?? getContainer,
546
- getPopupContainer,
547
- getHostNode: () => anchor
548
- })
549
- });
550
- api.close = destroy;
662
+ };
551
663
  return api;
552
- };
664
+ }, [getScrollElement, layer]);
553
665
  };
554
666
 
555
667
  var style = {"kne-responsive-boundary":"kne-react-modal_qwqLr","kne-responsive-scroll":"kne-react-modal_vmry7","drawer":"kne-react-modal_cDsbq","small":"kne-react-modal_3QgK-","is-mobile":"kne-react-modal_FFf73","large":"kne-react-modal_8lell","drawer-body-inner":"kne-react-modal_xgX4D","drawer-antd-header":"kne-react-modal_3EUxr","drawer-antd-footer":"kne-react-modal_2G81L","drawer-antd-body":"kne-react-modal_eKVMu","drawer-outer":"kne-react-modal_P3Cw8","no-title":"kne-react-modal_upRot","no-footer":"kne-react-modal_j4sGu","no-padding":"kne-react-modal_pg37M","drawer-title":"kne-react-modal_-hw5V","drawer-body":"kne-react-modal_nJj7Y","body-scroll-off":"kne-react-modal_2Wh1H","modal-footer-extra":"kne-react-modal_M4hy9","drawer-close":"kne-react-modal_dVh38","drawer-host":"kne-react-modal_Nsxi9","drawer-host-render":"kne-react-modal_iGDWP","drawer-panel-host":"kne-react-modal_A0h-2","drawer-panel-body":"kne-react-modal_E4tcc","drawer-form-host":"kne-react-modal_vKkQW","drawer-wrapper-mobile":"kne-react-modal_vsxOs","drawer-wrapper-mobile-vertical":"kne-react-modal_HG8Vz","drawer-root-mobile":"kne-react-modal_G-qXe"};
@@ -892,17 +1004,6 @@ const Drawer = withLocale(({
892
1004
  });
893
1005
  });
894
1006
 
895
- const usePatchElement = () => {
896
- const [elements, setElements] = react.useState([]);
897
- const patchElement = react.useCallback(element => {
898
- setElements(originElements => [...originElements, element]);
899
- return () => {
900
- setElements(originElements => originElements.filter(ele => ele !== element));
901
- };
902
- }, []);
903
- return [elements, patchElement];
904
- };
905
-
906
1007
  const VIEWPORT_EXAMPLE_SELECTORS$1 = ['.example-driver-device-scroll'];
907
1008
 
908
1009
  // 与 Modal / Drawer 一致:真机 viewport;示例框 / container 仍 boundary
@@ -1215,43 +1316,48 @@ const mapConfirmProps = (props, {
1215
1316
  icon,
1216
1317
  isMobile
1217
1318
  });
1319
+
1320
+ // Confirm 走 antd ConfirmDialog:未传 zIndex 时用 static 顶层,适合真确认框盖住内容弹窗
1321
+ const config = {
1322
+ ...otherProps,
1323
+ okText: confirmText ?? otherProps.okText,
1324
+ cancelText: cancelText ?? otherProps.cancelText,
1325
+ onOk: onConfirm ?? otherProps.onOk,
1326
+ onCancel: onCancel ?? otherProps.onCancel,
1327
+ getContainer: resolveModalGetContainer({
1328
+ customGetContainer,
1329
+ getPopupContainer,
1330
+ getHostNode: () => anchor
1331
+ }),
1332
+ centered: true,
1333
+ afterClose: function () {
1334
+ var args = [].slice.call(arguments);
1335
+ unlock();
1336
+ userAfterClose && userAfterClose(...args);
1337
+ afterClose && afterClose(...args);
1338
+ },
1339
+ icon: null,
1340
+ classNames: {
1341
+ mask: classnames__default["default"](isMobile && modalStyle['modal-mask-fullscreen'], isMobile && fixedModeClass, otherProps.classNames?.mask)
1342
+ },
1343
+ wrapClassName: classnames__default["default"](modalStyle['modal-wrap'], modalStyle['modal-wrap-centered'], confirmStyle['confirm-modal-wrap'], wrapClassName, {
1344
+ [confirmStyle['is-danger']]: danger,
1345
+ [confirmStyle['is-mobile']]: isMobile,
1346
+ [fixedModeClass]: isMobile
1347
+ }),
1348
+ title: confirmTitle ? /*#__PURE__*/jsxRuntime.jsx(ConfirmLocaleRoot, {
1349
+ children: confirmTitle
1350
+ }) : null,
1351
+ content: /*#__PURE__*/jsxRuntime.jsx(ConfirmLocaleRoot, {
1352
+ children: content
1353
+ })
1354
+ };
1355
+ if (zIndex !== undefined) {
1356
+ config.zIndex = zIndex;
1357
+ }
1218
1358
  return {
1219
1359
  modalMethod: type,
1220
- config: {
1221
- ...otherProps,
1222
- zIndex: zIndex ?? 1100,
1223
- okText: confirmText ?? otherProps.okText,
1224
- cancelText: cancelText ?? otherProps.cancelText,
1225
- onOk: onConfirm ?? otherProps.onOk,
1226
- onCancel: onCancel ?? otherProps.onCancel,
1227
- getContainer: resolveModalGetContainer({
1228
- customGetContainer,
1229
- getPopupContainer,
1230
- getHostNode: () => anchor
1231
- }),
1232
- centered: true,
1233
- afterClose: function () {
1234
- var args = [].slice.call(arguments);
1235
- unlock();
1236
- userAfterClose && userAfterClose(...args);
1237
- afterClose && afterClose(...args);
1238
- },
1239
- icon: null,
1240
- classNames: {
1241
- mask: classnames__default["default"](isMobile && modalStyle['modal-mask-fullscreen'], isMobile && fixedModeClass, otherProps.classNames?.mask)
1242
- },
1243
- wrapClassName: classnames__default["default"](modalStyle['modal-wrap'], modalStyle['modal-wrap-centered'], confirmStyle['confirm-modal-wrap'], wrapClassName, {
1244
- [confirmStyle['is-danger']]: danger,
1245
- [confirmStyle['is-mobile']]: isMobile,
1246
- [fixedModeClass]: isMobile
1247
- }),
1248
- title: confirmTitle ? /*#__PURE__*/jsxRuntime.jsx(ConfirmLocaleRoot, {
1249
- children: confirmTitle
1250
- }) : null,
1251
- content: /*#__PURE__*/jsxRuntime.jsx(ConfirmLocaleRoot, {
1252
- children: content
1253
- })
1254
- }
1360
+ config
1255
1361
  };
1256
1362
  };
1257
1363
  const useConfirmModal = () => {
@@ -1375,6 +1481,8 @@ const modalClassNames = {
1375
1481
  exports.ColumnsLayout = ColumnsLayout;
1376
1482
  exports.Drawer = Drawer;
1377
1483
  exports.DrawerContextHolder = DrawerContextHolder;
1484
+ exports.ModalContextHolder = ModalContextHolder;
1485
+ exports.ModalLayerContext = ModalLayerContext;
1378
1486
  exports.ScrollRegion = ScrollRegion;
1379
1487
  exports.TabsLayout = TabsLayout;
1380
1488
  exports.createDrawerRender = createDrawerRender;
@@ -1384,4 +1492,5 @@ exports.modalClassNames = modalClassNames;
1384
1492
  exports.useConfirmModal = useConfirmModal;
1385
1493
  exports.useDrawer = useDrawer;
1386
1494
  exports.useModal = useModal;
1495
+ exports.useModalLayer = useModalLayer;
1387
1496
  //# sourceMappingURL=index.js.map