@wpmoo/ui 0.9.0 → 1.0.0-rc.10

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 (99) hide show
  1. package/ASSET_LICENSE.md +4 -2
  2. package/LICENSE +28 -0
  3. package/README.md +105 -100
  4. package/certification.json +25 -3
  5. package/dist/assets/css/moo-ui.css +14005 -12457
  6. package/dist/assets/css/moo-ui.min.css +10 -28
  7. package/dist/assets/css/moo.css +2606 -902
  8. package/dist/assets/css/moo.min.css +10 -29
  9. package/dist/js/chart.js +15312 -0
  10. package/dist/js/chart.min.js +27 -0
  11. package/dist/js/combobox.js +129 -85
  12. package/dist/js/context-menu.js +35 -66
  13. package/dist/js/datatable.js +390 -188
  14. package/dist/js/datepicker.js +1197 -0
  15. package/dist/js/datepicker.min.js +7 -0
  16. package/dist/js/moo-ui.js +3947 -0
  17. package/dist/js/moo-ui.min.js +7 -0
  18. package/dist/js/sheet.js +46 -0
  19. package/dist/js/sidebar.js +190 -172
  20. package/dist/js/slider.js +289 -0
  21. package/dist/js/state.js +76 -0
  22. package/dist/release-manifest.json +24 -0
  23. package/package.json +39 -9
  24. package/scss/_components.scss +70 -0
  25. package/scss/_config.scss +18 -0
  26. package/scss/_settings.scss +5 -0
  27. package/scss/components/_accordion.scss +54 -0
  28. package/scss/components/_alert.scss +82 -0
  29. package/scss/components/_avatar.scss +139 -0
  30. package/scss/components/_badge.scss +34 -0
  31. package/scss/components/_breadcrumb.scss +119 -0
  32. package/scss/components/_button.scss +200 -0
  33. package/scss/components/_button_group.scss +57 -0
  34. package/scss/components/_card.scss +114 -0
  35. package/scss/components/_checkbox.scss +78 -0
  36. package/scss/components/_close_button.scss +56 -0
  37. package/scss/components/_collapsible.scss +44 -0
  38. package/scss/components/_combobox.scss +253 -0
  39. package/scss/components/_context-menu.scss +18 -0
  40. package/scss/components/_datatable.scss +1067 -0
  41. package/scss/components/_datepicker.scss +237 -0
  42. package/scss/components/_dialog.scss +69 -0
  43. package/scss/components/_dropdown.scss +201 -0
  44. package/scss/components/_field.scss +94 -0
  45. package/scss/components/_input.scss +31 -0
  46. package/scss/components/_input_group.scss +151 -0
  47. package/scss/components/_kbd.scss +11 -0
  48. package/scss/components/_menubar.scss +17 -0
  49. package/scss/components/_navigation.scss +36 -0
  50. package/scss/components/_pagination.scss +58 -0
  51. package/scss/components/_popover.scss +26 -0
  52. package/scss/components/_progress.scss +5 -0
  53. package/scss/components/_radio_group.scss +108 -0
  54. package/scss/components/_search_trigger.scss +61 -0
  55. package/scss/components/_select.scss +8 -0
  56. package/scss/components/_separator.scss +10 -0
  57. package/scss/components/_sheet.scss +16 -0
  58. package/scss/components/_sidebar.scss +5 -0
  59. package/scss/components/_skeleton.scss +10 -0
  60. package/scss/components/_slider.scss +213 -0
  61. package/scss/components/_spinner.scss +11 -0
  62. package/scss/components/_switch.scss +30 -0
  63. package/scss/components/_table.scss +12 -0
  64. package/scss/components/_tabs.scss +116 -0
  65. package/scss/components/_textarea.scss +8 -0
  66. package/scss/components/_toast.scss +116 -0
  67. package/scss/components/_toggle_group.scss +17 -0
  68. package/scss/components/_tooltip.scss +64 -0
  69. package/scss/components/sidebar/_base.scss +86 -0
  70. package/scss/components/sidebar/_collapsed.scss +112 -0
  71. package/scss/components/sidebar/_identity.scss +71 -0
  72. package/scss/components/sidebar/_menus.scss +219 -0
  73. package/scss/foundations/_backdrop.scss +102 -0
  74. package/scss/foundations/_focus.scss +83 -0
  75. package/scss/foundations/_globals.scss +5 -0
  76. package/scss/foundations/_scope.scss +26 -0
  77. package/scss/layouts/_app.scss +75 -0
  78. package/scss/layouts/_page_grid.scss +27 -0
  79. package/scss/layouts/app/sidebar/_base.scss +84 -0
  80. package/scss/layouts/app/sidebar/_default.scss +20 -0
  81. package/scss/layouts/app/sidebar/_floating.scss +36 -0
  82. package/scss/layouts/app/sidebar/_inset.scss +32 -0
  83. package/scss/mixins/_banner.scss +9 -0
  84. package/scss/moo-core.scss +16 -0
  85. package/scss/moo-ui.scss +54 -0
  86. package/scss/settings/_bootstrap_overrides.scss +7 -0
  87. package/scss/settings/_component_variables.scss +244 -0
  88. package/scss/settings/_forms.scss +28 -0
  89. package/scss/settings/_options.scss +2 -0
  90. package/scss/settings/_palette.scss +90 -0
  91. package/scss/themes/_forms.scss +15 -0
  92. package/scss/themes/_root.scss +20 -0
  93. package/scss/themes/_standalone.scss +3 -0
  94. package/scss/themes/_theme.scss +244 -0
  95. package/scss/utilities/_background_color.scss +18 -0
  96. package/scss/utilities/_scroll_fade.scss +193 -0
  97. package/scss/utilities/_scroll_fade_primitives.scss +43 -0
  98. package/scss/_facade-settings.scss +0 -51
  99. package/scss/settings/_facade_public.scss +0 -23
