underpost 2.8.843 → 2.8.844

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.
@@ -39,6 +39,7 @@ const logger = loggerFactory(import.meta);
39
39
 
40
40
  const Modal = {
41
41
  Data: {},
42
+
42
43
  Render: async function (
43
44
  options = {
44
45
  id: '',
@@ -86,14 +87,14 @@ const Modal = {
86
87
  onBarUiOpen: {},
87
88
  onBarUiClose: {},
88
89
  onHome: {},
90
+ homeModals: options.homeModals ? options.homeModals : [],
89
91
  query: options.query ? `${window.location.search}` : undefined,
90
92
  };
91
- const setCenterRestore = () => {
92
- const ResponsiveData = Responsive.getResponsiveData();
93
- top = `${ResponsiveData.height / 2 - height / 2}px`;
94
- left = `${ResponsiveData.width / 2 - width / 2}px`;
95
- };
96
- if (idModal !== 'main-body') setCenterRestore();
93
+
94
+ if (idModal !== 'main-body' && options.mode !== 'view') {
95
+ top = `${window.innerHeight / 2 - height / 2}px`;
96
+ left = `${window.innerWidth / 2 - width / 2}px`;
97
+ }
97
98
  if (options && 'mode' in options) {
98
99
  this.Data[idModal][options.mode] = {};
99
100
  switch (options.mode) {
@@ -104,6 +105,7 @@ const Modal = {
104
105
  options.style = {
105
106
  ...options.style,
106
107
  'min-width': `${minWidth}px`,
108
+ width: '100%',
107
109
  };
108
110
 
109
111
  if (this.mobileModal()) {
@@ -434,8 +436,14 @@ const Modal = {
434
436
  rules: [] /*{ type: 'isEmpty' }, { type: 'isEmail' }*/,
435
437
  },
436
438
  ];
437
- let hoverHistBox = false;
438
- let hoverInputBox = false;
439
+ // Reusable hover/focus controller for search history panel
440
+ let unbindDocSearch = null;
441
+ const hoverFocusCtl = EventsUI.HoverFocusController({
442
+ inputSelector: `.top-bar-search-box-container`,
443
+ panelSelector: `.${id}`,
444
+ activeElementId: inputSearchBoxId,
445
+ onDismiss: () => dismissSearchBox(),
446
+ });
439
447
  let currentKeyBoardSearchBoxIndex = 0;
440
448
  let results = [];
441
449
  let historySearchBox = [];
@@ -592,7 +600,7 @@ const Modal = {
592
600
  const isSearchBoxActiveElement = isActiveElement(inputSearchBoxId);
593
601
  checkHistoryBoxTitleStatus();
594
602
  checkShortcutContainerInfoEnabled();
595
- if (!isSearchBoxActiveElement && !hoverHistBox && !hoverInputBox) {
603
+ if (!isSearchBoxActiveElement && !hoverFocusCtl.shouldStay()) {
596
604
  Modal.removeModal(searchBoxHistoryId);
597
605
  return;
598
606
  }
@@ -676,27 +684,19 @@ const Modal = {
676
684
  barMode: options.barMode,
677
685
  });
678
686
 
679
- const titleNode = s(`.title-modal-${id}`).cloneNode(true);
680
- s(`.title-modal-${id}`).remove();
681
- s(`.btn-bar-modal-container-render-${id}`).classList.add('in');
682
- s(`.btn-bar-modal-container-render-${id}`).classList.add('fll');
683
- s(`.btn-bar-modal-container-render-${id}`).appendChild(titleNode);
687
+ // Bind hover/focus and click-outside to dismiss
688
+ hoverFocusCtl.bind();
689
+ unbindDocSearch = EventsUI.bindDismissOnDocumentClick({
690
+ shouldStay: hoverFocusCtl.shouldStay,
691
+ onDismiss: () => dismissSearchBox(),
692
+ anchors: [`.top-bar-search-box-container`, `.${id}`],
693
+ });
694
+ // Ensure cleanup when modal closes
695
+ Modal.Data[id].onCloseListener[`unbind-doc-${id}`] = () => unbindDocSearch && unbindDocSearch();
684
696
 
685
- prepend(`.btn-bar-modal-container-${id}`, html`<div class="hide">${inputInfoNode.outerHTML}</div>`);
697
+ Modal.MoveTitleToBar(id);
686
698
 
687
- s(`.top-bar-search-box-container`).onmouseover = () => {
688
- hoverInputBox = true;
689
- };
690
- s(`.top-bar-search-box-container`).onmouseout = () => {
691
- hoverInputBox = false;
692
- };
693
- s(`.${id}`).onmouseover = () => {
694
- hoverHistBox = true;
695
- };
696
- s(`.${id}`).onmouseout = () => {
697
- hoverHistBox = false;
698
- s(`.${inputSearchBoxId}`).focus();
699
- };
699
+ prepend(`.btn-bar-modal-container-${id}`, html`<div class="hide">${inputInfoNode.outerHTML}</div>`);
700
700
  }
701
701
  };
702
702
 
@@ -708,14 +708,23 @@ const Modal = {
708
708
  searchBoxHistoryOpen();
709
709
  searchBoxCallBack(formDataInfoNode[0]);
710
710
  };
711
- s('.top-bar-search-box').onblur = () => {
712
- if (!hoverHistBox && !hoverInputBox && !isActiveElement(inputSearchBoxId)) {
713
- Modal.removeModal(searchBoxHistoryId);
711
+
712
+ const dismissSearchBox = () => {
713
+ if (unbindDocSearch) {
714
+ try {
715
+ unbindDocSearch();
716
+ } catch (e) {}
714
717
  }
718
+ Modal.removeModal(searchBoxHistoryId);
719
+ };
720
+ s('.top-bar-search-box').onblur = () => {
721
+ hoverFocusCtl.checkDismiss();
715
722
  };
716
723
  EventsUI.onClick(`.top-bar-search-box-container`, () => {
717
724
  searchBoxHistoryOpen();
718
725
  searchBoxCallBack(formDataInfoNode[0]);
726
+ const inputEl = s(`.${inputSearchBoxId}`);
727
+ if (inputEl && inputEl.focus) inputEl.focus();
719
728
  });
720
729
 
721
730
  const timePressDelay = 100;
@@ -897,8 +906,10 @@ const Modal = {
897
906
  barConfig.buttons.menu.disabled = true;
898
907
  barConfig.buttons.close.disabled = true;
899
908
  const id = 'bottom-bar';
900
- if (options && options.homeModals && !options.homeModals.includes(id)) options.homeModals.push(id);
901
- else options.homeModals = [id];
909
+ if (!this.Data[idModal].homeModals) this.Data[idModal].homeModals = [];
910
+ if (!this.Data[idModal].homeModals.includes(id)) {
911
+ this.Data[idModal].homeModals.push(id);
912
+ }
902
913
  const html = async () => html`
903
914
  <style>
904
915
  .top-bar-search-box-container {
@@ -1048,13 +1059,79 @@ const Modal = {
1048
1059
 
1049
1060
  {
1050
1061
  htmls(`.action-btn-lang-render`, html` ${s('html').lang}`);
1051
- EventsUI.onClick(`.action-btn-lang`, () => {
1052
- let lang = 'en';
1053
- if (s('html').lang === 'en') lang = 'es';
1054
- if (s(`.dropdown-option-${lang}`))
1055
- DropDown.Tokens['settings-lang'].onClickEvents[`dropdown-option-${lang}`]();
1056
- else Translate.renderLang(lang);
1057
- });
1062
+ // old method
1063
+ // EventsUI.onClick(`.action-btn-lang`, () => {
1064
+ // let lang = 'en';
1065
+ // if (s('html').lang === 'en') lang = 'es';
1066
+ // if (s(`.dropdown-option-${lang}`))
1067
+ // DropDown.Tokens['settings-lang'].onClickEvents[`dropdown-option-${lang}`]();
1068
+ // else Translate.renderLang(lang);
1069
+ // });
1070
+
1071
+ // Open lightweight empty modal on language button, with shared dismiss logic
1072
+ EventsUI.onClick(
1073
+ `.action-btn-lang`,
1074
+ async () => {
1075
+ const id = 'action-btn-lang-modal';
1076
+ if (s(`.${id}`)) {
1077
+ return s(`.btn-close-${id}`).click();
1078
+ }
1079
+ const { barConfig } = await Themes[Css.currentTheme]();
1080
+ barConfig.buttons.maximize.disabled = true;
1081
+ barConfig.buttons.minimize.disabled = true;
1082
+ barConfig.buttons.restore.disabled = true;
1083
+ barConfig.buttons.menu.disabled = true;
1084
+ barConfig.buttons.close.disabled = false;
1085
+ await Modal.Render({
1086
+ id,
1087
+ barConfig,
1088
+ title: html`${renderViewTitle({
1089
+ icon: html`<i class="fas fa-language mini-title"></i>`,
1090
+ text: Translate.Render('select lang'),
1091
+ })}`,
1092
+ html: () => html``,
1093
+ titleClass: 'mini-title',
1094
+ style: {
1095
+ resize: 'none',
1096
+ 'max-width': '300px',
1097
+ height: '150px !important',
1098
+ 'z-index': 7,
1099
+ },
1100
+ dragDisabled: true,
1101
+ maximize: true,
1102
+ heightBottomBar: 0,
1103
+ heightTopBar: originHeightTopBar,
1104
+ barMode: options.barMode,
1105
+ });
1106
+
1107
+ // Move title inside the bar container to align with control buttons
1108
+ Modal.MoveTitleToBar(id);
1109
+
1110
+ // Position the language selection modal relative to the language button
1111
+ Modal.positionRelativeToAnchor({
1112
+ modalSelector: `.${id}`,
1113
+ anchorSelector: '.action-btn-lang',
1114
+ align: 'right',
1115
+ offset: { x: 0, y: 6 },
1116
+ autoVertical: true,
1117
+ });
1118
+
1119
+ // Hover/focus controller uses the button as input anchor
1120
+ const hoverFocusCtl = EventsUI.HoverFocusController({
1121
+ inputSelector: `.action-btn-lang`,
1122
+ panelSelector: `.${id}`,
1123
+ onDismiss: () => Modal.removeModal(id),
1124
+ });
1125
+ hoverFocusCtl.bind();
1126
+ const unbindDoc = EventsUI.bindDismissOnDocumentClick({
1127
+ shouldStay: hoverFocusCtl.shouldStay,
1128
+ onDismiss: () => Modal.removeModal(id),
1129
+ anchors: [`.action-btn-lang`, `.${id}`],
1130
+ });
1131
+ Modal.Data[id].onCloseListener[`unbind-doc-${id}`] = () => unbindDoc();
1132
+ },
1133
+ { context: 'modal', noGate: true, noLoading: true },
1134
+ );
1058
1135
  }
1059
1136
 
1060
1137
  {
@@ -1348,9 +1425,7 @@ const Modal = {
1348
1425
  this.onHomeRouterEvent = async () => {
1349
1426
  for (const keyModal of Object.keys(this.Data)) {
1350
1427
  if (
1351
- ![idModal, 'main-body-top', 'main-body']
1352
- .concat(options?.homeModals ? options.homeModals : [])
1353
- .includes(keyModal)
1428
+ ![idModal, 'main-body-top', 'main-body'].concat(this.Data[idModal]?.homeModals || []).includes(keyModal)
1354
1429
  )
1355
1430
  s(`.btn-close-${keyModal}`).click();
1356
1431
  backMenuButtonEvent();
@@ -1429,42 +1504,139 @@ const Modal = {
1429
1504
  default:
1430
1505
  break;
1431
1506
  }
1507
+ // Track drag position for consistency
1508
+ let dragPosition = { x: 0, y: 0 };
1509
+
1510
+ // Initialize drag options with proper bounds and smooth transitions
1432
1511
  let dragOptions = {
1433
- // disabled: true,
1434
- handle,
1435
- onDragStart: (data) => {
1436
- if (!s(`.${idModal}`)) return;
1437
- // logger.info('Dragging started', data);
1438
- s(`.${idModal}`).style.transition = null;
1512
+ handle: handle,
1513
+ bounds: {
1514
+ top: 0,
1515
+ left: 0,
1516
+ right: 0,
1517
+ bottom: 0,
1518
+ },
1519
+ preventDefault: true,
1520
+ position: { x: 0, y: 0 },
1521
+ onDragStart: () => {
1522
+ const modal = s(`.${idModal}`);
1523
+ if (!modal) return false; // Prevent drag if modal not found
1524
+
1525
+ // Store current position
1526
+ const computedStyle = window.getComputedStyle(modal);
1527
+ const matrix = new DOMMatrixReadOnly(computedStyle.transform);
1528
+ dragPosition = {
1529
+ x: matrix.m41 || 0,
1530
+ y: matrix.m42 || 0,
1531
+ };
1532
+
1533
+ modal.style.transition = 'none';
1534
+ modal.style.willChange = 'transform';
1535
+ return true; // Allow drag to start
1439
1536
  },
1440
1537
  onDrag: (data) => {
1441
- if (!s(`.${idModal}`)) return;
1442
- // logger.info('Dragging', data);
1538
+ // Update position based on drag delta
1539
+ dragPosition = { x: data.x, y: data.y };
1443
1540
  },
1444
- onDragEnd: (data) => {
1445
- if (!s(`.${idModal}`)) return;
1446
- // logger.info('Dragging stopped', data);
1447
- s(`.${idModal}`).style.transition = transition;
1448
- Object.keys(this.Data[idModal].onDragEndListener).map((keyListener) =>
1449
- this.Data[idModal].onDragEndListener[keyListener](),
1450
- );
1541
+ onDragEnd: () => {
1542
+ const modal = s(`.${idModal}`);
1543
+ if (!modal) return;
1544
+
1545
+ modal.style.willChange = '';
1546
+ modal.style.transition = transition;
1547
+
1548
+ // Update drag instance with current position
1549
+ if (dragInstance) {
1550
+ dragInstance.updateOptions({
1551
+ position: dragPosition,
1552
+ });
1553
+ }
1554
+
1555
+ // Notify listeners
1556
+ Object.keys(this.Data[idModal].onDragEndListener || {}).forEach((keyListener) => {
1557
+ this.Data[idModal].onDragEndListener[keyListener]?.();
1558
+ });
1451
1559
  },
1452
1560
  };
1453
- let dragInstance;
1454
- // new Draggable(s(`.${idModal}`), { disabled: true });
1455
- const setDragInstance = () => (options?.dragDisabled ? null : new Draggable(s(`.${idModal}`), dragOptions));
1561
+
1562
+ let dragInstance = null;
1563
+
1564
+ // Initialize or update drag instance
1565
+ const setDragInstance = () => {
1566
+ if (options?.dragDisabled) {
1567
+ if (dragInstance) {
1568
+ dragInstance.destroy();
1569
+ dragInstance = null;
1570
+ }
1571
+ return null;
1572
+ }
1573
+
1574
+ const modal = s(`.${idModal}`);
1575
+ if (!modal) {
1576
+ console.warn(`Modal element .${idModal} not found for drag initialization`);
1577
+ return null;
1578
+ }
1579
+
1580
+ // Ensure the modal has position: absolute for proper dragging
1581
+ if (window.getComputedStyle(modal).position !== 'absolute') {
1582
+ modal.style.position = 'absolute';
1583
+ }
1584
+
1585
+ // Clean up existing instance
1586
+ if (dragInstance) {
1587
+ dragInstance.destroy();
1588
+ }
1589
+
1590
+ try {
1591
+ // Create new instance with updated options
1592
+ dragInstance = new Draggable(modal, dragOptions);
1593
+ return dragInstance;
1594
+ } catch (error) {
1595
+ console.error('Failed to initialize draggable:', error);
1596
+ return null;
1597
+ }
1598
+ };
1599
+
1600
+ // Expose method to update drag options
1456
1601
  this.Data[idModal].setDragInstance = (updateDragOptions) => {
1457
- dragOptions = {
1458
- ...dragOptions,
1459
- ...updateDragOptions,
1460
- };
1602
+ if (updateDragOptions) {
1603
+ dragOptions = { ...dragOptions, ...updateDragOptions };
1604
+ }
1461
1605
  dragInstance = setDragInstance();
1462
1606
  this.Data[idModal].dragInstance = dragInstance;
1463
1607
  this.Data[idModal].dragOptions = dragOptions;
1464
1608
  };
1465
- s(`.${idModal}`).style.transition = '0.15s';
1466
- setTimeout(() => (s(`.${idModal}`).style.opacity = '1'));
1467
- setTimeout(() => (s(`.${idModal}`).style.transition = transition), 150);
1609
+ // Initialize modal with proper transitions
1610
+ const modal = s(`.${idModal}`);
1611
+ if (modal) {
1612
+ // Initial state
1613
+ modal.style.transition = 'opacity 0.15s ease, transform 0.3s ease';
1614
+ modal.style.opacity = '0';
1615
+
1616
+ // Trigger fade-in after a small delay to allow initial render
1617
+ requestAnimationFrame(() => {
1618
+ if (!modal) return;
1619
+ modal.style.opacity = '1';
1620
+
1621
+ // Set final transition after initial animation completes
1622
+ setTimeout(() => {
1623
+ if (modal) {
1624
+ modal.style.transition = transition;
1625
+
1626
+ // Initialize drag after transitions are set
1627
+ if (!options.dragDisabled) {
1628
+ setDragInstance();
1629
+ if (!options.mode) {
1630
+ dragInstance.updateOptions({
1631
+ position: { x: 0, y: 0 },
1632
+ disabled: false, // Ensure drag is enabled after restore
1633
+ });
1634
+ }
1635
+ }
1636
+ }
1637
+ }, 150);
1638
+ });
1639
+ }
1468
1640
 
1469
1641
  const btnCloseEvent = () => {
1470
1642
  Object.keys(this.Data[idModal].onCloseListener).map((keyListener) =>
@@ -1490,13 +1662,13 @@ const Modal = {
1490
1662
  for (const subIdModal of Object.keys(this.Data).reverse()) {
1491
1663
  if (this.Data[subIdModal].options.route) {
1492
1664
  newPath = `${newPath}${this.Data[subIdModal].options.route}`;
1493
- // console.warn('SET MODAL URI', newPath);
1665
+ console.warn('------------> SET MODAL URI', newPath);
1494
1666
  setPath(newPath);
1495
1667
  this.setTopModalCallback(subIdModal);
1496
1668
  return setDocTitle({ ...options.RouterInstance, route: this.Data[subIdModal].options.route });
1497
1669
  }
1498
1670
  }
1499
- // console.warn('SET MODAL URI', newPath);
1671
+ console.warn('-------------> SET MODAL URI', newPath);
1500
1672
  setPath(`${newPath}${Modal.homeCid ? `?cid=${Modal.homeCid}` : ''}`);
1501
1673
  return setDocTitle({ ...options.RouterInstance, route: '' });
1502
1674
  }
@@ -1505,37 +1677,110 @@ const Modal = {
1505
1677
  };
1506
1678
  s(`.btn-close-${idModal}`).onclick = btnCloseEvent;
1507
1679
 
1680
+ // Minimize button handler
1508
1681
  s(`.btn-minimize-${idModal}`).onclick = () => {
1509
- if (options.slideMenu) delete this.Data[idModal].slideMenu;
1510
- s(`.${idModal}`).style.transition = '0.3s';
1682
+ const modal = s(`.${idModal}`);
1683
+ if (!modal) return;
1684
+
1685
+ if (options.slideMenu) {
1686
+ delete this.Data[idModal].slideMenu;
1687
+ }
1688
+
1689
+ // Keep drag enabled when minimized
1690
+ if (dragInstance) {
1691
+ dragInstance.updateOptions({ disabled: false });
1692
+ }
1693
+
1694
+ // Set up transition
1695
+ modal.style.transition = 'height 0.3s ease, transform 0.3s ease';
1696
+
1697
+ // Update button states
1511
1698
  s(`.btn-minimize-${idModal}`).style.display = 'none';
1512
1699
  s(`.btn-maximize-${idModal}`).style.display = null;
1513
1700
  s(`.btn-restore-${idModal}`).style.display = null;
1514
- s(`.${idModal}`).style.height = `${s(`.bar-default-modal-${idModal}`).clientHeight}px`;
1515
- setTimeout(() => (s(`.${idModal}`).style.transition = transition), 300);
1701
+
1702
+ // Collapse to header height
1703
+ const header = s(`.bar-default-modal-${idModal}`);
1704
+ if (header) {
1705
+ modal.style.height = `${header.clientHeight}px`;
1706
+ modal.style.overflow = 'hidden';
1707
+ }
1708
+
1709
+ // Restore transition after animation
1710
+ setTimeout(() => {
1711
+ if (modal) {
1712
+ modal.style.transition = transition;
1713
+ }
1714
+ }, 300);
1516
1715
  };
1716
+ // Restore button handler
1517
1717
  s(`.btn-restore-${idModal}`).onclick = () => {
1518
- if (options.slideMenu) delete this.Data[idModal].slideMenu;
1519
- s(`.${idModal}`).style.transition = '0.3s';
1718
+ const modal = s(`.${idModal}`);
1719
+ if (!modal) return;
1720
+
1721
+ if (options.slideMenu) {
1722
+ delete this.Data[idModal].slideMenu;
1723
+ }
1724
+
1725
+ // Re-enable dragging
1726
+ if (dragInstance) {
1727
+ dragInstance.updateOptions({ disabled: false });
1728
+ }
1729
+
1730
+ // Set up transition
1731
+ modal.style.transition = 'all 0.3s ease';
1732
+
1733
+ // Update button states
1520
1734
  s(`.btn-restore-${idModal}`).style.display = 'none';
1521
1735
  s(`.btn-minimize-${idModal}`).style.display = null;
1522
1736
  s(`.btn-maximize-${idModal}`).style.display = null;
1523
- s(`.${idModal}`).style.transform = null;
1524
- s(`.${idModal}`).style.height = null;
1525
- s(`.${idModal}`).style.width = null;
1526
- setCenterRestore();
1527
- s(`.${idModal}`).style.top = top;
1528
- s(`.${idModal}`).style.left = left;
1529
- dragInstance = setDragInstance();
1530
- setTimeout(() => (s(`.${idModal}`).style.transition = transition), 300);
1737
+
1738
+ // Restore original dimensions and position
1739
+ modal.style.transform = '';
1740
+ modal.style.height = '';
1741
+ left = 0;
1742
+ width = 300;
1743
+ modal.style.left = `${left}px`;
1744
+ modal.style.width = `${width}px`;
1745
+ modal.style.overflow = '';
1746
+
1747
+ // Reset drag position
1748
+ dragPosition = { x: 0, y: 0 };
1749
+
1750
+ // Set new position
1751
+ modal.style.transform = `translate(0, 0)`;
1752
+
1753
+ // Adjust top position based on top bar visibility
1754
+ const heightDefaultTopBar = 40; // Default top bar height if not specified
1755
+ s(`.${idModal}`).style.top = s(`.main-body-btn-ui-close`).classList.contains('hide')
1756
+ ? `0px`
1757
+ : `${options.heightTopBar ? options.heightTopBar : heightDefaultTopBar}px`;
1758
+
1759
+ // Re-enable drag after restore
1760
+ if (dragInstance) {
1761
+ dragInstance.updateOptions({
1762
+ position: { x: 0, y: 0 },
1763
+ disabled: false, // Ensure drag is enabled after restore
1764
+ });
1765
+ }
1766
+ setTimeout(() => (s(`.${idModal}`) ? (s(`.${idModal}`).style.transition = transition) : null), 300);
1531
1767
  };
1532
1768
  s(`.btn-maximize-${idModal}`).onclick = () => {
1533
- s(`.${idModal}`).style.transition = '0.3s';
1534
- setTimeout(() => (s(`.${idModal}`).style.transition = transition), 300);
1769
+ const modal = s(`.${idModal}`);
1770
+ if (!modal) return;
1771
+
1772
+ // Disable drag when maximizing
1773
+ if (dragInstance) {
1774
+ dragInstance.updateOptions({ disabled: true });
1775
+ }
1776
+
1777
+ modal.style.transition = '0.3s';
1778
+ setTimeout(() => (modal ? (modal.style.transition = transition) : null), 300);
1779
+
1535
1780
  s(`.btn-maximize-${idModal}`).style.display = 'none';
1536
1781
  s(`.btn-restore-${idModal}`).style.display = null;
1537
1782
  s(`.btn-minimize-${idModal}`).style.display = null;
1538
- s(`.${idModal}`).style.transform = null;
1783
+ modal.style.transform = null;
1539
1784
 
1540
1785
  if (options.slideMenu) {
1541
1786
  const idSlide = this.Data[options.slideMenu]['slide-menu']
@@ -1770,6 +2015,105 @@ const Modal = {
1770
2015
  };
1771
2016
  });
1772
2017
  },
2018
+ // Move modal title element into the bar's render container so it aligns with control buttons
2019
+ /**
2020
+ * Position a modal relative to an anchor element
2021
+ * @param {Object} options - Positioning options
2022
+ * @param {string} options.modalSelector - CSS selector for the modal element
2023
+ * @param {string} options.anchorSelector - CSS selector for the anchor element
2024
+ * @param {Object} [options.offset={x: 0, y: 6}] - Offset from anchor
2025
+ * @param {string} [options.align='right'] - Horizontal alignment ('left' or 'right')
2026
+ * @param {boolean} [options.autoVertical=true] - Whether to automatically determine vertical position
2027
+ * @param {boolean} [options.placeAbove] - Force position above/below anchor (overrides autoVertical)
2028
+ */
2029
+ positionRelativeToAnchor({
2030
+ modalSelector,
2031
+ anchorSelector,
2032
+ offset = { x: 0, y: 6 },
2033
+ align = 'right',
2034
+ autoVertical = true,
2035
+ placeAbove,
2036
+ }) {
2037
+ try {
2038
+ const modal = s(modalSelector);
2039
+ const anchor = s(anchorSelector);
2040
+
2041
+ if (!modal || !anchor || !anchor.getBoundingClientRect) return;
2042
+
2043
+ // First, position the modal near its final position but off-screen
2044
+ const arect = anchor.getBoundingClientRect();
2045
+ const vh = window.innerHeight;
2046
+ const vw = window.innerWidth;
2047
+ const safeMargin = 6;
2048
+
2049
+ // Determine vertical position
2050
+ let finalPlaceAbove = placeAbove;
2051
+ if (autoVertical && placeAbove === undefined) {
2052
+ const inBottomBar = anchor.closest && anchor.closest('.bottom-bar');
2053
+ const inTopBar = anchor.closest && anchor.closest('.slide-menu-top-bar');
2054
+
2055
+ if (inBottomBar) finalPlaceAbove = true;
2056
+ else if (inTopBar) finalPlaceAbove = false;
2057
+ else finalPlaceAbove = arect.top > vh / 2; // heuristic fallback
2058
+ }
2059
+
2060
+ // Set initial position (slightly offset from final position)
2061
+ const initialOffset = finalPlaceAbove ? 20 : -20;
2062
+ modal.style.position = 'fixed';
2063
+ modal.style.opacity = '0';
2064
+ modal.style.transition = 'opacity 150ms ease-out, transform 150ms ease-out';
2065
+
2066
+ // Position near the anchor but slightly offset
2067
+ modal.style.top = `${finalPlaceAbove ? arect.top - 40 : arect.bottom + 20}px`;
2068
+ modal.style.left = `${align === 'right' ? arect.right - 200 : arect.left}px`;
2069
+ modal.style.transform = 'translateY(0)';
2070
+
2071
+ // Force reflow to ensure initial styles are applied
2072
+ modal.offsetHeight;
2073
+
2074
+ // Now calculate final position
2075
+ const mrect = modal.getBoundingClientRect();
2076
+
2077
+ // Calculate final top position
2078
+ const top = finalPlaceAbove ? arect.top - mrect.height - offset.y : arect.bottom + offset.y;
2079
+
2080
+ // Calculate final left position based on alignment
2081
+ let left;
2082
+ if (align === 'right') {
2083
+ left = arect.right - mrect.width - offset.x; // align right edges
2084
+ } else {
2085
+ left = arect.left + offset.x; // align left edges
2086
+ }
2087
+
2088
+ // Ensure modal stays within viewport bounds
2089
+ left = Math.max(safeMargin, Math.min(left, vw - mrect.width - safeMargin));
2090
+ const finalTop = Math.max(safeMargin, Math.min(top, vh - mrect.height - safeMargin));
2091
+
2092
+ // Apply final position with smooth transition
2093
+ requestAnimationFrame(() => {
2094
+ modal.style.top = `${Math.round(finalTop)}px`;
2095
+ modal.style.left = `${Math.round(left)}px`;
2096
+ modal.style.opacity = '1';
2097
+ });
2098
+ } catch (e) {
2099
+ console.error('Error positioning modal:', e);
2100
+ }
2101
+ },
2102
+
2103
+ MoveTitleToBar(idModal) {
2104
+ try {
2105
+ const titleEl = s(`.title-modal-${idModal}`);
2106
+ const container = s(`.btn-bar-modal-container-render-${idModal}`);
2107
+ if (!titleEl || !container) return;
2108
+ const titleNode = titleEl.cloneNode(true);
2109
+ titleEl.remove();
2110
+ container.classList.add('in');
2111
+ container.classList.add('fll');
2112
+ container.appendChild(titleNode);
2113
+ } catch (e) {
2114
+ // non-fatal: keep default placement if structure not present
2115
+ }
2116
+ },
1773
2117
  headerTitleHeight: 40,
1774
2118
  actionBtnCenter: function () {
1775
2119
  if (!s(`.btn-close-modal-menu`).classList.contains('hide')) {
@@ -15,10 +15,10 @@ const NotificationManager = {
15
15
  right: 5px !important;
16
16
  width: 300px !important;
17
17
  bottom: ${5 + (options?.heightBottomBar ? options.heightBottomBar : 0)}px !important;
18
- z-index: 5 !important;
18
+ z-index: 11 !important;
19
19
  }
20
20
  .notification-board-title {
21
- padding: 5px !important;
21
+ padding: 11px !important;
22
22
  }
23
23
  .notification-manager-date {
24
24
  font-size: 20px !important;