@appius-fr/apx 2.5.0 → 2.5.2

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.
@@ -1612,7 +1612,6 @@ __webpack_require__.r(__webpack_exports__);
1612
1612
  /* harmony export */ toast: () => (/* binding */ toast)
1613
1613
  /* harmony export */ });
1614
1614
  /* harmony import */ var _css_toast_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./css/toast.css */ "./modules/toast/css/toast.css");
1615
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
1616
1615
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
1617
1616
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
1618
1617
  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
@@ -1627,13 +1626,30 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+
1627
1626
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
1628
1627
  function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
1629
1628
  function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
1629
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
1630
1630
  // Minimal, framework-agnostic ToastManager for APX
1631
1631
  // ESM-first, no side effects on import. DOM only when used.
1632
1632
 
1633
1633
 
1634
+ /**
1635
+ * @typedef {Object} PositionObject
1636
+ * @property {'sticky'|'relative'|'anchored'} [type]
1637
+ * @property {string} [x]
1638
+ * @property {string} [y]
1639
+ * @property {HTMLElement} [element]
1640
+ * @property {'top'|'right'|'bottom'|'left'|'above'|'below'|'before'|'after'} [placement]
1641
+ * @property {string} [gap]
1642
+ * @property {boolean} [useNativeCSS]
1643
+ */
1644
+
1645
+ /**
1646
+ * @typedef {string|PositionObject} Position
1647
+ */
1648
+
1634
1649
  /**
1635
1650
  * @typedef {Object} ToastConfig
1636
- * @property {'bottom-right'|'bottom-left'|'top-right'|'top-left'} [position]
1651
+ * @property {Position} [position]
1652
+ * @property {'up'|'down'|'auto'} [flow] Flow direction for stacking toasts. 'auto' determines based on position. Default: 'auto'
1637
1653
  * @property {number} [maxToasts]
1638
1654
  * @property {number} [defaultDurationMs]
1639
1655
  * @property {number} [zIndex]
@@ -1642,6 +1658,7 @@ function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input ==
1642
1658
  * @property {boolean} [dedupe]
1643
1659
  * @property {string} [containerClass]
1644
1660
  * @property {number} [offset]
1661
+ * @property {string} [id]
1645
1662
  */
1646
1663
 
1647
1664
  /**
@@ -1654,6 +1671,8 @@ function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input ==
1654
1671
  * @property {(ref: ToastRef, ev: MouseEvent) => void} [onClick]
1655
1672
  * @property {(ref: ToastRef, reason: 'timeout'|'close'|'api'|'overflow') => void} [onClose]
1656
1673
  * @property {string} [className]
1674
+ * @property {Position} [position]
1675
+ * @property {'up'|'down'|'auto'} [flow] Flow direction for stacking toasts. 'auto' determines based on position. Default: 'auto'
1657
1676
  */
1658
1677
 
1659
1678
  /**
@@ -1668,6 +1687,40 @@ function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input ==
1668
1687
  */
1669
1688
 
1670
1689
  var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
