@wpmoo/ui 1.0.0-rc.5 → 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 (48) hide show
  1. package/ASSET_LICENSE.md +1 -1
  2. package/README.md +5 -6
  3. package/certification.json +1 -1
  4. package/dist/assets/css/moo-ui.css +7748 -8075
  5. package/dist/assets/css/moo-ui.min.css +2 -2
  6. package/dist/assets/css/moo.css +289 -285
  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 +193 -51
  16. package/dist/js/moo-ui.min.js +3 -3
  17. package/dist/js/sidebar.js +145 -170
  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/_datatable.scss +27 -3
  24. package/scss/components/_dialog.scss +16 -0
  25. package/scss/components/_dropdown.scss +38 -3
  26. package/scss/components/_switch.scss +13 -5
  27. package/scss/components/_table.scss +6 -0
  28. package/scss/components/_tabs.scss +1 -1
  29. package/scss/components/sidebar/_collapsed.scss +28 -19
  30. package/scss/components/sidebar/_inset.scss +14 -44
  31. package/scss/components/sidebar/_layout.scss +86 -11
  32. package/scss/foundations/_backdrop.scss +128 -0
  33. package/scss/foundations/_scope.scss +5 -0
  34. package/scss/mixins/_banner.scss +3 -0
  35. package/scss/moo-core.scss +6 -34
  36. package/scss/moo-ui.scss +6 -6
  37. package/scss/settings/_bootstrap_overrides.scss +4 -0
  38. package/scss/settings/_component_variables.scss +13 -1
  39. package/scss/settings/_options.scss +2 -0
  40. package/scss/settings/_palette.scss +1 -1
  41. package/scss/themes/_forms.scss +15 -0
  42. package/scss/themes/_root.scss +16 -0
  43. package/scss/themes/_standalone.scss +5 -0
  44. package/scss/themes/{_scoped_core.scss → _theme.scss} +1 -0
  45. package/scss/foundations/_core_state_layer.scss +0 -25
  46. package/scss/foundations/_overlay_backdrop.scss +0 -19
  47. package/scss/themes/_standalone_root.scss +0 -119
  48. /package/scss/foundations/{_core_global_primitives.scss → _globals.scss} +0 -0
@@ -1,19 +1,56 @@
1
1
  /*!
2
- * Moo UI sidebar.js v1.0.0-rc.5 (https://ui.wpmoo.org/)
2
+ * Moo UI sidebar.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
  */
6
- const instances = new WeakMap();
6
+ var __defProp = Object.defineProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+
9
+ // src/js/theme-owner.js
10
+ var OWNER_SELECTOR = '.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]';
11
+ var PORTAL_SELECTOR = "[data-moo-overlay-portal-host]";
12
+ function documentFor(node) {
13
+ if (node?.nodeType === 9) return node;
14
+ if (node?.ownerDocument) return node.ownerDocument;
15
+ return typeof document === "undefined" ? null : document;
16
+ }
17
+ __name(documentFor, "documentFor");
18
+ function isResolvedOwner(node) {
19
+ return Boolean(node?.matches?.(OWNER_SELECTOR));
20
+ }
21
+ __name(isResolvedOwner, "isResolvedOwner");
22
+ function ownerRootFor(node) {
23
+ const ownerDocument = documentFor(node);
24
+ if (node?.nodeType === 9 || node === ownerDocument?.body) {
25
+ return ownerDocument?.body?.firstElementChild || null;
26
+ }
27
+ return node;
28
+ }
29
+ __name(ownerRootFor, "ownerRootFor");
30
+ function findThemeOwner(node = typeof document === "undefined" ? null : document) {
31
+ const start = ownerRootFor(node);
32
+ if (!start) return null;
33
+ if (isResolvedOwner(start)) return start;
34
+ return start.closest?.(OWNER_SELECTOR) || null;
35
+ }
36
+ __name(findThemeOwner, "findThemeOwner");
37
+ function ownerPortalRoot(owner) {
38
+ if (!owner) return null;
39
+ return Array.from(owner.children || []).find(
40
+ (child) => child.matches?.(PORTAL_SELECTOR)
41
+ ) || owner;
42
+ }
43
+ __name(ownerPortalRoot, "ownerPortalRoot");
7
44
 
