@wpmoo/ui 1.0.0-rc.6 → 1.0.0-rc.7

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 (46) hide show
  1. package/ASSET_LICENSE.md +1 -1
  2. package/README.md +4 -4
  3. package/certification.json +1 -1
  4. package/dist/assets/css/moo-ui.css +7742 -8126
  5. package/dist/assets/css/moo-ui.min.css +2 -2
  6. package/dist/assets/css/moo.css +242 -290
  7. package/dist/assets/css/moo.min.css +2 -2
  8. package/dist/js/chart.js +57 -5
  9. package/dist/js/chart.min.js +3 -3
  10. package/dist/js/combobox.js +1 -1
  11. package/dist/js/context-menu.js +1 -1
  12. package/dist/js/datatable.js +114 -213
  13. package/dist/js/datepicker.js +79 -37
  14. package/dist/js/datepicker.min.js +2 -2
  15. package/dist/js/moo-ui.js +168 -51
  16. package/dist/js/moo-ui.min.js +3 -3
  17. package/dist/js/sidebar.js +121 -174
  18. package/dist/js/slider.js +1 -1
  19. package/package.json +8 -8
  20. package/scss/components/_alert.scss +1 -2
  21. package/scss/components/_card.scss +5 -5
  22. package/scss/components/_close_button.scss +8 -3
  23. package/scss/components/_dialog.scss +16 -0
  24. package/scss/components/_dropdown.scss +2 -7
  25. package/scss/components/_switch.scss +13 -5
  26. package/scss/components/_tabs.scss +1 -1
  27. package/scss/components/sidebar/_collapsed.scss +20 -23
  28. package/scss/components/sidebar/_inset.scss +14 -44
  29. package/scss/components/sidebar/_layout.scss +86 -11
  30. package/scss/foundations/_backdrop.scss +128 -0
  31. package/scss/foundations/_scope.scss +5 -0
  32. package/scss/mixins/_banner.scss +3 -0
  33. package/scss/moo-core.scss +6 -34
  34. package/scss/moo-ui.scss +6 -6
  35. package/scss/settings/_bootstrap_overrides.scss +4 -0
  36. package/scss/settings/_component_variables.scss +6 -1
  37. package/scss/settings/_options.scss +2 -0
  38. package/scss/settings/_palette.scss +1 -1
  39. package/scss/themes/_forms.scss +15 -0
  40. package/scss/themes/_root.scss +16 -0
  41. package/scss/themes/_standalone.scss +5 -0
  42. package/scss/themes/{_scoped_core.scss → _theme.scss} +1 -0
  43. package/scss/foundations/_core_state_layer.scss +0 -25
  44. package/scss/foundations/_overlay_backdrop.scss +0 -19
  45. package/scss/themes/_standalone_root.scss +0 -119
  46. /package/scss/foundations/{_core_global_primitives.scss → _globals.scss} +0 -0