1690
+
1691
+ // Shared container cache: Map<position, HTMLElement>
1692
+ // Containers are shared between managers with the same position
1693
+ var _containerCache = new Map();
1694
+
1695
+ // Garbage collection: cleanup empty unmanaged containers after a delay
1696
+ var GC_DELAY_MS = 20000; // 20 seconds
1697
+ var _gcTimeoutId = null;
1698
+
1699
+ // Wrapper for all toast containers (keeps DOM clean)
1700
+ var _toastWrapper = null;
1701
+
1702
+ /**
1703
+ * Get or create the toast containers wrapper
1704
+ * @returns {HTMLElement|null}
1705
+ */
1706
+ function getToastWrapper() {
1707
+ if (!isBrowser) return null;
1708
+ if (!_toastWrapper) {
1709
+ _toastWrapper = document.querySelector('.APX-toast-wrapper');
1710
+ if (!_toastWrapper) {
1711
+ _toastWrapper = createEl('div', 'APX-toast-wrapper');
1712
+ _toastWrapper.style.position = 'fixed';
1713
+ _toastWrapper.style.top = '0';
1714
+ _toastWrapper.style.left = '0';
1715
+ _toastWrapper.style.width = '0';
1716
+ _toastWrapper.style.height = '0';
1717
+ _toastWrapper.style.pointerEvents = 'none';
1718
+ _toastWrapper.style.zIndex = '10000'; // Below containers but above most content
1719
+ document.body.appendChild(_toastWrapper);
1720
+ }
1721
+ }
1722
+ return _toastWrapper;
1723
+ }
1671
1724
  var DEFAULT_CONFIG = {
1672
1725
  position: 'bottom-right',
1673
1726
  maxToasts: 5,
@@ -1693,6 +1746,194 @@ function createEl(tag, classNames) {
1693
1746
  return el;
1694
1747
  }
1695
1748
 
1749
+ /**
1750
+ * Normalize placement synonyms to CSS values
1751
+ * @param {string} placement
1752
+ * @returns {'top'|'right'|'bottom'|'left'}
1753
+ */
1754
+ function normalizePlacement(placement) {
1755
+ var synonyms = {
1756
+ 'above': 'top',
1757
+ 'below': 'bottom',
1758
+ 'before': 'left',
1759
+ 'after': 'right'
1760
+ };
1761
+ return synonyms[placement] || placement;
1762
+ }
1763
+
1764
+ /**
1765
+ * Determine flow direction based on position
1766
+ * @param {Position} position
1767
+ * @returns {'up'|'down'}
1768
+ */
1769
+ function determineFlow(position) {
1770
+ if (typeof position === 'string') {
1771
+ // String positions: top = up, bottom = down
1772
+ if (position.includes('top')) return 'up';
1773
+ if (position.includes('bottom')) return 'down';
1774
+ return 'down'; // default
1775
+ }
1776
+
1777
+ if (_typeof(position) === 'object' && position !== null) {
1778
+ var type = position.type || (position.x || position.y ? 'sticky' : null);
1779
+ if (type === 'sticky' || !type && (position.x || position.y)) {
1780
+ // Sticky: determine based on y coordinate
1781
+ if (position.y !== undefined) {
1782
+ // If y starts with '-' or is a small value, likely top (up)
1783
+ // If y is a large value or percentage, likely bottom (down)
1784
+ if (position.y.startsWith('-')) {
1785
+ // Negative = from bottom, so flow down
1786
+ return 'down';
1787
+ }
1788
+ var num = parseFloat(position.y);
1789
+ if (!isNaN(num)) {
1790
+ // If y < 50% of viewport height, likely top (up)
1791
+ // Otherwise likely bottom (down)
1792
+ if (position.y.includes('%')) {
1793
+ return num < 50 ? 'up' : 'down';
1794
+ }
1795
+ // For px values, assume < 400px is top (up)
1796
+ return num < 400 ? 'up' : 'down';
1797
+ }
1798
+ }
1799
+ return 'down'; // default
1800
+ }
1801
+
1802
+ if (type === 'anchored' && position.placement) {
1803
+ // Anchored: placement determines flow
1804
+ var placement = normalizePlacement(position.placement);
1805
+ if (placement === 'top' || placement === 'above') return 'up';
1806
+ if (placement === 'bottom' || placement === 'below') return 'down';
1807
+ // For left/right, default to down
1808
+ return 'down';
1809
+ }
1810
+ if (type === 'relative') {
1811
+ // Relative: determine based on y offset
1812
+ if (position.y !== undefined) {
1813
+ var _num = parseFloat(position.y);
1814
+ if (!isNaN(_num)) {
1815
+ // Negative y = above element = flow up
1816
+ // Positive y = below element = flow down
1817
+ return _num < 0 ? 'up' : 'down';
1818
+ }
1819
+ }
1820
+ return 'down'; // default
1821
+ }
1822
+ }
1823
+
1824
+ return 'down'; // default
1825
+ }
1826
+
1827
+ /**
1828
+ * Garbage collection: remove empty unmanaged containers
1829
+ */
1830
+ function cleanupEmptyContainers() {
1831
+ if (!isBrowser) return;
1832
+ var containers = document.querySelectorAll('.APX-toast-container:not([data-apx-toast-managed="true"])');
1833
+ containers.forEach(function (container) {
1834
+ // Check if container is empty (no toasts)
1835
+ if (container.children.length === 0) {
1836
+ var positionKey = container.getAttribute('data-apx-toast-position');
1837
+ if (positionKey) {
1838
+ _containerCache["delete"](positionKey);
1839
+ }
1840
+ container.remove();
1841
+ }
1842
+ });
1843
+
1844
+ // Clean up wrapper if it's empty (all containers removed)
1845
+ if (_toastWrapper && _toastWrapper.children.length === 0) {
1846
+ _toastWrapper.remove();
1847
+ _toastWrapper = null;
1848
+ }
1849
+ _gcTimeoutId = null;
1850
+ }
1851
+
1852
+ /**
1853
+ * Schedule garbage collection for empty unmanaged containers
1854
+ */
1855
+ function scheduleGarbageCollection() {
1856
+ if (_gcTimeoutId) {
1857
+ clearTimeout(_gcTimeoutId);
1858
+ }
1859
+ _gcTimeoutId = setTimeout(cleanupEmptyContainers, GC_DELAY_MS);
1860
+ }
1861
+
1862
+ /**
1863
+ * Find all scrollable parent elements
1864
+ * @param {HTMLElement} element
1865
+ * @returns {HTMLElement[]}
1866
+ */
1867
+ function findScrollableParents(element) {
1868
+ var scrollables = [];
1869
+ var current = element.parentElement;
1870
+ while (current && current !== document.body && current !== document.documentElement) {
1871
+ var style = window.getComputedStyle(current);
1872
+ var overflow = style.overflow + style.overflowY + style.overflowX;
1873
+ if (overflow.includes('scroll') || overflow.includes('auto')) {
1874
+ scrollables.push(current);
1875
+ }
1876
+ current = current.parentElement;
1877
+ }
1878
+ return scrollables;
1879
+ }
1880
+
1881
+ /**
1882
+ * Hash an element to create a unique identifier
1883
+ * @param {HTMLElement} el
1884
+ * @returns {string}
1885
+ */
1886
+ function hashElement(el) {
1887
+ var rect = el.getBoundingClientRect();
1888
+ var str = "".concat(el.tagName, "_").concat(rect.left, "_").concat(rect.top, "_").concat(rect.width, "_").concat(rect.height);
1889
+ var hash = 0;
1890
+ for (var i = 0; i < str.length; i++) {
1891
+ var _char = str.charCodeAt(i);
1892
+ hash = (hash << 5) - hash + _char;
1893
+ hash = hash & hash; // Convert to 32bit integer
1894
+ }
1895
+
1896
+ return Math.abs(hash).toString(36);
1897
+ }
1898
+
1899
+ /**
1900
+ * Serialize position options into a unique key
1901
+ * @param {Position} position
1902
+ * @returns {string}
1903
+ */
1904
+ function serializePosition(position) {
1905
+ if (typeof position === 'string') {
1906
+ return "s:".concat(position);
1907
+ }
1908
+ if (_typeof(position) === 'object' && position !== null) {
1909
+ var parts = [];
1910
+
1911
+ // Type (default: sticky if x/y provided)
1912
+ var type = position.type || (position.x || position.y ? 'sticky' : null);
1913
+ if (type) parts.push("t:".concat(type));
1914
+
1915
+ // Coordinates
1916
+ if (position.x !== undefined) parts.push("x:".concat(position.x));
1917
+ if (position.y !== undefined) parts.push("y:".concat(position.y));
1918
+
1919
+ // For relative/anchored: use element ID or hash
1920
+ if (position.element) {
1921
+ var _position$element$dat;
1922
+ var elementId = position.element.id || ((_position$element$dat = position.element.dataset) === null || _position$element$dat === void 0 ? void 0 : _position$element$dat.apxToastAnchorId) || "el_".concat(hashElement(position.element));
1923
+ parts.push("el:".concat(elementId));
1924
+ }
1925
+ if (position.placement) {
1926
+ // Normalize placement for serialization (use CSS values)
1927
+ var normalized = normalizePlacement(position.placement);
1928
+ parts.push("p:".concat(normalized));
1929
+ }
1930
+ if (position.gap !== undefined) parts.push("g:".concat(position.gap));
1931
+ if (position.useNativeCSS) parts.push("native:true");
1932
+ return "o:".concat(parts.join('|'));
1933
+ }
1934
+ return 's:bottom-right';
1935
+ }
1936
+
1696
1937
  /**
1697
1938
  * ToastManager class
1698
1939
  */
@@ -1747,9 +1988,17 @@ var ToastManager = /*#__PURE__*/function () {
1747
1988
  _ref.update(options);
1748
1989
  return _ref;
1749
1990
  }
1991
+
1992
+ // Determine position and flow for this toast (options take precedence over config)
1993
+ var position = options.position || this.config.position || 'bottom-right';
1994
+ var flow = options.flow !== undefined ? options.flow : this.config.flow !== undefined ? this.config.flow : 'auto';
1995
+ var finalFlow = flow === 'auto' ? determineFlow(position) : flow;
1996
+
1997
+ // Ensure default container is set (for backward compatibility)
1750
1998
  this.ensureContainer();
1751
1999
  var toastEl = createEl('div', "APX-toast APX-toast--".concat(options.type));
1752
2000
  toastEl.setAttribute('role', 'status');
2001
+ // Priority: options.id > config.id > auto-generated
1753
2002
  var toastId = options.id || "t_".concat(Date.now(), "_").concat(Math.random().toString(36).slice(2, 8));
1754
2003
  toastEl.dataset.toastId = toastId;
1755
2004
  if (options.className) toastEl.className += " ".concat(options.className);
@@ -1767,7 +2016,172 @@ var ToastManager = /*#__PURE__*/function () {
1767
2016
  closeBtn.type = 'button';
1768
2017
  toastEl.appendChild(closeBtn);
1769
2018
  }
1770
- this.container.appendChild(toastEl);
2019
+
2020
+ // Get or create container for this specific position
2021
+ var container = null;
2022
+ var positionUpdateFn = null;
2023
+ var positionCleanup = null;
2024
+ var originalElementStyle = null;
2025
+ if (position && _typeof(position) === 'object' && position !== null) {
2026
+ var type = position.type || (position.x || position.y ? 'sticky' : null);
2027
+ if (position.useNativeCSS && (type === 'relative' || type === 'anchored') && position.element) {
2028
+ // useNativeCSS: true - create container in target element using native CSS
2029
+ var targetEl = position.element;
2030
+ originalElementStyle = targetEl.style.position;
2031
+ targetEl.style.position = 'relative';
2032
+
2033
+ // Create a container for stacking toasts (reuse if exists)
2034
+ var positionKey = serializePosition(position);
2035
+ var nativeContainer = targetEl.querySelector("[data-apx-toast-position=\"".concat(positionKey, "\"]"));
2036
+ if (!nativeContainer) {
2037
+ nativeContainer = createEl('div', 'APX-toast-container APX-toast-container-native');
2038
+ nativeContainer.setAttribute('data-apx-toast-position', positionKey);
2039
+ nativeContainer.style.position = 'absolute';
2040
+ nativeContainer.style.zIndex = String(this.config.zIndex);
2041
+ nativeContainer.style.gap = "".concat(this.config.gap, "px");
2042
+ nativeContainer.setAttribute('aria-live', String(this.config.ariaLive));
2043
+ nativeContainer.style.flexDirection = finalFlow === 'up' ? 'column-reverse' : 'column';
2044
+
2045
+ // Apply positioning to container
2046
+ if (type === 'relative') {
2047
+ // Relative: use x/y directly
2048
+ if (position.x !== undefined) {
2049
+ if (position.x.startsWith('-')) {
2050
+ nativeContainer.style.right = position.x.substring(1);
2051
+ } else {
2052
+ nativeContainer.style.left = position.x;
2053
+ }
2054
+ }
2055
+ if (position.y !== undefined) {
2056
+ if (position.y.startsWith('-')) {
2057
+ nativeContainer.style.bottom = position.y.substring(1);
2058
+ } else {
2059
+ nativeContainer.style.top = position.y;
2060
+ }
2061
+ }
2062
+ } else if (type === 'anchored') {
2063
+ // Anchored: position outside the element using 100% (element size) + gap
2064
+ var placement = normalizePlacement(position.placement);
2065
+ var gap = position.gap || '1em';
2066
+ switch (placement) {
2067
+ case 'top':
2068
+ nativeContainer.style.bottom = "calc(100% + ".concat(gap, ")");
2069
+ nativeContainer.style.left = '0';
2070
+ break;
2071
+ case 'bottom':
2072
+ nativeContainer.style.top = "calc(100% + ".concat(gap, ")");
2073
+ nativeContainer.style.left = '0';
2074
+ break;
2075
+ case 'left':
2076
+ nativeContainer.style.right = "calc(100% + ".concat(gap, ")");
2077
+ nativeContainer.style.top = '0';
2078
+ break;
2079
+ case 'right':
2080
+ nativeContainer.style.left = "calc(100% + ".concat(gap, ")");
2081
+ nativeContainer.style.top = '0';
2082
+ break;
2083
+ }
2084
+ }
2085
+ targetEl.appendChild(nativeContainer);
2086
+ } else {
2087
+ // Update flow direction if container exists
2088
+ nativeContainer.style.flexDirection = finalFlow === 'up' ? 'column-reverse' : 'column';
2089
+ }
2090
+ container = nativeContainer;
2091
+ positionCleanup = function positionCleanup() {
2092
+ if (targetEl && targetEl.parentElement) {
2093
+ targetEl.style.position = originalElementStyle || '';
2094
+ // Remove native container if empty
2095
+ if (nativeContainer && nativeContainer.children.length === 0) {
2096
+ var _positionKey = nativeContainer.getAttribute('data-apx-toast-position');
2097
+ if (_positionKey) {
2098
+ _containerCache["delete"](_positionKey);
2099
+ }
2100
+ nativeContainer.remove();
2101
+ }
2102
+ }
2103
+ };
2104
+ } else {
2105
+ // Default: get or create container for this position
2106
+ container = this.getContainerForPosition(position, finalFlow);
2107
+ var updateContainerPosition = function updateContainerPosition() {
2108
+ var styles = _this.calculatePosition(position, container);
2109
+ Object.assign(container.style, styles);
2110
+ };
2111
+ updateContainerPosition();
2112
+
2113
+ // For relative/anchored, listen to scroll/resize
2114
+ if ((type === 'relative' || type === 'anchored') && position.element) {
2115
+ var rafId = null;
2116
+ var lastUpdate = 0;
2117
+ var throttleMs = 16; // ~60fps
2118
+
2119
+ var throttledUpdate = function throttledUpdate() {
2120
+ var now = Date.now();
2121
+ if (now - lastUpdate < throttleMs) {
2122
+ if (rafId) cancelAnimationFrame(rafId);
2123
+ rafId = requestAnimationFrame(function () {
2124
+ updateContainerPosition();
2125
+ lastUpdate = Date.now();
2126
+ });
2127
+ } else {
2128
+ updateContainerPosition();
2129
+ lastUpdate = now;
2130
+ }
2131
+ };
2132
+
2133
+ // Find all scrollable parents
2134
+ var scrollableParents = findScrollableParents(position.element);
2135
+
2136
+ // Listen to scroll on window and all scrollable parents
2137
+ window.addEventListener('scroll', throttledUpdate, {
2138
+ passive: true
2139
+ });
2140
+ window.addEventListener('resize', throttledUpdate, {
2141
+ passive: true
2142
+ });
2143
+
2144
+ // Listen to scroll on all scrollable parent elements
2145
+ scrollableParents.forEach(function (parent) {
2146
+ parent.addEventListener('scroll', throttledUpdate, {
2147
+ passive: true
2148
+ });
2149
+ });
2150
+
2151
+ // Watch for element removal
2152
+ var observer = new MutationObserver(function () {
2153
+ if (!position.element.parentElement) {
2154
+ ref.close('api');
2155
+ }
2156
+ });
2157
+ observer.observe(document.body, {
2158
+ childList: true,
2159
+ subtree: true
2160
+ });
2161
+ positionUpdateFn = updateContainerPosition;
2162
+ positionCleanup = function positionCleanup() {
2163
+ window.removeEventListener('scroll', throttledUpdate);
2164
+ window.removeEventListener('resize', throttledUpdate);
2165
+ scrollableParents.forEach(function (parent) {
2166
+ parent.removeEventListener('scroll', throttledUpdate);
2167
+ });
2168
+ observer.disconnect();
2169
+ if (rafId) cancelAnimationFrame(rafId);
2170
+ };
2171
+ }
2172
+ }
2173
+ } else {
2174
+ // String position - get or create container for this position
2175
+ container = this.getContainerForPosition(position, finalFlow);
2176
+ }
2177
+
2178
+ // Append toast to the appropriate container
2179
+ if (container) {
2180
+ container.appendChild(toastEl);
2181
+ } else {
2182
+ // Fallback to default container
2183
+ this.container.appendChild(toastEl);
2184
+ }
1771
2185
 
1772
2186
  // Enter animation
1773
2187
  toastEl.classList.add('APX-toast--enter');
@@ -1864,6 +2278,13 @@ var ToastManager = /*#__PURE__*/function () {
1864
2278
  var cleanup = function cleanup(reason) {
1865
2279
  if (!toastEl) return;
1866
2280
  pauseTimer();
2281
+
2282
+ // Cleanup position listeners and restore styles
2283
+ if (positionCleanup) {
2284
+ positionCleanup();
2285
+ positionCleanup = null;
2286
+ }
2287
+
1867
2288
  // If overflow, remove immediately to enforce hard cap
1868
2289
  if (reason === 'overflow') {
1869
2290
  if (toastEl.parentElement) toastEl.parentElement.removeChild(toastEl);
@@ -1885,6 +2306,9 @@ var ToastManager = /*#__PURE__*/function () {
1885
2306
  var idx = _this.open.indexOf(ref);
1886
2307
  if (idx >= 0) _this.open.splice(idx, 1);
1887
2308
  _this.idToRef["delete"](toastId);
2309
+
2310
+ // Schedule garbage collection for unmanaged containers
2311
+ scheduleGarbageCollection();
1888
2312
  finish(reason);
1889
2313
  };
1890
2314
  toastEl.addEventListener('transitionend', removeEl);
@@ -1984,42 +2408,245 @@ var ToastManager = /*#__PURE__*/function () {
1984
2408
  if (!o.type) o.type = 'info';
1985
2409
  if (typeof o.dismissible !== 'boolean') o.dismissible = true;
1986
2410
  if (typeof o.durationMs !== 'number') o.durationMs = this.config.defaultDurationMs;
2411
+ // Use id from options if provided, otherwise use id from config, otherwise undefined (will be auto-generated)
2412
+ if (!o.id && this.config.id) o.id = this.config.id;
1987
2413
  return o;
1988
2414
  }
2415
+
2416
+ /**
2417
+ * Find or create a container for a specific position
2418
+ * @param {Position} position
2419
+ * @param {'up'|'down'} [flow] Flow direction (already determined, not 'auto')
2420
+ * @param {boolean} [managed] Whether this container is managed by a manager (default: false)
2421
+ * @returns {HTMLElement|null}
2422
+ */
2423
+ }, {
2424
+ key: "getContainerForPosition",
2425
+ value: function getContainerForPosition(position, flow) {
2426
+ var managed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
2427
+ if (!isBrowser) return null;
2428
+ var positionKey = serializePosition(position);
2429
+
2430
+ // Flow should already be determined ('up' or 'down'), but fallback to auto if needed
2431
+ var finalFlow = flow && flow !== 'auto' ? flow : determineFlow(position);
2432
+
2433
+ // 1. Check memory cache
2434
+ var c = _containerCache.get(positionKey);
2435
+
2436
+ // 2. If not in cache, search in DOM by data attribute
2437
+ if (!c && isBrowser) {
2438
+ c = document.querySelector("[data-apx-toast-position=\"".concat(positionKey, "\"]"));
2439
+ if (c) {
2440
+ _containerCache.set(positionKey, c);
2441
+ }
2442
+ }
2443
+
2444
+ // 3. If still not found, create new container
2445
+ if (!c) {
2446
+ c = createEl('div', 'APX-toast-container');
2447
+ c.setAttribute('data-apx-toast-position', positionKey);
2448
+
2449
+ // Mark as managed if created by a manager
2450
+ if (managed) {
2451
+ c.setAttribute('data-apx-toast-managed', 'true');
2452
+ }
2453
+
2454
+ // Determine position class (for CSS)
2455
+ var posClass = typeof position === 'string' ? position : position.type || 'bottom-right';
2456
+ c.classList.add("APX-toast-container--".concat(posClass));
2457
+ c.setAttribute('aria-live', String(this.config.ariaLive));
2458
+ c.style.zIndex = String(this.config.zIndex);
2459
+ c.style.gap = "".concat(this.config.gap, "px");
2460
+
2461
+ // Apply flow direction
2462
+ c.style.flexDirection = finalFlow === 'up' ? 'column-reverse' : 'column';
2463
+
2464
+ // Apply position styles if object position
2465
+ if (_typeof(position) === 'object' && position !== null) {
2466
+ c.style.position = 'fixed';
2467
+ var styles = this.calculatePosition(position, c);
2468
+ Object.assign(c.style, styles);
2469
+ }
2470
+
2471
+ // Append to wrapper for clean DOM organization
2472
+ var wrapper = getToastWrapper();
2473
+ if (wrapper) {
2474
+ wrapper.appendChild(c);
2475
+ } else {
2476
+ document.body.appendChild(c);
2477
+ }
2478
+ _containerCache.set(positionKey, c);
2479
+ } else {
2480
+ // Update flow direction if container exists (may be shared)
2481
+ c.style.flexDirection = finalFlow === 'up' ? 'column-reverse' : 'column';
2482
+
2483
+ // If container exists and is now managed, mark it
2484
+ if (managed && !c.hasAttribute('data-apx-toast-managed')) {
2485
+ c.setAttribute('data-apx-toast-managed', 'true');
2486
+ }
2487
+ }
2488
+ return c;
2489
+ }
1989
2490
  }, {
1990
2491
  key: "ensureContainer",
1991
2492
  value: function ensureContainer() {
1992
2493
  if (this.container || !isBrowser) return;
1993
- var c = createEl('div', 'APX-toast-container');
1994
- var pos = this.config.position || 'bottom-right';
1995
- c.classList.add("APX-toast-container--".concat(pos));
1996
- if (this.config.containerClass) c.classList.add(this.config.containerClass);
1997
- c.style.zIndex = String(this.config.zIndex);
1998
- c.style.gap = "".concat(this.config.gap, "px");
1999
- if (this.config.offset) {
2000
- var offset = "".concat(this.config.offset, "px");
2001
- if (pos.includes('bottom')) c.style.bottom = offset;else c.style.top = offset;
2002
- if (pos.includes('right')) c.style.right = offset;else c.style.left = offset;
2003
- }
2004
- c.setAttribute('aria-live', String(this.config.ariaLive));
2005
- document.body.appendChild(c);
2006
- this.container = c;
2494
+ var position = this.config.position || 'bottom-right';
2495
+ var flow = this.config.flow !== undefined ? this.config.flow : 'auto';
2496
+ // Containers created by ensureContainer are managed
2497
+ this.container = this.getContainerForPosition(position, flow, true);
2007
2498
  this.applyContainerConfig();
2008
2499
  }
2009
2500
  }, {
2010
2501
  key: "applyContainerConfig",
2011
2502
  value: function applyContainerConfig() {
2012
- var _this$container$class;
2503
+ var _this2 = this;
2013
2504
  if (!this.container) return;
2505
+ var position = this.config.position || 'bottom-right';
2506
+ var pos = typeof position === 'string' ? position : position.type || 'bottom-right';
2507
+
2508
+ // Apply styles (these may be overridden by other managers sharing the container)
2014
2509
  this.container.style.zIndex = String(this.config.zIndex);
2015
2510
  this.container.style.gap = "".concat(this.config.gap, "px");
2016
2511
  this.container.setAttribute('aria-live', String(this.config.ariaLive));
2017
- // Update position class
2018
- var posClasses = ['bottom-right', 'bottom-left', 'top-right', 'top-left'].map(function (p) {
2019
- return "APX-toast-container--".concat(p);
2020
- });
2021
- (_this$container$class = this.container.classList).remove.apply(_this$container$class, _toConsumableArray(posClasses));
2022
- this.container.classList.add("APX-toast-container--".concat(this.config.position));
2512
+
2513
+ // Update position class (only for string positions)
2514
+ if (typeof position === 'string') {
2515
+ var _this$container$class;
2516
+ var posClasses = ['bottom-right', 'bottom-left', 'top-right', 'top-left'].map(function (p) {
2517
+ return "APX-toast-container--".concat(p);
2518
+ });
2519
+ (_this$container$class = this.container.classList).remove.apply(_this$container$class, _toConsumableArray(posClasses));
2520
+ this.container.classList.add("APX-toast-container--".concat(pos));
2521
+ }
2522
+
2523
+ // Apply container class if specified
2524
+ if (this.config.containerClass) {
2525
+ this.config.containerClass.split(' ').filter(Boolean).forEach(function (cls) {
2526
+ _this2.container.classList.add(cls);
2527
+ });
2528
+ }
2529
+
2530
+ // Apply offset (only for string positions)
2531
+ if (typeof position === 'string') {
2532
+ if (this.config.offset) {
2533
+ var offset = "".concat(this.config.offset, "px");
2534
+ if (pos.includes('bottom')) this.container.style.bottom = offset;else this.container.style.top = offset;
2535
+ if (pos.includes('right')) this.container.style.right = offset;else this.container.style.left = offset;
2536
+ } else {
2537
+ // Reset offset if not specified
2538
+ if (pos.includes('bottom')) this.container.style.bottom = '';else this.container.style.top = '';
2539
+ if (pos.includes('right')) this.container.style.right = '';else this.container.style.left = '';
2540
+ }
2541
+ } else if (_typeof(position) === 'object' && position !== null) {
2542
+ // For object positions, ensure container has position: fixed
2543
+ this.container.style.position = 'fixed';
2544
+ }
2545
+ }
2546
+
2547
+ /**
2548
+ * Calculate position for a container based on position config
2549
+ * @param {Position} position
2550
+ * @param {HTMLElement} containerEl
2551
+ * @returns {{left?: string, top?: string, right?: string, bottom?: string}}
2552
+ */
2553
+ }, {
2554
+ key: "calculatePosition",
2555
+ value: function calculatePosition(position, containerEl) {
2556
+ if (typeof position === 'string') {
2557
+ // String positions are handled by container CSS
2558
+ return {};
2559
+ }
2560
+ if (_typeof(position) === 'object' && position !== null) {
2561
+ var type = position.type || (position.x || position.y ? 'sticky' : null);
2562
+ if (type === 'sticky' || !type && (position.x || position.y)) {
2563
+ // Sticky: fixed position relative to viewport
2564
+ var styles = {};
2565
+ if (position.x !== undefined) {
2566
+ if (position.x.startsWith('-')) {
2567
+ styles.right = position.x.substring(1);
2568
+ } else {
2569
+ styles.left = position.x;
2570
+ }
2571
+ }
2572
+ if (position.y !== undefined) {
2573
+ if (position.y.startsWith('-')) {
2574
+ styles.bottom = position.y.substring(1);
2575
+ } else {
2576
+ styles.top = position.y;
2577
+ }
2578
+ }
2579
+ return styles;
2580
+ }
2581
+ if (type === 'relative' && position.element) {
2582
+ // Relative: position relative to element with x/y offset
2583
+ // Use fixed positioning, so getBoundingClientRect() is relative to viewport
2584
+ var rect = position.element.getBoundingClientRect();
2585
+
2586
+ // Parse x/y offsets (can be px, em, etc.)
2587
+ var parseOffset = function parseOffset(val) {
2588
+ if (!val) return 0;
2589
+ var num = parseFloat(val);
2590
+ if (val.includes('em')) {
2591
+ // Convert em to px (approximate: 1em = 16px)
2592
+ return num * 16;
2593
+ }
2594
+ return num;
2595
+ };
2596
+ var offsetX = parseOffset(position.x || '0');
2597
+ var offsetY = parseOffset(position.y || '0');
2598
+ var _styles = {
2599
+ left: "".concat(rect.left + offsetX, "px"),
2600
+ top: "".concat(rect.top + offsetY, "px")
2601
+ };
2602
+ return _styles;
2603
+ }
2604
+ if (type === 'anchored' && position.element) {
2605
+ // Anchored: position relative to element with placement
2606
+ var _rect = position.element.getBoundingClientRect();
2607
+ var gap = position.gap || '1em';
2608
+
2609
+ // Parse gap (can be px, em, etc.)
2610
+ var parseGap = function parseGap(val) {
2611
+ var num = parseFloat(val);
2612
+ if (val.includes('em')) {
2613
+ return num * 16; // Approximate: 1em = 16px
2614
+ }
2615
+
2616
+ return num;
2617
+ };
2618
+ var gapPx = parseGap(gap);
2619
+ var _styles2 = {};
2620
+
2621
+ // Normalize placement synonyms (above/below/before/after) to CSS values
2622
+ var placement = normalizePlacement(position.placement);
2623
+ switch (placement) {
2624
+ case 'top':
2625
+ // Toast above element: bottom of toast = top of element - gap
2626
+ // bottom = viewport height - (element top - gap) = viewport height - element top + gap
2627
+ _styles2.bottom = "".concat(window.innerHeight - _rect.top + gapPx, "px");
2628
+ _styles2.left = "".concat(_rect.left, "px");
2629
+ break;
2630
+ case 'bottom':
2631
+ // Toast below element: top of toast = bottom of element + gap
2632
+ _styles2.top = "".concat(_rect.bottom + gapPx, "px");
2633
+ _styles2.left = "".concat(_rect.left, "px");
2634
+ break;
2635
+ case 'left':
2636
+ // Toast to the left: right of toast = left of element - gap
2637
+ _styles2.right = "".concat(window.innerWidth - _rect.left + gapPx, "px");
2638
+ _styles2.top = "".concat(_rect.top, "px");
2639
+ break;
2640
+ case 'right':
2641
+ // Toast to the right: left of toast = right of element + gap
2642
+ _styles2.left = "".concat(_rect.right + gapPx, "px");
2643
+ _styles2.top = "".concat(_rect.top, "px");
2644
+ break;
2645
+ }
2646
+ return _styles2;
2647
+ }
2648
+ }
2649
+ return {};
2023
2650
  }
2024
2651
  }]);
2025
2652
  return ToastManager;