8
- export default class Sidebar {
45
+ // src/js/components/sidebar.js
46
+ var instances = /* @__PURE__ */ new WeakMap();
47
+ var _Sidebar = class _Sidebar {
9
48
  static getInstance(element) {
10
49
  return element?.nodeType === 1 ? instances.get(element) || null : null;
11
50
  }
12
-
13
51
  static getOrCreateInstance(element, config = {}) {
14
- return Sidebar.getInstance(element) || new Sidebar(element, config);
52
+ return _Sidebar.getInstance(element) || new _Sidebar(element, config);
15
53
  }
16
-
17
54
  constructor(element, config = {}) {
18
55
  if (element?.nodeType !== 1 || !element.matches('[data-slot="sidebar-wrapper"]')) {
19
56
  throw new TypeError("Sidebar requires a [data-slot=sidebar-wrapper] root.");
@@ -22,25 +59,24 @@ export default class Sidebar {
22
59
  if (existing) {
23
60
  return existing;
24
61
  }
25
-
26
62
  this._element = element;
27
63
  this._document = element.ownerDocument;
28
64
  this._window = this._document.defaultView;
29
- this._root = this._document.documentElement;
65
+ this._documentElement = this._document.documentElement;
66
+ this._root = findThemeOwner(element) || this._document.body || this._documentElement;
30
67
  this._sidebar = element.querySelector('[data-slot="sidebar"]');
31
68
  this._config = {
32
69
  breakpoint: "(min-width: 992px)",
33
70
  storagePrefix: "moo-sidebar:",
34
71
  keyboard: true,
35
- ...config,
72
+ ...config
36
73
  };
37
74
  this._listeners = [];
38
- this._tooltipAnchors = new Set();
75
+ this._tooltipAnchors = /* @__PURE__ */ new Set();
39
76
  this._flyout = null;
40
77
  this._flyoutOwner = null;
41
78
  this._offcanvas = null;
42
79
  this._offcanvasTrigger = null;
43
-
44
80
  instances.set(element, this);
45
81
  this._bindEvents();
46
82
  this._restoreState();
@@ -48,7 +84,6 @@ export default class Sidebar {
48
84
  this._element.setAttribute("data-sidebar-ready", "");
49
85
  this._observeDirection();
50
86
  }
51
-
52
87
  dispose() {
53
88
  this._listeners.forEach(({ target, type, handler, options }) => {
54
89
  target.removeEventListener(type, handler, options);
@@ -56,48 +91,41 @@ export default class Sidebar {
56
91
  this._listeners = [];
57
92
  this._directionObserver?.disconnect();
58
93
  this._closeFlyouts();
59
- this._element
60
- .querySelectorAll("[data-sidebar-dropdown-positioned]")
61
- .forEach((item) => this._clearDropdownPosition(item));
62
- this._element
63
- .querySelectorAll("[data-sidebar-tooltip]")
64
- .forEach((control) => this._disposeTooltip(control));
94
+ this._element.querySelectorAll("[data-sidebar-dropdown-positioned]").forEach((item) => this._clearDropdownPosition(item));
95
+ this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => this._disposeTooltip(control));
65
96
  this._offcanvas?.dispose();
66
97
  this._offcanvas = null;
67
98
  this._offcanvasTrigger = null;
68
99
  this._element.removeAttribute("data-sidebar-ready");
69
100
  instances.delete(this._element);
70
101
  }
71
-
72
102
  _listen(target, type, handler, options) {
73
103
  target?.addEventListener(type, handler, options);
74
104
  if (target) {
75
105
  this._listeners.push({ target, type, handler, options });
76
106
  }
77
107
  }
78
-
79
108
  _bootstrap(name) {
80
109
  return this._window.bootstrap?.[name] || null;
81
110
  }
82
-
111
+ _portalRoot(trigger = this._element) {
112
+ return ownerPortalRoot(findThemeOwner(trigger)) || this._document.body || this._documentElement;
113
+ }
83
114
  _isDesktop() {
84
115
  return this._window.matchMedia(this._config.breakpoint).matches;
85
116
  }
86
-
87
117
  _isCollapsed() {
88
118
  return this._isDesktop() && this._element.dataset.sidebarState === "collapsed";
89
119
  }
90
-
91
120
  _trigger(name, detail = {}) {
92
121
  return this._element.dispatchEvent(
93
122
  new this._window.CustomEvent(`${name}.moo.sidebar`, {
94
123
  bubbles: true,
95
124
  cancelable: name === "show" || name === "hide",
96
- detail,
125
+ detail
97
126
  })
98
127
  );
99
128
  }
100
-
101
129
  _observeDirection() {
102
130
  const Observer = this._window.MutationObserver;
103
131
  if (!Observer) {
@@ -107,12 +135,11 @@ export default class Sidebar {
107
135
  this._closeFlyouts();
108
136
  this._syncTooltips();
109
137
  });
110
- this._directionObserver.observe(this._root, {
138
+ this._directionObserver.observe(this._documentElement, {
111
139
  attributes: true,
112
- attributeFilter: ["dir"],
140
+ attributeFilter: ["dir"]
113
141
  });
114
142
  }
115
-
116
143
  _restoreState() {
117
144
  const key = this._element.dataset.sidebarKey;
118
145
  let stored = null;
@@ -123,25 +150,15 @@ export default class Sidebar {
123
150
  stored = null;
124
151
  }
125
152
  }
126
- const initial =
127
- stored === "collapsed" || stored === "expanded"
128
- ? stored
129
- : this._element.dataset.sidebarState === "collapsed"
130
- ? "collapsed"
131
- : "expanded";
153
+ const initial = stored === "collapsed" || stored === "expanded" ? stored : this._element.dataset.sidebarState === "collapsed" ? "collapsed" : "expanded";
132
154
  this._setState(initial, false, false);
133
155
  }
134
-
135
156
  _scrollActiveItemIntoView() {
136
157
  const activeRoute = this._element.querySelector(
137
- '[data-slot="sidebar-content"] a[data-slot="sidebar-menu-button"][aria-current="page"], ' +
138
- '[data-slot="sidebar-content"] a.sidebar-menu-button[aria-current="page"], ' +
139
- '[data-slot="sidebar-content"] a.sidebar-menu-button.active'
158
+ '[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'
140
159
  );
141
160
  const active = activeRoute || this._element.querySelector(
142
- '[data-slot="sidebar-content"] [data-slot="sidebar-menu-button"][aria-current="page"], ' +
143
- '[data-slot="sidebar-content"] .sidebar-menu-button[aria-current="page"], ' +
144
- '[data-slot="sidebar-content"] .sidebar-menu-button.active'
161
+ '[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'
145
162
  );
146
163
  const content = active?.closest?.('[data-slot="sidebar-content"]');
147
164
  if (!active || !content || content.clientHeight <= 0) {
@@ -151,23 +168,16 @@ export default class Sidebar {
151
168
  if (maxScrollTop <= 0) {
152
169
  return;
153
170
  }
154
-
155
171
  const contentRect = content.getBoundingClientRect();
156
172
  const activeRect = active.getBoundingClientRect();
157
173
  if (contentRect.height <= 0 || activeRect.height <= 0) {
158
174
  return;
159
175
  }
160
-
161
- const targetScrollTop =
162
- content.scrollTop +
163
- activeRect.top -
164
- contentRect.top -
165
- (content.clientHeight - activeRect.height) / 2;
176
+ const targetScrollTop = content.scrollTop + activeRect.top - contentRect.top - (content.clientHeight - activeRect.height) / 2;
166
177
  content.scrollTop = Math.round(
167
178
  Math.min(Math.max(targetScrollTop, 0), maxScrollTop)
168
179
  );
169
180
  }
170
-
171
181
  _setState(state, persist = true, emit = true) {
172
182
  const next = state === "collapsed" ? "collapsed" : "expanded";
173
183
  const previous = this._element.dataset.sidebarState;
@@ -180,36 +190,26 @@ export default class Sidebar {
180
190
  try {
181
191
  this._window.localStorage.setItem(this._config.storagePrefix + key, next);
182
192
  } catch (_) {
183
- /* Storage is best-effort in restricted browsing contexts. */
184
193
  }
185
194
  }
186
195
  this._syncControls();
187
196
  if (next === "expanded") {
188
- this._element
189
- .querySelectorAll(".sidebar-menu-item")
190
- .forEach((item) => this._resetFlyoutTrigger(item));
197
+ this._element.querySelectorAll(".sidebar-menu-item").forEach((item) => this._resetFlyoutTrigger(item));
191
198
  }
192
199
  this._syncTooltips();
193
200
  if (emit && previous !== next) {
194
201
  this._trigger("change", { state: next, previousState: previous || null });
195
202
  }
196
203
  }
197
-
198
204
  _toggle() {
199
205
  this._setState(
200
206
  this._element.dataset.sidebarState === "collapsed" ? "expanded" : "collapsed"
201
207
  );
202
208
  }
203
-
204
209
  _syncControls() {
205
- const expanded = this._isDesktop()
206
- ? this._element.dataset.sidebarState === "expanded"
207
- : this._sidebar?.classList.contains("show") || false;
208
- this._element
209
- .querySelectorAll("[data-sidebar-trigger], [data-sidebar-rail]")
210
- .forEach((control) => control.setAttribute("aria-expanded", String(expanded)));
210
+ const expanded = this._isDesktop() ? this._element.dataset.sidebarState === "expanded" : this._sidebar?.classList.contains("show") || false;
211
+ this._element.querySelectorAll("[data-sidebar-trigger], [data-sidebar-rail]").forEach((control) => control.setAttribute("aria-expanded", String(expanded)));
211
212
  }
212
-
213
213
  _resetFlyoutTrigger(item, expanded = null) {
214
214
  const trigger = item?.querySelector(":scope > .sidebar-menu-sub-trigger");
215
215
  const submenu = item?.querySelector(":scope > .sidebar-menu-sub");
@@ -220,68 +220,75 @@ export default class Sidebar {
220
220
  );
221
221
  }
222
222
  }
223
-
224
223
  _clearDropdownPosition(control) {
225
- const item = control?.matches?.(".sidebar-menu-item")
226
- ? control
227
- : control?.closest?.(".sidebar-menu-item");
224
+ const item = control?.matches?.(".sidebar-menu-item") ? control : control?.closest?.(".sidebar-menu-item");
228
225
  if (!item) {
229
226
  return;
230
227
  }
231
228
  delete item.dataset.sidebarDropdownPositioned;
232
229
  item.style.removeProperty("--moo-sidebar-dropdown-block-start");
233
- item.style.removeProperty("--moo-sidebar-dropdown-inline-start");
230
+ item.style.removeProperty("--moo-sidebar-dropdown-block-end");
231
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
232
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
234
233
  }
235
-
236
234
  _closeDropdowns(exceptControl = null) {
237
235
  const Dropdown = this._bootstrap("Dropdown");
238
- this._element
239
- .querySelectorAll('[data-bs-toggle="dropdown"][aria-expanded="true"]')
240
- .forEach((control) => {
241
- if (control === exceptControl) {
242
- return;
243
- }
244
- if (Dropdown) {
245
- Dropdown.getOrCreateInstance(control).hide();
246
- } else {
247
- control.setAttribute("aria-expanded", "false");
248
- control
249
- .closest(".dropdown")
250
- ?.querySelector(".dropdown-menu.show")
251
- ?.classList.remove("show");
252
- this._clearDropdownPosition(control);
253
- }
254
- });
236
+ this._element.querySelectorAll('[data-bs-toggle="dropdown"][aria-expanded="true"]').forEach((control) => {
237
+ if (control === exceptControl) {
238
+ return;
239
+ }
240
+ if (Dropdown) {
241
+ Dropdown.getOrCreateInstance(control).hide();
242
+ } else {
243
+ control.setAttribute("aria-expanded", "false");
244
+ control.closest(".dropdown")?.querySelector(".dropdown-menu.show")?.classList.remove("show");
245
+ this._clearDropdownPosition(control);
246
+ }
247
+ });
255
248
  }
256
-
257
249
  _positionDropdown(control) {
258
250
  const item = control?.closest?.(".sidebar-menu-item");
259
- const isHeaderWorkspace =
260
- control?.classList.contains("sidebar-menu-button--workspace") &&
261
- control.closest('[data-slot="sidebar-header"]');
262
- if (!control || !item || !isHeaderWorkspace || !this._isDesktop()) {
251
+ const isHeaderWorkspace = control?.classList.contains("sidebar-menu-button--workspace") && control.closest('[data-slot="sidebar-header"]');
252
+ const isFooterAccount = control?.classList.contains("sidebar-menu-button--account") && control.closest('[data-slot="sidebar-footer"]');
253
+ if (!control || !item || !(isHeaderWorkspace || isFooterAccount) || !this._isDesktop()) {
263
254
  this._clearDropdownPosition(control);
264
255
  return;
265
256
  }
266
-
267
257
  this._closeFlyouts();
268
258
  const rect = control.getBoundingClientRect();
269
259
  const gap = 4;
270
- const inlineStart =
271
- this._root.dir === "rtl"
272
- ? this._window.innerWidth - rect.left + gap
273
- : rect.right + gap;
274
- item.style.setProperty(
275
- "--moo-sidebar-dropdown-block-start",
276
- `${Math.round(rect.bottom + gap)}px`
277
- );
278
- item.style.setProperty(
279
- "--moo-sidebar-dropdown-inline-start",
280
- `${Math.round(inlineStart)}px`
281
- );
260
+ const sidebar = control.closest('[data-slot="sidebar"]');
261
+ const side = sidebar?.dataset.side || "left";
262
+ if (side === "right") {
263
+ const right = this._window.innerWidth - rect.left + gap;
264
+ item.style.setProperty(
265
+ "--moo-sidebar-dropdown-right",
266
+ `${Math.round(right)}px`
267
+ );
268
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
269
+ } else {
270
+ const left = rect.right + gap;
271
+ item.style.setProperty(
272
+ "--moo-sidebar-dropdown-left",
273
+ `${Math.round(left)}px`
274
+ );
275
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
276
+ }
277
+ if (isHeaderWorkspace) {
278
+ item.style.setProperty(
279
+ "--moo-sidebar-dropdown-block-start",
280
+ `${Math.round(rect.bottom + gap)}px`
281
+ );
282
+ item.style.removeProperty("--moo-sidebar-dropdown-block-end");
283
+ } else {
284
+ item.style.setProperty(
285
+ "--moo-sidebar-dropdown-block-end",
286
+ `${Math.round(this._window.innerHeight - rect.top + gap)}px`
287
+ );
288
+ item.style.removeProperty("--moo-sidebar-dropdown-block-start");
289
+ }
282
290
  item.dataset.sidebarDropdownPositioned = "";
283
291
  }
284
-
285
292
  _removeFlyoutPortal() {
286
293
  this._flyout?.remove();
287
294
  if (this._flyoutOwner) {
@@ -290,17 +297,15 @@ export default class Sidebar {
290
297
  this._flyout = null;
291
298
  this._flyoutOwner = null;
292
299
  }
293
-
294
300
  _closeFlyouts() {
295
301
  this._removeFlyoutPortal();
296
302
  this._element.querySelectorAll(".sidebar-menu-item--flyout-open").forEach((item) => {
297
303
  item.classList.remove("sidebar-menu-item--flyout-open");
298
304
  item.style.removeProperty("--moo-sidebar-flyout-block-start");
299
- item.style.removeProperty("--moo-sidebar-flyout-inline-start");
305
+ item.style.removeProperty("--moo-sidebar-flyout-left");
300
306
  this._resetFlyoutTrigger(item, false);
301
307
  });
302
308
  }
303
-
304
309
  _openFlyout(item) {
305
310
  const submenu = item?.querySelector(":scope > .sidebar-menu-sub");
306
311
  const trigger = item?.querySelector(":scope > .sidebar-menu-sub-trigger");
@@ -310,15 +315,12 @@ export default class Sidebar {
310
315
  if (this._flyoutOwner === item && this._flyout) {
311
316
  return;
312
317
  }
313
-
314
318
  this._closeDropdowns();
315
319
  this._closeFlyouts();
316
320
  const rect = item.getBoundingClientRect();
321
+ const sidebar = this._sidebar;
322
+ const sidebarRect = sidebar?.getBoundingClientRect() || rect;
317
323
  const gap = 4;
318
- const inlineStart =
319
- this._root.dir === "rtl"
320
- ? this._window.innerWidth - rect.left + gap
321
- : rect.right + gap;
322
324
  const flyout = submenu.cloneNode(true);
323
325
  flyout.removeAttribute("id");
324
326
  flyout.classList.remove("collapse", "show", "collapsing");
@@ -326,29 +328,23 @@ export default class Sidebar {
326
328
  flyout.dataset.sidebarFlyout = "";
327
329
  flyout.removeAttribute("style");
328
330
  flyout.style.setProperty("--moo-sidebar-flyout-block-start", `${Math.round(rect.top)}px`);
329
- flyout.style.setProperty(
330
- "--moo-sidebar-flyout-inline-start",
331
- `${Math.round(inlineStart)}px`
332
- );
333
- const root = this._element.closest(".moo-ui") || this._document.body;
334
- root.appendChild(flyout);
331
+ const side = sidebar?.dataset.side || "left";
332
+ this._portalRoot(item).appendChild(flyout);
333
+ const flyoutWidth = flyout.getBoundingClientRect().width;
334
+ const left = side === "right" ? sidebarRect.left - flyoutWidth - gap : sidebarRect.right + gap;
335
+ const boundedLeft = Math.max(0, Math.min(this._window.innerWidth - flyoutWidth, left));
336
+ flyout.style.setProperty("--moo-sidebar-flyout-left", `${Math.round(boundedLeft)}px`);
335
337
  this._flyout = flyout;
336
338
  this._flyoutOwner = item;
337
339
  item.classList.add("sidebar-menu-item--flyout-open");
338
340
  trigger?.setAttribute("aria-expanded", "true");
339
341
  }
340
-
341
342
  _tooltipAnchor(control) {
342
343
  return control.closest("li") || control;
343
344
  }
344
-
345
345
  _isIdentityTrigger(control) {
346
- return (
347
- control.closest(".sidebar-menu-item--account") ||
348
- control.classList.contains("sidebar-menu-button--workspace")
349
- );
346
+ return control.closest(".sidebar-menu-item--account") || control.classList.contains("sidebar-menu-button--workspace");
350
347
  }
351
-
352
348
  _disposeTooltip(control) {
353
349
  const Tooltip = this._bootstrap("Tooltip");
354
350
  if (!Tooltip || !control) {
@@ -362,38 +358,34 @@ export default class Sidebar {
362
358
  anchor.removeAttribute("data-bs-original-title");
363
359
  anchor.removeAttribute("aria-describedby");
364
360
  }
365
-
366
361
  _syncTooltips() {
367
362
  const Tooltip = this._bootstrap("Tooltip");
368
363
  if (!Tooltip) {
369
364
  return;
370
365
  }
371
366
  const collapsed = this._isCollapsed();
372
- const placement = this._root.dir === "rtl" ? "left" : "right";
367
+ const side = this._sidebar?.dataset.side || "left";
368
+ const placement = side === "right" ? "left" : "right";
373
369
  this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => {
374
370
  this._disposeTooltip(control);
375
- if (
376
- !collapsed ||
377
- this._isIdentityTrigger(control) ||
378
- control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")
379
- ) {
371
+ if (!collapsed || this._isIdentityTrigger(control) || control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")) {
380
372
  return;
381
373
  }
382
-
383
- // Bootstrap permits one plugin instance per element. A dropdown or
384
- // collapse trigger keeps that instance slot; its neutral <li> owns the
385
- // state-driven Sidebar tooltip instead.
386
374
  const anchor = this._tooltipAnchor(control);
387
375
  new Tooltip(anchor, {
388
376
  title: control.getAttribute("data-sidebar-tooltip"),
389
377
  placement,
390
- container: "body",
391
- trigger: "hover focus",
378
+ // Bootstrap's default tooltip offset is 6px. The icon rail's
379
+ // boundary calculation still lets the tooltip box overlap the
380
+ // physical Sidebar edge at that distance, so keep an explicit 8px
381
+ // separation for both physical placements.
382
+ offset: [0, 8],
383
+ container: this._portalRoot(control),
384
+ trigger: "hover focus"
392
385
  });
393
386
  this._tooltipAnchors.add(anchor);
394
387
  });
395
388
  }
396
-
397
389
  _bindEvents() {
398
390
  this._listen(this._sidebar, "shown.bs.offcanvas", () => this._syncControls());
399
391
  this._listen(this._sidebar, "hidden.bs.offcanvas", () => {
@@ -427,14 +419,11 @@ export default class Sidebar {
427
419
  this._listen(this._document, "keydown", (event) => this._handleShortcut(event));
428
420
  this._listen(this._window, "resize", () => {
429
421
  this._closeFlyouts();
430
- this._element
431
- .querySelectorAll("[data-sidebar-dropdown-positioned]")
432
- .forEach((item) => this._clearDropdownPosition(item));
422
+ this._element.querySelectorAll("[data-sidebar-dropdown-positioned]").forEach((item) => this._clearDropdownPosition(item));
433
423
  this._syncControls();
434
424
  this._syncTooltips();
435
425
  });
436
426
  }
437
-
438
427
  _dropdownControl(target) {
439
428
  if (!(target instanceof this._window.Element) || !this._element.contains(target)) {
440
429
  return null;
@@ -446,12 +435,9 @@ export default class Sidebar {
446
435
  '[data-bs-toggle="dropdown"][data-sidebar-tooltip]'
447
436
  ) || null;
448
437
  }
449
-
450
438
  _handleControlClick(event) {
451
439
  const target = event.target;
452
- const control = target instanceof this._window.Element
453
- ? target.closest("[data-sidebar-trigger], [data-sidebar-rail]")
454
- : null;
440
+ const control = target instanceof this._window.Element ? target.closest("[data-sidebar-trigger], [data-sidebar-rail]") : null;
455
441
  if (!control || !this._element.contains(control)) {
456
442
  return;
457
443
  }
@@ -472,12 +458,9 @@ export default class Sidebar {
472
458
  this._offcanvas.toggle();
473
459
  }
474
460
  }
475
-
476
461
  _handleSubmenuClick(event) {
477
462
  const target = event.target;
478
- const trigger = target instanceof this._window.Element
479
- ? target.closest(".sidebar-menu-sub-trigger")
480
- : null;
463
+ const trigger = target instanceof this._window.Element ? target.closest(".sidebar-menu-sub-trigger") : null;
481
464
  const item = trigger?.closest(".sidebar-menu-item");
482
465
  if (!trigger || !item || !this._element.contains(item) || !this._isCollapsed()) {
483
466
  return;
@@ -490,7 +473,6 @@ export default class Sidebar {
490
473
  this._openFlyout(item);
491
474
  }
492
475
  }
493
-
494
476
  _handleOutsideFlyoutClick(event) {
495
477
  if (!this._flyout) {
496
478
  return;
@@ -501,27 +483,15 @@ export default class Sidebar {
501
483
  this._closeFlyouts();
502
484
  }
503
485
  }
504
-
505
486
  _handleShortcut(event) {
506
487
  const target = event.target;
507
- const isEditable =
508
- target instanceof this._window.Element &&
509
- (target.matches("input, textarea, select") || target.isContentEditable);
510
- if (
511
- event.defaultPrevented ||
512
- event.isComposing ||
513
- isEditable ||
514
- !this._config.keyboard ||
515
- !(event.metaKey || event.ctrlKey) ||
516
- event.key.toLowerCase() !== "b" ||
517
- !this._isDesktop()
518
- ) {
488
+ const isEditable = target instanceof this._window.Element && (target.matches("input, textarea, select") || target.isContentEditable);
489
+ if (event.defaultPrevented || event.isComposing || isEditable || !this._config.keyboard || !(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "b" || !this._isDesktop()) {
519
490
  return;
520
491
  }
521
- const preferred =
522
- this._document.querySelector(
523
- '[data-slot="sidebar-wrapper"][data-sidebar-key]'
524
- ) || this._document.querySelector('[data-slot="sidebar-wrapper"]');
492
+ const preferred = this._document.querySelector(
493
+ '[data-slot="sidebar-wrapper"][data-sidebar-key]'
494
+ ) || this._document.querySelector('[data-slot="sidebar-wrapper"]');
525
495
  if (preferred !== this._element) {
526
496
  return;
527
497
  }
@@ -529,4 +499,9 @@ export default class Sidebar {
529
499
  this._closeFlyouts();
530
500
  this._toggle();
531
501
  }
532
- }
502
+ };
503
+ __name(_Sidebar, "Sidebar");
504
+ var Sidebar = _Sidebar;
505
+ export {
506
+ Sidebar as default
507
+ };
package/dist/js/slider.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI slider.js v1.0.0-rc.5 (https://ui.wpmoo.org/)
2
+ * Moo UI slider.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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/ui",
3
- "version": "1.0.0-rc.5",
3
+ "version": "1.0.0-rc.7",
4
4
  "description": "Bootstrap markup. shadcn feel. A frontend-framework-free component system for server-rendered apps.",
5
5
  "license": "MIT",
6
6
  "author": "WPMoo (https://wpmoo.org)",
@@ -15,13 +15,13 @@
15
15
  "private": false,
16
16
  "type": "module",
17
17
  "scripts": {
18
- "build": ".venv/bin/python build.py",
19
- "dev": ".venv/bin/python dev.py",
20
- "test": ".venv/bin/python scripts/run-test-tier.py run release",
21
- "test:quick": ".venv/bin/python scripts/run-test-tier.py run quick",
22
- "test:browser-smoke": ".venv/bin/python scripts/run-test-tier.py run browser-smoke",
23
- "test:browser-full": ".venv/bin/python scripts/run-test-tier.py run browser-full",
24
- "test:release": ".venv/bin/python scripts/run-test-tier.py run release",
18
+ "build": ".venv/bin/python3 build.py",
19
+ "dev": ".venv/bin/python3 dev.py",
20
+ "test": ".venv/bin/python3 scripts/run-test-tier.py run release",
21
+ "test:quick": ".venv/bin/python3 scripts/run-test-tier.py run quick",
22
+ "test:browser-smoke": ".venv/bin/python3 scripts/run-test-tier.py run browser-smoke",
23
+ "test:browser-full": ".venv/bin/python3 scripts/run-test-tier.py run browser-full",
24
+ "test:release": ".venv/bin/python3 scripts/run-test-tier.py run release",
25
25
  "deploy": "wrangler deploy",
26
26
  "preview": "wrangler dev"
27
27
  },
@@ -24,10 +24,9 @@ $moo-alert-dismissible-close-space: $btn-close-width + ($btn-close-padding-x * 2
24
24
  .alert-dismissible .btn-close {
25
25
  top: auto;
26
26
  right: auto;
27
- inset-block-start: 50%;
27
+ inset-block-start: var(--bs-alert-padding-y);
28
28
  inset-inline-end: var(--bs-alert-padding-x);
29
29
  padding: $btn-close-padding-y $btn-close-padding-x;
30
- transform: translateY(-50%);
31
30
  }
32
31
 
33
32
  // The reference's destructive alert keeps the same neutral surface and
@@ -47,11 +47,11 @@
47
47
  border: var(--bs-card-border-width) solid var(--bs-card-border-color);
48
48
  box-shadow: var(--bs-card-box-shadow);
49
49
 
50
- &[data-bs-theme="dark"],
51
- :where([data-bs-theme="dark"]) &:not([data-bs-theme="light"]):not([data-bs-theme="light"] *) {
52
- --bs-card-bg: color-mix(in srgb, var(--bs-secondary-bg) #{$moo-card-bg-mix-dark}, var(--bs-body-bg));
53
- --moo-card-footer-bg: #{$moo-card-footer-bg-dark};
54
- }
50
+ }
51
+
52
+ :scope[data-bs-theme="dark"] .card {
53
+ --bs-card-bg: color-mix(in srgb, var(--bs-secondary-bg) #{$moo-card-bg-mix-dark}, var(--bs-body-bg));
54
+ --moo-card-footer-bg: #{$moo-card-footer-bg-dark};
55
55
  }
56
56
 
57
57
  .card--sm {