@@ -1,14 +1,57 @@
1
- const instances = new WeakMap();
1
+ /*!
2
+ * Moo UI sidebar.js v1.0.0-rc.10 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+
10
+ // src/js/theme-owner.js
11
+ var OWNER_SELECTOR = '.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]';
12
+ var PORTAL_SELECTOR = "[data-moo-overlay-host]";
13
+ function documentFor(node) {
14
+ if (node?.nodeType === 9) return node;
15
+ if (node?.ownerDocument) return node.ownerDocument;
16
+ return typeof document === "undefined" ? null : document;
17
+ }
18
+ __name(documentFor, "documentFor");
19
+ function isResolvedOwner(node) {
20
+ return Boolean(node?.matches?.(OWNER_SELECTOR));
21
+ }
22
+ __name(isResolvedOwner, "isResolvedOwner");
23
+ function ownerRootFor(node) {
24
+ const ownerDocument = documentFor(node);
25
+ if (node?.nodeType === 9 || node === ownerDocument?.body) {
26
+ return ownerDocument?.body?.firstElementChild || null;
27
+ }
28
+ return node;
29
+ }
30
+ __name(ownerRootFor, "ownerRootFor");
31
+ function findThemeOwner(node = typeof document === "undefined" ? null : document) {
32
+ const start = ownerRootFor(node);
33
+ if (!start) return null;
34
+ if (isResolvedOwner(start)) return start;
35
+ return start.closest?.(OWNER_SELECTOR) || null;
36
+ }
37
+ __name(findThemeOwner, "findThemeOwner");
38
+ function ownerPortalRoot(owner) {
39
+ if (!owner) return null;
40
+ return Array.from(owner.children || []).find(
41
+ (child) => child.matches?.(PORTAL_SELECTOR)
42
+ ) || owner;
43
+ }
44
+ __name(ownerPortalRoot, "ownerPortalRoot");
2
45
 
3
- export default class Sidebar {
46
+ // src/js/components/sidebar.js
47
+ var instances = /* @__PURE__ */ new WeakMap();
48
+ var _Sidebar = class _Sidebar {
4
49
  static getInstance(element) {
5
50
  return element?.nodeType === 1 ? instances.get(element) || null : null;
6
51
  }
7
-
8
52
  static getOrCreateInstance(element, config = {}) {
9
- return Sidebar.getInstance(element) || new Sidebar(element, config);
53
+ return _Sidebar.getInstance(element) || new _Sidebar(element, config);
10
54
  }
11
-
12
55
  constructor(element, config = {}) {
13
56
  if (element?.nodeType !== 1 || !element.matches('[data-slot="sidebar-wrapper"]')) {
14
57
  throw new TypeError("Sidebar requires a [data-slot=sidebar-wrapper] root.");
@@ -17,32 +60,31 @@ export default class Sidebar {
17
60
  if (existing) {
18
61
  return existing;
19
62
  }
20
-
21
63
  this._element = element;
22
64
  this._document = element.ownerDocument;
23
65
  this._window = this._document.defaultView;
24
- this._root = this._document.documentElement;
66
+ this._documentElement = this._document.documentElement;
67
+ this._root = findThemeOwner(element) || this._document.body || this._documentElement;
25
68
  this._sidebar = element.querySelector('[data-slot="sidebar"]');
26
69
  this._config = {
27
70
  breakpoint: "(min-width: 992px)",
28
71
  storagePrefix: "moo-sidebar:",
29
72
  keyboard: true,
30
- ...config,
73
+ ...config
31
74
  };
32
75
  this._listeners = [];
33
- this._tooltipAnchors = new Set();
76
+ this._tooltipAnchors = /* @__PURE__ */ new Set();
34
77
  this._flyout = null;
35
78
  this._flyoutOwner = null;
36
79
  this._offcanvas = null;
37
80
  this._offcanvasTrigger = null;
38
-
39
81
  instances.set(element, this);
40
82
  this._bindEvents();
41
83
  this._restoreState();
42
- this._element.setAttribute("data-moo-sidebar-ready", "");
84
+ this._scrollActiveItemIntoView();
85
+ this._element.setAttribute("data-sidebar-ready", "");
43
86
  this._observeDirection();
44
87
  }
45
-
46
88
  dispose() {
47
89
  this._listeners.forEach(({ target, type, handler, options }) => {
48
90
  target.removeEventListener(type, handler, options);
@@ -50,48 +92,41 @@ export default class Sidebar {
50
92
  this._listeners = [];
51
93
  this._directionObserver?.disconnect();
52
94
  this._closeFlyouts();
53
- this._element
54
- .querySelectorAll("[data-moo-sidebar-dropdown-positioned]")
55
- .forEach((item) => this._clearDropdownPosition(item));
56
- this._element
57
- .querySelectorAll("[data-moo-sidebar-tooltip]")
58
- .forEach((control) => this._disposeTooltip(control));
95
+ this._element.querySelectorAll("[data-sidebar-dropdown-positioned]").forEach((item) => this._clearDropdownPosition(item));
96
+ this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => this._disposeTooltip(control));
59
97
  this._offcanvas?.dispose();
60
98
  this._offcanvas = null;
61
99
  this._offcanvasTrigger = null;
62
- this._element.removeAttribute("data-moo-sidebar-ready");
100
+ this._element.removeAttribute("data-sidebar-ready");
63
101
  instances.delete(this._element);
64
102
  }
65
-
66
103
  _listen(target, type, handler, options) {
67
104
  target?.addEventListener(type, handler, options);
68
105
  if (target) {
69
106
  this._listeners.push({ target, type, handler, options });
70
107
  }
71
108
  }
72
-
73
109
  _bootstrap(name) {
74
110
  return this._window.bootstrap?.[name] || null;
75
111
  }
76
-
112
+ _portalRoot(trigger = this._element) {
113
+ return ownerPortalRoot(findThemeOwner(trigger)) || this._document.body || this._documentElement;
114
+ }
77
115
  _isDesktop() {
78
116
  return this._window.matchMedia(this._config.breakpoint).matches;
79
117
  }
80
-
81
118
  _isCollapsed() {
82
- return this._isDesktop() && this._element.dataset.mooSidebarState === "collapsed";
119
+ return this._isDesktop() && this._sidebar?.dataset?.collapsible === "icon" && this._element.dataset.sidebarState === "collapsed";
83
120
  }
84
-
85
121
  _trigger(name, detail = {}) {
86
122
  return this._element.dispatchEvent(
87
123
  new this._window.CustomEvent(`${name}.moo.sidebar`, {
88
124
  bubbles: true,
89
125
  cancelable: name === "show" || name === "hide",
90
- detail,
126
+ detail
91
127
  })
92
128
  );
93
129
  }
94
-
95
130
  _observeDirection() {
96
131
  const Observer = this._window.MutationObserver;
97
132
  if (!Observer) {
@@ -101,14 +136,13 @@ export default class Sidebar {
101
136
  this._closeFlyouts();
102
137
  this._syncTooltips();
103
138
  });
104
- this._directionObserver.observe(this._root, {
139
+ this._directionObserver.observe(this._documentElement, {
105
140
  attributes: true,
106
- attributeFilter: ["dir"],
141
+ attributeFilter: ["dir"]
107
142
  });
108
143
  }
109
-
110
144
  _restoreState() {
111
- const key = this._element.dataset.mooSidebarKey;
145
+ const key = this._element.dataset.sidebarKey;
112
146
  let stored = null;
113
147
  if (key) {
114
148
  try {
@@ -117,57 +151,66 @@ export default class Sidebar {
117
151
  stored = null;
118
152
  }
119
153
  }
120
- const initial =
121
- stored === "collapsed" || stored === "expanded"
122
- ? stored
123
- : this._element.dataset.mooSidebarState === "collapsed"
124
- ? "collapsed"
125
- : "expanded";
154
+ const initial = stored === "collapsed" || stored === "expanded" ? stored : this._element.dataset.sidebarState === "collapsed" ? "collapsed" : "expanded";
126
155
  this._setState(initial, false, false);
127
156
  }
128
-
157
+ _scrollActiveItemIntoView() {
158
+ const activeRoute = this._element.querySelector(
159
+ '[data-slot="sidebar-content"] a[data-slot="sidebar-menu-button"][aria-current="page"], [data-slot="sidebar-content"] a.sidebar-menu-button[aria-current="page"], [data-slot="sidebar-content"] a.sidebar-menu-button.active'
160
+ );
161
+ const active = activeRoute || this._element.querySelector(
162
+ '[data-slot="sidebar-content"] [data-slot="sidebar-menu-button"][aria-current="page"], [data-slot="sidebar-content"] .sidebar-menu-button[aria-current="page"], [data-slot="sidebar-content"] .sidebar-menu-button.active'
163
+ );
164
+ const content = active?.closest?.('[data-slot="sidebar-content"]');
165
+ if (!active || !content || content.clientHeight <= 0) {
166
+ return;
167
+ }
168
+ const maxScrollTop = content.scrollHeight - content.clientHeight;
169
+ if (maxScrollTop <= 0) {
170
+ return;
171
+ }
172
+ const contentRect = content.getBoundingClientRect();
173
+ const activeRect = active.getBoundingClientRect();
174
+ if (contentRect.height <= 0 || activeRect.height <= 0) {
175
+ return;
176
+ }
177
+ const targetScrollTop = content.scrollTop + activeRect.top - contentRect.top - (content.clientHeight - activeRect.height) / 2;
178
+ content.scrollTop = Math.round(
179
+ Math.min(Math.max(targetScrollTop, 0), maxScrollTop)
180
+ );
181
+ }
129
182
  _setState(state, persist = true, emit = true) {
130
183
  const next = state === "collapsed" ? "collapsed" : "expanded";
131
- const previous = this._element.dataset.mooSidebarState;
132
- this._element.dataset.mooSidebarState = next;
133
- const key = this._element.dataset.mooSidebarKey;
184
+ const previous = this._element.dataset.sidebarState;
185
+ this._element.dataset.sidebarState = next;
186
+ const key = this._element.dataset.sidebarKey;
134
187
  if (key === "catalog-shell") {
135
- this._root.dataset.mooSidebarCatalogState = next;
188
+ this._root.dataset.sidebarCatalogState = next;
136
189
  }
137
190
  if (persist && key) {
138
191
  try {
139
192
  this._window.localStorage.setItem(this._config.storagePrefix + key, next);
140
193
  } catch (_) {
141
- /* Storage is best-effort in restricted browsing contexts. */
142
194
  }
143
195
  }
144
196
  this._syncControls();
145
197
  if (next === "expanded") {
146
- this._element
147
- .querySelectorAll(".sidebar-menu-item")
148
- .forEach((item) => this._resetFlyoutTrigger(item));
198
+ this._element.querySelectorAll(".sidebar-menu-item").forEach((item) => this._resetFlyoutTrigger(item));
149
199
  }
150
200
  this._syncTooltips();
151
201
  if (emit && previous !== next) {
152
202
  this._trigger("change", { state: next, previousState: previous || null });
153
203
  }
154
204
  }
155
-
156
205
  _toggle() {
157
206
  this._setState(
158
- this._element.dataset.mooSidebarState === "collapsed" ? "expanded" : "collapsed"
207
+ this._element.dataset.sidebarState === "collapsed" ? "expanded" : "collapsed"
159
208
  );
160
209
  }
161
-
162
210
  _syncControls() {
163
- const expanded = this._isDesktop()
164
- ? this._element.dataset.mooSidebarState === "expanded"
165
- : this._sidebar?.classList.contains("show") || false;
166
- this._element
167
- .querySelectorAll("[data-moo-sidebar-trigger], [data-moo-sidebar-rail]")
168
- .forEach((control) => control.setAttribute("aria-expanded", String(expanded)));
211
+ const expanded = this._isDesktop() ? this._element.dataset.sidebarState === "expanded" : this._sidebar?.classList.contains("show") || false;
212
+ this._element.querySelectorAll("[data-sidebar-trigger], [data-sidebar-rail]").forEach((control) => control.setAttribute("aria-expanded", String(expanded)));
169
213
  }
170
-
171
214
  _resetFlyoutTrigger(item, expanded = null) {
172
215
  const trigger = item?.querySelector(":scope > .sidebar-menu-sub-trigger");
173
216
  const submenu = item?.querySelector(":scope > .sidebar-menu-sub");
@@ -178,68 +221,75 @@ export default class Sidebar {
178
221
  );
179
222
  }
180
223
  }
181
-
182
224
  _clearDropdownPosition(control) {
183
- const item = control?.matches?.(".sidebar-menu-item")
184
- ? control
185
- : control?.closest?.(".sidebar-menu-item");
225
+ const item = control?.matches?.(".sidebar-menu-item") ? control : control?.closest?.(".sidebar-menu-item");
186
226
  if (!item) {
187
227
  return;
188
228
  }
189
- delete item.dataset.mooSidebarDropdownPositioned;
229
+ delete item.dataset.sidebarDropdownPositioned;
190
230
  item.style.removeProperty("--moo-sidebar-dropdown-block-start");
191
- item.style.removeProperty("--moo-sidebar-dropdown-inline-start");
231
+ item.style.removeProperty("--moo-sidebar-dropdown-block-end");
232
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
233
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
192
234
  }
193
-
194
235
  _closeDropdowns(exceptControl = null) {
195
236
  const Dropdown = this._bootstrap("Dropdown");
196
- this._element
197
- .querySelectorAll('[data-bs-toggle="dropdown"][aria-expanded="true"]')
198
- .forEach((control) => {
199
- if (control === exceptControl) {
200
- return;
201
- }
202
- if (Dropdown) {
203
- Dropdown.getOrCreateInstance(control).hide();
204
- } else {
205
- control.setAttribute("aria-expanded", "false");
206
- control
207
- .closest(".dropdown")
208
- ?.querySelector(".dropdown-menu.show")
209
- ?.classList.remove("show");
210
- this._clearDropdownPosition(control);
211
- }
212
- });
237
+ this._element.querySelectorAll('[data-bs-toggle="dropdown"][aria-expanded="true"]').forEach((control) => {
238
+ if (control === exceptControl) {
239
+ return;
240
+ }
241
+ if (Dropdown) {
242
+ Dropdown.getOrCreateInstance(control).hide();
243
+ } else {
244
+ control.setAttribute("aria-expanded", "false");
245
+ control.closest(".dropdown")?.querySelector(".dropdown-menu.show")?.classList.remove("show");
246
+ this._clearDropdownPosition(control);
247
+ }
248
+ });
213
249
  }
214
-
215
250
  _positionDropdown(control) {
216
251
  const item = control?.closest?.(".sidebar-menu-item");
217
- const isHeaderWorkspace =
218
- control?.classList.contains("sidebar-menu-button--workspace") &&
219
- control.closest('[data-slot="sidebar-header"]');
220
- if (!control || !item || !isHeaderWorkspace || !this._isDesktop()) {
252
+ const isHeaderWorkspace = control?.classList.contains("sidebar-menu-button--workspace") && control.closest('[data-slot="sidebar-header"]');
253
+ const isFooterAccount = control?.closest(".sidebar-menu-item--account") && control.closest('[data-slot="sidebar-footer"]');
254
+ if (!control || !item || !(isHeaderWorkspace || isFooterAccount) || !this._isDesktop()) {
221
255
  this._clearDropdownPosition(control);
222
256
  return;
223
257
  }
224
-
225
258
  this._closeFlyouts();
226
259
  const rect = control.getBoundingClientRect();
227
260
  const gap = 4;
228
- const inlineStart =
229
- this._root.dir === "rtl"
230
- ? this._window.innerWidth - rect.left + gap
231
- : rect.right + gap;
232
- item.style.setProperty(
233
- "--moo-sidebar-dropdown-block-start",
234
- `${Math.round(rect.bottom + gap)}px`
235
- );
236
- item.style.setProperty(
237
- "--moo-sidebar-dropdown-inline-start",
238
- `${Math.round(inlineStart)}px`
239
- );
240
- item.dataset.mooSidebarDropdownPositioned = "";
261
+ const sidebar = control.closest('[data-slot="sidebar"]');
262
+ const side = sidebar?.dataset.side || "left";
263
+ if (side === "right") {
264
+ const right = this._window.innerWidth - rect.left + gap;
265
+ item.style.setProperty(
266
+ "--moo-sidebar-dropdown-right",
267
+ `${Math.round(right)}px`
268
+ );
269
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
270
+ } else {
271
+ const left = rect.right + gap;
272
+ item.style.setProperty(
273
+ "--moo-sidebar-dropdown-left",
274
+ `${Math.round(left)}px`
275
+ );
276
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
277
+ }
278
+ if (isHeaderWorkspace) {
279
+ item.style.setProperty(
280
+ "--moo-sidebar-dropdown-block-start",
281
+ `${Math.round(rect.bottom + gap)}px`
282
+ );
283
+ item.style.removeProperty("--moo-sidebar-dropdown-block-end");
284
+ } else {
285
+ item.style.setProperty(
286
+ "--moo-sidebar-dropdown-block-end",
287
+ `${Math.round(this._window.innerHeight - rect.top + gap)}px`
288
+ );
289
+ item.style.removeProperty("--moo-sidebar-dropdown-block-start");
290
+ }
291
+ item.dataset.sidebarDropdownPositioned = "";
241
292
  }
242
-
243
293
  _removeFlyoutPortal() {
244
294
  this._flyout?.remove();
245
295
  if (this._flyoutOwner) {
@@ -248,17 +298,15 @@ export default class Sidebar {
248
298
  this._flyout = null;
249
299
  this._flyoutOwner = null;
250
300
  }
251
-
252
301
  _closeFlyouts() {
253
302
  this._removeFlyoutPortal();
254
303
  this._element.querySelectorAll(".sidebar-menu-item--flyout-open").forEach((item) => {
255
304
  item.classList.remove("sidebar-menu-item--flyout-open");
256
305
  item.style.removeProperty("--moo-sidebar-flyout-block-start");
257
- item.style.removeProperty("--moo-sidebar-flyout-inline-start");
306
+ item.style.removeProperty("--moo-sidebar-flyout-left");
258
307
  this._resetFlyoutTrigger(item, false);
259
308
  });
260
309
  }
261
-
262
310
  _openFlyout(item) {
263
311
  const submenu = item?.querySelector(":scope > .sidebar-menu-sub");
264
312
  const trigger = item?.querySelector(":scope > .sidebar-menu-sub-trigger");
@@ -268,45 +316,36 @@ export default class Sidebar {
268
316
  if (this._flyoutOwner === item && this._flyout) {
269
317
  return;
270
318
  }
271
-
272
319
  this._closeDropdowns();
273
320
  this._closeFlyouts();
274
321
  const rect = item.getBoundingClientRect();
322
+ const sidebar = this._sidebar;
323
+ const sidebarRect = sidebar?.getBoundingClientRect() || rect;
275
324
  const gap = 4;
276
- const inlineStart =
277
- this._root.dir === "rtl"
278
- ? this._window.innerWidth - rect.left + gap
279
- : rect.right + gap;
280
325
  const flyout = submenu.cloneNode(true);
281
326
  flyout.removeAttribute("id");
282
327
  flyout.classList.remove("collapse", "show", "collapsing");
283
328
  flyout.classList.add("sidebar-menu-flyout");
284
- flyout.dataset.mooSidebarFlyout = "";
329
+ flyout.dataset.sidebarFlyout = "";
285
330
  flyout.removeAttribute("style");
286
331
  flyout.style.setProperty("--moo-sidebar-flyout-block-start", `${Math.round(rect.top)}px`);
287
- flyout.style.setProperty(
288
- "--moo-sidebar-flyout-inline-start",
289
- `${Math.round(inlineStart)}px`
290
- );
291
- const root = this._element.closest(".moo-ui") || this._document.body;
292
- root.appendChild(flyout);
332
+ const side = sidebar?.dataset.side || "left";
333
+ this._portalRoot(item).appendChild(flyout);
334
+ const flyoutWidth = flyout.getBoundingClientRect().width;
335
+ const left = side === "right" ? sidebarRect.left - flyoutWidth - gap : sidebarRect.right + gap;
336
+ const boundedLeft = Math.max(0, Math.min(this._window.innerWidth - flyoutWidth, left));
337
+ flyout.style.setProperty("--moo-sidebar-flyout-left", `${Math.round(boundedLeft)}px`);
293
338
  this._flyout = flyout;
294
339
  this._flyoutOwner = item;
295
340
  item.classList.add("sidebar-menu-item--flyout-open");
296
341
  trigger?.setAttribute("aria-expanded", "true");
297
342
  }
298
-
299
343
  _tooltipAnchor(control) {
300
344
  return control.closest("li") || control;
301
345
  }
302
-
303
346
  _isIdentityTrigger(control) {
304
- return (
305
- control.closest(".sidebar-menu-item--account") ||
306
- control.classList.contains("sidebar-menu-button--workspace")
307
- );
347
+ return control.closest(".sidebar-menu-item--account") || control.classList.contains("sidebar-menu-button--workspace");
308
348
  }
309
-
310
349
  _disposeTooltip(control) {
311
350
  const Tooltip = this._bootstrap("Tooltip");
312
351
  if (!Tooltip || !control) {
@@ -320,38 +359,34 @@ export default class Sidebar {
320
359
  anchor.removeAttribute("data-bs-original-title");
321
360
  anchor.removeAttribute("aria-describedby");
322
361
  }
323
-
324
362
  _syncTooltips() {
325
363
  const Tooltip = this._bootstrap("Tooltip");
326
364
  if (!Tooltip) {
327
365
  return;
328
366
  }
329
367
  const collapsed = this._isCollapsed();
330
- const placement = this._root.dir === "rtl" ? "left" : "right";
331
- this._element.querySelectorAll("[data-moo-sidebar-tooltip]").forEach((control) => {
368
+ const side = this._sidebar?.dataset.side || "left";
369
+ const placement = side === "right" ? "left" : "right";
370
+ this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => {
332
371
  this._disposeTooltip(control);
333
- if (
334
- !collapsed ||
335
- this._isIdentityTrigger(control) ||
336
- control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")
337
- ) {
372
+ if (!collapsed || this._isIdentityTrigger(control) || control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")) {
338
373
  return;
339
374
  }
340
-
341
- // Bootstrap permits one plugin instance per element. A dropdown or
342
- // collapse trigger keeps that instance slot; its neutral <li> owns the
343
- // state-driven Sidebar tooltip instead.
344
375
  const anchor = this._tooltipAnchor(control);
345
376
  new Tooltip(anchor, {
346
- title: control.getAttribute("data-moo-sidebar-tooltip"),
377
+ title: control.getAttribute("data-sidebar-tooltip"),
347
378
  placement,
348
- container: "body",
349
- trigger: "hover focus",
379
+ // Bootstrap's default tooltip offset is 6px. The icon rail's
380
+ // boundary calculation still lets the tooltip box overlap the
381
+ // physical Sidebar edge at that distance, so keep an explicit 8px
382
+ // separation for both physical placements.
383
+ offset: [0, 8],
384
+ container: this._portalRoot(control),
385
+ trigger: "hover focus"
350
386
  });
351
387
  this._tooltipAnchors.add(anchor);
352
388
  });
353
389
  }
354
-
355
390
  _bindEvents() {
356
391
  this._listen(this._sidebar, "shown.bs.offcanvas", () => this._syncControls());
357
392
  this._listen(this._sidebar, "hidden.bs.offcanvas", () => {
@@ -385,31 +420,25 @@ export default class Sidebar {
385
420
  this._listen(this._document, "keydown", (event) => this._handleShortcut(event));
386
421
  this._listen(this._window, "resize", () => {
387
422
  this._closeFlyouts();
388
- this._element
389
- .querySelectorAll("[data-moo-sidebar-dropdown-positioned]")
390
- .forEach((item) => this._clearDropdownPosition(item));
423
+ this._element.querySelectorAll("[data-sidebar-dropdown-positioned]").forEach((item) => this._clearDropdownPosition(item));
391
424
  this._syncControls();
392
425
  this._syncTooltips();
393
426
  });
394
427
  }
395
-
396
428
  _dropdownControl(target) {
397
429
  if (!(target instanceof this._window.Element) || !this._element.contains(target)) {
398
430
  return null;
399
431
  }
400
- if (target.matches('[data-bs-toggle="dropdown"][data-moo-sidebar-tooltip]')) {
432
+ if (target.matches('[data-bs-toggle="dropdown"][data-sidebar-tooltip]')) {
401
433
  return target;
402
434
  }
403
435
  return target.querySelector?.(
404
- '[data-bs-toggle="dropdown"][data-moo-sidebar-tooltip]'
436
+ '[data-bs-toggle="dropdown"][data-sidebar-tooltip]'
405
437
  ) || null;
406
438
  }
407
-
408
439
  _handleControlClick(event) {
409
440
  const target = event.target;
410
- const control = target instanceof this._window.Element
411
- ? target.closest("[data-moo-sidebar-trigger], [data-moo-sidebar-rail]")
412
- : null;
441
+ const control = target instanceof this._window.Element ? target.closest("[data-sidebar-trigger], [data-sidebar-rail]") : null;
413
442
  if (!control || !this._element.contains(control)) {
414
443
  return;
415
444
  }
@@ -419,7 +448,7 @@ export default class Sidebar {
419
448
  this._toggle();
420
449
  return;
421
450
  }
422
- if (!control.matches("[data-moo-sidebar-trigger]")) {
451
+ if (!control.matches("[data-sidebar-trigger]")) {
423
452
  return;
424
453
  }
425
454
  const sidebar = this._document.getElementById(control.getAttribute("aria-controls"));
@@ -430,12 +459,9 @@ export default class Sidebar {
430
459
  this._offcanvas.toggle();
431
460
  }
432
461
  }
433
-
434
462
  _handleSubmenuClick(event) {
435
463
  const target = event.target;
436
- const trigger = target instanceof this._window.Element
437
- ? target.closest(".sidebar-menu-sub-trigger")
438
- : null;
464
+ const trigger = target instanceof this._window.Element ? target.closest(".sidebar-menu-sub-trigger") : null;
439
465
  const item = trigger?.closest(".sidebar-menu-item");
440
466
  if (!trigger || !item || !this._element.contains(item) || !this._isCollapsed()) {
441
467
  return;
@@ -448,7 +474,6 @@ export default class Sidebar {
448
474
  this._openFlyout(item);
449
475
  }
450
476
  }
451
-
452
477
  _handleOutsideFlyoutClick(event) {
453
478
  if (!this._flyout) {
454
479
  return;
@@ -459,27 +484,15 @@ export default class Sidebar {
459
484
  this._closeFlyouts();
460
485
  }
461
486
  }
462
-
463
487
  _handleShortcut(event) {
464
488
  const target = event.target;
465
- const isEditable =
466
- target instanceof this._window.Element &&
467
- (target.matches("input, textarea, select") || target.isContentEditable);
468
- if (
469
- event.defaultPrevented ||
470
- event.isComposing ||
471
- isEditable ||
472
- !this._config.keyboard ||
473
- !(event.metaKey || event.ctrlKey) ||
474
- event.key.toLowerCase() !== "b" ||
475
- !this._isDesktop()
476
- ) {
489
+ const isEditable = target instanceof this._window.Element && (target.matches("input, textarea, select") || target.isContentEditable);
490
+ if (event.defaultPrevented || event.isComposing || isEditable || !this._config.keyboard || !(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "b" || !this._isDesktop()) {
477
491
  return;
478
492
  }
479
- const preferred =
480
- this._document.querySelector(
481
- '[data-slot="sidebar-wrapper"][data-moo-sidebar-key]'
482
- ) || this._document.querySelector('[data-slot="sidebar-wrapper"]');
493
+ const preferred = this._document.querySelector(
494
+ '[data-slot="sidebar-wrapper"][data-sidebar-key]'
495
+ ) || this._document.querySelector('[data-slot="sidebar-wrapper"]');
483
496
  if (preferred !== this._element) {
484
497
  return;
485
498
  }
@@ -487,4 +500,9 @@ export default class Sidebar {
487
500
  this._closeFlyouts();
488
501
  this._toggle();
489
502
  }
490
- }
503
+ };
504
+ __name(_Sidebar, "Sidebar");
505
+ var Sidebar = _Sidebar;
506
+ export {
507
+ Sidebar as default
508
+ };