package/dist/js/moo-ui.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI moo-ui.js v1.0.0-rc.6 (https://ui.wpmoo.org/)
2
+ * Moo UI moo-ui.js v1.0.0-rc.7 (https://ui.wpmoo.org/)
3
3
  * Copyright 2026 WPMoo (https://wpmoo.org)
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
@@ -14562,12 +14562,57 @@ function readThemeColors(themeElement, window2) {
14562
14562
  return colors2;
14563
14563
  }
14564
14564
  function resolveThemeElement(element) {
14565
- return element?.closest?.("[data-bs-theme]") || element?.ownerDocument?.documentElement || null;
14565
+ const scoped = element?.closest?.("[data-bs-theme]");
14566
+ if (scoped) return scoped;
14567
+ const documentBody = element?.ownerDocument?.body;
14568
+ if (documentBody?.hasAttribute?.("data-bs-theme")) {
14569
+ return documentBody;
14570
+ }
14571
+ return element?.ownerDocument?.documentElement || null;
14566
14572
  }
14567
14573
  function themeElementIsDark(element) {
14568
14574
  const theme = element?.getAttribute?.("data-bs-theme") || element?.dataset?.bsTheme;
14569
14575
  return theme === "dark";
14570
14576
  }
14577
+ function clampColorChannel(value) {
14578
+ return Math.min(255, Math.max(0, Math.round(value)));
14579
+ }
14580
+ function normalizeColorComponent(value) {
14581
+ const component = String(value).trim();
14582
+ if (component.endsWith("%")) {
14583
+ const percentage = Number.parseFloat(component.slice(0, -1));
14584
+ return Number.isFinite(percentage) ? clampColorChannel(percentage / 100 * 255) : null;
14585
+ }
14586
+ const normalized = Number.parseFloat(component);
14587
+ return Number.isFinite(normalized) ? clampColorChannel(normalized * 255) : null;
14588
+ }
14589
+ function normalizeColorAlpha(value) {
14590
+ if (value === void 0) return 1;
14591
+ const alpha2 = String(value).trim();
14592
+ if (alpha2.endsWith("%")) {
14593
+ const percentage = Number.parseFloat(alpha2.slice(0, -1));
14594
+ return Number.isFinite(percentage) ? Math.min(1, Math.max(0, percentage / 100)) : null;
14595
+ }
14596
+ const numeric = Number.parseFloat(alpha2);
14597
+ return Number.isFinite(numeric) ? Math.min(1, Math.max(0, numeric)) : null;
14598
+ }
14599
+ function normalizeCanvasColor(value) {
14600
+ if (typeof value !== "string") return null;
14601
+ const color2 = value.trim();
14602
+ const match = color2.match(
14603
+ /^color\(\s*srgb\s+([^\s/]+)\s+([^\s/]+)\s+([^\s/]+)(?:\s*\/\s*([^\s)]+))?\s*\)$/i
14604
+ );
14605
+ if (!match) return color2 || null;
14606
+ const channels = match.slice(1, 4).map(normalizeColorComponent);
14607
+ const alpha2 = normalizeColorAlpha(match[4]);
14608
+ if (channels.some((channel) => channel === null) || alpha2 === null) {
14609
+ return null;
14610
+ }
14611
+ if (alpha2 >= 1) {
14612
+ return `rgb(${channels.join(", ")})`;
14613
+ }
14614
+ return `rgba(${channels.join(", ")}, ${Number(alpha2.toFixed(3))})`;
14615
+ }
14571
14616
  function resolveCanvasColor(document2, window2, value, fallback, themeElement) {
14572
14617
  const createElement = document2?.createElement;
14573
14618
  const getComputedStyle3 = window2?.getComputedStyle;
@@ -14581,7 +14626,7 @@ function resolveCanvasColor(document2, window2, value, fallback, themeElement) {
14581
14626
  parent.appendChild(probe);
14582
14627
  try {
14583
14628
  const resolved = getComputedStyle3.call(window2, probe).color;
14584
- return resolved || fallback;
14629
+ return normalizeCanvasColor(resolved) || fallback;
14585
14630
  } finally {
14586
14631
  probe.remove();
14587
14632
  }
@@ -14853,7 +14898,7 @@ function applyThemeToChart(chart, theme, metadata, explicitColorFields = []) {
14853
14898
  chart.options.plugins.tooltip.bodyColor = theme.secondaryText;
14854
14899
  chart.options.plugins.tooltip.borderColor = theme.borderColor;
14855
14900
  }
14856
- chart.update("none");
14901
+ chart.update();
14857
14902
  }
14858
14903
  function themedGrid(theme) {
14859
14904
  return {
@@ -15174,9 +15219,16 @@ var MooChart = class _MooChart {
15174
15219
  }
15175
15220
  });
15176
15221
  });
15177
- this._observer.observe(this._themeElement, {
15222
+ const observerOptions = {
15178
15223
  attributes: true,
15179
15224
  attributeFilter: ["data-bs-theme", "style"]
15225
+ };
15226
+ const observerTargets = [
15227
+ this._themeElement,
15228
+ this._document?.documentElement
15229
+ ].filter((target, index2, targets) => target && targets.indexOf(target) === index2);
15230
+ observerTargets.forEach((target) => {
15231
+ this._observer.observe(target, observerOptions);
15180
15232
  });
15181
15233
  }
15182
15234
  instances2.set(element, this);
@@ -15947,6 +15999,37 @@ var ContextMenu = class _ContextMenu {
15947
15999
  }
15948
16000
  };
15949
16001
 
16002
+ // src/js/theme-owner.js
16003
+ var OWNER_SELECTOR = '.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]';
16004
+ var PORTAL_SELECTOR = "[data-moo-overlay-portal-host]";
16005
+ function documentFor(node) {
16006
+ if (node?.nodeType === 9) return node;
16007
+ if (node?.ownerDocument) return node.ownerDocument;
16008
+ return typeof document === "undefined" ? null : document;
16009
+ }
16010
+ function isResolvedOwner(node) {
16011
+ return Boolean(node?.matches?.(OWNER_SELECTOR));
16012
+ }
16013
+ function ownerRootFor(node) {
16014
+ const ownerDocument = documentFor(node);
16015
+ if (node?.nodeType === 9 || node === ownerDocument?.body) {
16016
+ return ownerDocument?.body?.firstElementChild || null;
16017
+ }
16018
+ return node;
16019
+ }
16020
+ function findThemeOwner(node = typeof document === "undefined" ? null : document) {
16021
+ const start = ownerRootFor(node);
16022
+ if (!start) return null;
16023
+ if (isResolvedOwner(start)) return start;
16024
+ return start.closest?.(OWNER_SELECTOR) || null;
16025
+ }
16026
+ function ownerPortalRoot(owner) {
16027
+ if (!owner) return null;
16028
+ return Array.from(owner.children || []).find(
16029
+ (child) => child.matches?.(PORTAL_SELECTOR)
16030
+ ) || owner;
16031
+ }
16032
+
15950
16033
  // src/js/components/datatable.js
15951
16034
  var instances5 = /* @__PURE__ */ new WeakMap();
15952
16035
  function normalize2(value) {
@@ -16036,7 +16119,8 @@ var DataTable = class _DataTable {
16036
16119
  this._listeners.push({ target, type, handler, options });
16037
16120
  }
16038
16121
  // Row menus are authored next to their trigger so the public HTML stays
16039
- // inspectable, but when opened they are temporarily moved under body.
16122
+ // inspectable, but when opened they are temporarily moved to their nearest
16123
+ // Moo owner portal.
16040
16124
  // That keeps the menu out of the scroll wrapper's layout math, so Safari
16041
16125
  // cannot clip it at the rounded frame edge or shift the visible table
16042
16126
  // slice while Popper positions it against the viewport.
@@ -16156,10 +16240,11 @@ var DataTable = class _DataTable {
16156
16240
  this._reparentedRowMenus.set(menu, {
16157
16241
  parent: menu.parentNode,
16158
16242
  nextSibling: menu.nextSibling,
16159
- trigger
16243
+ trigger,
16244
+ owner: findThemeOwner(trigger)
16160
16245
  });
16161
16246
  this._reparentedRowMenuByTrigger.set(trigger, menu);
16162
- this._document.body.appendChild(menu);
16247
+ this._portalRoot(trigger).appendChild(menu);
16163
16248
  }
16164
16249
  _restoreRowActionMenuForTrigger(trigger) {
16165
16250
  const menu = this._reparentedRowMenuByTrigger.get(trigger) || this._rowActionMenuForTrigger(trigger);
@@ -16172,8 +16257,10 @@ var DataTable = class _DataTable {
16172
16257
  if (!original) {
16173
16258
  return;
16174
16259
  }
16175
- const { parent, nextSibling, trigger } = original;
16176
- if (parent?.isConnected) {
16260
+ const { parent, nextSibling, trigger, owner } = original;
16261
+ if (owner?.isConnected === false) {
16262
+ menu.remove();
16263
+ } else if (parent?.isConnected) {
16177
16264
  if (nextSibling?.parentNode === parent) {
16178
16265
  parent.insertBefore(menu, nextSibling);
16179
16266
  } else {
@@ -16211,10 +16298,11 @@ var DataTable = class _DataTable {
16211
16298
  parent: menu.parentNode,
16212
16299
  nextSibling: menu.nextSibling,
16213
16300
  trigger,
16214
- owner: trigger.closest(".datatable-sort")
16301
+ owner: trigger.closest(".datatable-sort"),
16302
+ themeOwner: findThemeOwner(trigger)
16215
16303
  });
16216
16304
  this._reparentedSortMenuByTrigger.set(trigger, menu);
16217
- this._document.body.appendChild(menu);
16305
+ this._portalRoot(trigger).appendChild(menu);
16218
16306
  }
16219
16307
  _restoreSortMenuForTrigger(trigger) {
16220
16308
  const menu = this._reparentedSortMenuByTrigger.get(trigger) || this._sortMenuForTrigger(trigger);
@@ -16227,8 +16315,10 @@ var DataTable = class _DataTable {
16227
16315
  if (!original) {
16228
16316
  return;
16229
16317
  }
16230
- const { parent, nextSibling, trigger } = original;
16231
- if (parent?.isConnected) {
16318
+ const { parent, nextSibling, trigger, themeOwner } = original;
16319
+ if (themeOwner?.isConnected === false) {
16320
+ menu.remove();
16321
+ } else if (parent?.isConnected) {
16232
16322
  if (nextSibling?.parentNode === parent) {
16233
16323
  parent.insertBefore(menu, nextSibling);
16234
16324
  } else {
@@ -16250,6 +16340,9 @@ var DataTable = class _DataTable {
16250
16340
  _bootstrap(name) {
16251
16341
  return this._window.bootstrap?.[name] || null;
16252
16342
  }
16343
+ _portalRoot(trigger = this._element) {
16344
+ return ownerPortalRoot(findThemeOwner(trigger)) || this._document.body || this._document.documentElement;
16345
+ }
16253
16346
  // The bulk-actions bar's icon-only buttons carry data-bs-title instead of
16254
16347
  // a bare title attribute so Bootstrap's own Tooltip owns the hover/focus
16255
16348
  // affordance (matching the rest of Moo's icon-button contracts) rather
@@ -16267,7 +16360,10 @@ var DataTable = class _DataTable {
16267
16360
  return;
16268
16361
  }
16269
16362
  this._element.querySelectorAll('[data-datatable-bulk-actions] [data-bs-title]:not([data-bs-toggle="dropdown"])').forEach((trigger) => {
16270
- this._tooltips.push(Tooltip2.getOrCreateInstance(trigger, { animation: false }));
16363
+ this._tooltips.push(Tooltip2.getOrCreateInstance(trigger, {
16364
+ animation: false,
16365
+ container: this._portalRoot(trigger)
16366
+ }));
16271
16367
  });
16272
16368
  }
16273
16369
  _hideBulkTooltips({ blur = false } = {}) {
@@ -17358,42 +17454,54 @@ function datepickerContains(instance, target) {
17358
17454
  );
17359
17455
  }
17360
17456
  function syncPortaledPopoverContext(instance) {
17361
- const theme = instance._element.closest("[data-bs-theme]")?.getAttribute("data-bs-theme") || instance._document.documentElement.getAttribute("data-bs-theme");
17362
- const direction = getComputedStyle(instance._trigger).direction;
17457
+ const owner = findThemeOwner(instance._trigger);
17458
+ const theme = owner?.getAttribute("data-bs-theme") || null;
17459
+ const direction = instance._document.defaultView?.getComputedStyle(
17460
+ instance._trigger
17461
+ ).direction;
17363
17462
  if (theme) {
17364
17463
  instance._popover.setAttribute("data-bs-theme", theme);
17464
+ } else {
17465
+ instance._popover.removeAttribute("data-bs-theme");
17365
17466
  }
17366
- if (direction === "rtl") {
17367
- instance._popover.setAttribute("dir", "rtl");
17467
+ if (direction === "ltr" || direction === "rtl") {
17468
+ instance._popover.setAttribute("dir", direction);
17469
+ } else {
17470
+ instance._popover.removeAttribute("dir");
17368
17471
  }
17369
17472
  }
17370
17473
  function portalDatepickerPopover(instance) {
17371
17474
  const popover = instance._popover;
17372
- if (instance._popoverPortal?.host?.parentElement === instance._document.body) {
17475
+ const owner = findThemeOwner(instance._trigger);
17476
+ const portalRoot = ownerPortalRoot(owner) || instance._document.body || instance._document.documentElement;
17477
+ if (!portalRoot) {
17478
+ return;
17479
+ }
17480
+ if (instance._popoverPortal?.portal === portalRoot && popover.parentElement === portalRoot) {
17373
17481
  syncPortaledPopoverContext(instance);
17374
17482
  return;
17375
17483
  }
17376
- const host = instance._document.createElement("div");
17377
- host.className = "moo-ui";
17378
- host.dataset.datepickerPortalHost = "";
17484
+ restoreDatepickerPopover(instance);
17379
17485
  instance._popoverPortal = {
17380
17486
  parent: popover.parentNode,
17381
17487
  nextSibling: popover.nextSibling,
17382
- host,
17488
+ owner,
17489
+ portal: portalRoot,
17383
17490
  hadTheme: popover.hasAttribute("data-bs-theme"),
17384
17491
  theme: popover.getAttribute("data-bs-theme"),
17385
17492
  hadDirection: popover.hasAttribute("dir"),
17386
17493
  direction: popover.getAttribute("dir")
17387
17494
  };
17388
- host.appendChild(popover);
17389
- instance._document.body.appendChild(host);
17495
+ portalRoot.appendChild(popover);
17390
17496
  syncPortaledPopoverContext(instance);
17391
17497
  }
17392
17498
  function restoreDatepickerPopover(instance) {
17393
17499
  const portal = instance._popoverPortal;
17394
17500
  if (!portal) return;
17395
17501
  const popover = instance._popover;
17396
- if (portal.parent?.isConnected) {
17502
+ if (portal.owner?.isConnected === false) {
17503
+ popover.remove();
17504
+ } else if (portal.parent?.isConnected) {
17397
17505
  if (portal.nextSibling?.parentNode === portal.parent) {
17398
17506
  portal.parent.insertBefore(popover, portal.nextSibling);
17399
17507
  } else {
@@ -17402,7 +17510,6 @@ function restoreDatepickerPopover(instance) {
17402
17510
  } else {
17403
17511
  popover.remove();
17404
17512
  }
17405
- portal.host?.remove();
17406
17513
  if (portal.hadTheme) {
17407
17514
  popover.setAttribute("data-bs-theme", portal.theme);
17408
17515
  } else {
@@ -18315,7 +18422,8 @@ var Sidebar = class _Sidebar {
18315
18422
  this._element = element;
18316
18423
  this._document = element.ownerDocument;
18317
18424
  this._window = this._document.defaultView;
18318
- this._root = this._document.documentElement;
18425
+ this._documentElement = this._document.documentElement;
18426
+ this._root = findThemeOwner(element) || this._document.body || this._documentElement;
18319
18427
  this._sidebar = element.querySelector('[data-slot="sidebar"]');
18320
18428
  this._config = {
18321
18429
  breakpoint: "(min-width: 992px)",
@@ -18360,6 +18468,9 @@ var Sidebar = class _Sidebar {
18360
18468
  _bootstrap(name) {
18361
18469
  return this._window.bootstrap?.[name] || null;
18362
18470
  }
18471
+ _portalRoot(trigger = this._element) {
18472
+ return ownerPortalRoot(findThemeOwner(trigger)) || this._document.body || this._documentElement;
18473
+ }
18363
18474
  _isDesktop() {
18364
18475
  return this._window.matchMedia(this._config.breakpoint).matches;
18365
18476
  }
@@ -18384,7 +18495,7 @@ var Sidebar = class _Sidebar {
18384
18495
  this._closeFlyouts();
18385
18496
  this._syncTooltips();
18386
18497
  });
18387
- this._directionObserver.observe(this._root, {
18498
+ this._directionObserver.observe(this._documentElement, {
18388
18499
  attributes: true,
18389
18500
  attributeFilter: ["dir"]
18390
18501
  });
@@ -18477,8 +18588,8 @@ var Sidebar = class _Sidebar {
18477
18588
  delete item.dataset.sidebarDropdownPositioned;
18478
18589
  item.style.removeProperty("--moo-sidebar-dropdown-block-start");
18479
18590
  item.style.removeProperty("--moo-sidebar-dropdown-block-end");
18480
- item.style.removeProperty("--moo-sidebar-dropdown-inline-start");
18481
- item.style.removeProperty("--moo-sidebar-dropdown-inline-end");
18591
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
18592
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
18482
18593
  }
18483
18594
  _closeDropdowns(exceptControl = null) {
18484
18595
  const Dropdown = this._bootstrap("Dropdown");
@@ -18508,21 +18619,20 @@ var Sidebar = class _Sidebar {
18508
18619
  const gap = 4;
18509
18620
  const sidebar = control.closest('[data-slot="sidebar"]');
18510
18621
  const side = sidebar?.dataset.side || "left";
18511
- const isRtl = this._root.dir === "rtl";
18512
18622
  if (side === "right") {
18513
- const inlineEnd = isRtl ? rect.right + gap : this._window.innerWidth - rect.left + gap;
18623
+ const right = this._window.innerWidth - rect.left + gap;
18514
18624
  item.style.setProperty(
18515
- "--moo-sidebar-dropdown-inline-end",
18516
- `${Math.round(inlineEnd)}px`
18625
+ "--moo-sidebar-dropdown-right",
18626
+ `${Math.round(right)}px`
18517
18627
  );
18518
- item.style.removeProperty("--moo-sidebar-dropdown-inline-start");
18628
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
18519
18629
  } else {
18520
- const inlineStart = isRtl ? this._window.innerWidth - rect.left + gap : rect.right + gap;
18630
+ const left = rect.right + gap;
18521
18631
  item.style.setProperty(
18522
- "--moo-sidebar-dropdown-inline-start",
18523
- `${Math.round(inlineStart)}px`
18632
+ "--moo-sidebar-dropdown-left",
18633
+ `${Math.round(left)}px`
18524
18634
  );
18525
- item.style.removeProperty("--moo-sidebar-dropdown-inline-end");
18635
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
18526
18636
  }
18527
18637
  if (isHeaderWorkspace) {
18528
18638
  item.style.setProperty(
@@ -18552,7 +18662,7 @@ var Sidebar = class _Sidebar {
18552
18662
  this._element.querySelectorAll(".sidebar-menu-item--flyout-open").forEach((item) => {
18553
18663
  item.classList.remove("sidebar-menu-item--flyout-open");
18554
18664
  item.style.removeProperty("--moo-sidebar-flyout-block-start");
18555
- item.style.removeProperty("--moo-sidebar-flyout-inline-start");
18665
+ item.style.removeProperty("--moo-sidebar-flyout-left");
18556
18666
  this._resetFlyoutTrigger(item, false);
18557
18667
  });
18558
18668
  }
@@ -18568,8 +18678,9 @@ var Sidebar = class _Sidebar {
18568
18678
  this._closeDropdowns();
18569
18679
  this._closeFlyouts();
18570
18680
  const rect = item.getBoundingClientRect();
18681
+ const sidebar = this._sidebar;
18682
+ const sidebarRect = sidebar?.getBoundingClientRect() || rect;
18571
18683
  const gap = 4;
18572
- const inlineStart = this._root.dir === "rtl" ? this._window.innerWidth - rect.left + gap : rect.right + gap;
18573
18684
  const flyout = submenu.cloneNode(true);
18574
18685
  flyout.removeAttribute("id");
18575
18686
  flyout.classList.remove("collapse", "show", "collapsing");
@@ -18577,12 +18688,12 @@ var Sidebar = class _Sidebar {
18577
18688
  flyout.dataset.sidebarFlyout = "";
18578
18689
  flyout.removeAttribute("style");
18579
18690
  flyout.style.setProperty("--moo-sidebar-flyout-block-start", `${Math.round(rect.top)}px`);
18580
- flyout.style.setProperty(
18581
- "--moo-sidebar-flyout-inline-start",
18582
- `${Math.round(inlineStart)}px`
18583
- );
18584
- const root = this._element.closest(".moo-ui") || this._document.body;
18585
- root.appendChild(flyout);
18691
+ const side = sidebar?.dataset.side || "left";
18692
+ this._portalRoot(item).appendChild(flyout);
18693
+ const flyoutWidth = flyout.getBoundingClientRect().width;
18694
+ const left = side === "right" ? sidebarRect.left - flyoutWidth - gap : sidebarRect.right + gap;
18695
+ const boundedLeft = Math.max(0, Math.min(this._window.innerWidth - flyoutWidth, left));
18696
+ flyout.style.setProperty("--moo-sidebar-flyout-left", `${Math.round(boundedLeft)}px`);
18586
18697
  this._flyout = flyout;
18587
18698
  this._flyoutOwner = item;
18588
18699
  item.classList.add("sidebar-menu-item--flyout-open");
@@ -18613,7 +18724,8 @@ var Sidebar = class _Sidebar {
18613
18724
  return;
18614
18725
  }
18615
18726
  const collapsed = this._isCollapsed();
18616
- const placement = this._root.dir === "rtl" ? "left" : "right";
18727
+ const side = this._sidebar?.dataset.side || "left";
18728
+ const placement = side === "right" ? "left" : "right";
18617
18729
  this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => {
18618
18730
  this._disposeTooltip(control);
18619
18731
  if (!collapsed || this._isIdentityTrigger(control) || control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")) {
@@ -18623,7 +18735,12 @@ var Sidebar = class _Sidebar {
18623
18735
  new Tooltip2(anchor, {
18624
18736
  title: control.getAttribute("data-sidebar-tooltip"),
18625
18737
  placement,
18626
- container: "body",
18738
+ // Bootstrap's default tooltip offset is 6px. The icon rail's
18739
+ // boundary calculation still lets the tooltip box overlap the
18740
+ // physical Sidebar edge at that distance, so keep an explicit 8px
18741
+ // separation for both physical placements.
18742
+ offset: [0, 8],
18743
+ container: this._portalRoot(control),
18627
18744
  trigger: "hover focus"
18628
18745
  });
18629
18746
  this._tooltipAnchors.add(anchor);