@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
@@ -1,19 +1,56 @@
1
1
  /*!
2
- * Moo UI sidebar.js v1.0.0-rc.6 (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,79 +220,59 @@ 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
230
  item.style.removeProperty("--moo-sidebar-dropdown-block-end");
234
- item.style.removeProperty("--moo-sidebar-dropdown-inline-start");
235
- item.style.removeProperty("--moo-sidebar-dropdown-inline-end");
231
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
232
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
236
233
  }
237
-
238
234
  _closeDropdowns(exceptControl = null) {
239
235
  const Dropdown = this._bootstrap("Dropdown");
240
- this._element
241
- .querySelectorAll('[data-bs-toggle="dropdown"][aria-expanded="true"]')
242
- .forEach((control) => {
243
- if (control === exceptControl) {
244
- return;
245
- }
246
- if (Dropdown) {
247
- Dropdown.getOrCreateInstance(control).hide();
248
- } else {
249
- control.setAttribute("aria-expanded", "false");
250
- control
251
- .closest(".dropdown")
252
- ?.querySelector(".dropdown-menu.show")
253
- ?.classList.remove("show");
254
- this._clearDropdownPosition(control);
255
- }
256
- });
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
+ });
257
248
  }
258
-
259
249
  _positionDropdown(control) {
260
250
  const item = control?.closest?.(".sidebar-menu-item");
261
- const isHeaderWorkspace =
262
- control?.classList.contains("sidebar-menu-button--workspace") &&
263
- control.closest('[data-slot="sidebar-header"]');
264
- const isFooterAccount =
265
- control?.classList.contains("sidebar-menu-button--account") &&
266
- control.closest('[data-slot="sidebar-footer"]');
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"]');
267
253
  if (!control || !item || !(isHeaderWorkspace || isFooterAccount) || !this._isDesktop()) {
268
254
  this._clearDropdownPosition(control);
269
255
  return;
270
256
  }
271
-
272
257
  this._closeFlyouts();
273
258
  const rect = control.getBoundingClientRect();
274
259
  const gap = 4;
275
260
  const sidebar = control.closest('[data-slot="sidebar"]');
276
261
  const side = sidebar?.dataset.side || "left";
277
- const isRtl = this._root.dir === "rtl";
278
262
  if (side === "right") {
279
- const inlineEnd = isRtl
280
- ? rect.right + gap
281
- : this._window.innerWidth - rect.left + gap;
263
+ const right = this._window.innerWidth - rect.left + gap;
282
264
  item.style.setProperty(
283
- "--moo-sidebar-dropdown-inline-end",
284
- `${Math.round(inlineEnd)}px`
265
+ "--moo-sidebar-dropdown-right",
266
+ `${Math.round(right)}px`
285
267
  );
286
- item.style.removeProperty("--moo-sidebar-dropdown-inline-start");
268
+ item.style.removeProperty("--moo-sidebar-dropdown-left");
287
269
  } else {
288
- const inlineStart = isRtl
289
- ? this._window.innerWidth - rect.left + gap
290
- : rect.right + gap;
270
+ const left = rect.right + gap;
291
271
  item.style.setProperty(
292
- "--moo-sidebar-dropdown-inline-start",
293
- `${Math.round(inlineStart)}px`
272
+ "--moo-sidebar-dropdown-left",
273
+ `${Math.round(left)}px`
294
274
  );
295
- item.style.removeProperty("--moo-sidebar-dropdown-inline-end");
275
+ item.style.removeProperty("--moo-sidebar-dropdown-right");
296
276
  }
297
277
  if (isHeaderWorkspace) {
298
278
  item.style.setProperty(
@@ -309,7 +289,6 @@ export default class Sidebar {
309
289
  }
310
290
  item.dataset.sidebarDropdownPositioned = "";
311
291
  }
312
-
313
292
  _removeFlyoutPortal() {
314
293
  this._flyout?.remove();
315
294
  if (this._flyoutOwner) {
@@ -318,17 +297,15 @@ export default class Sidebar {
318
297
  this._flyout = null;
319
298
  this._flyoutOwner = null;
320
299
  }
321
-
322
300
  _closeFlyouts() {
323
301
  this._removeFlyoutPortal();
324
302
  this._element.querySelectorAll(".sidebar-menu-item--flyout-open").forEach((item) => {
325
303
  item.classList.remove("sidebar-menu-item--flyout-open");
326
304
  item.style.removeProperty("--moo-sidebar-flyout-block-start");
327
- item.style.removeProperty("--moo-sidebar-flyout-inline-start");
305
+ item.style.removeProperty("--moo-sidebar-flyout-left");
328
306
  this._resetFlyoutTrigger(item, false);
329
307
  });
330
308
  }
331
-
332
309
  _openFlyout(item) {
333
310
  const submenu = item?.querySelector(":scope > .sidebar-menu-sub");
334
311
  const trigger = item?.querySelector(":scope > .sidebar-menu-sub-trigger");
@@ -338,15 +315,12 @@ export default class Sidebar {
338
315
  if (this._flyoutOwner === item && this._flyout) {
339
316
  return;
340
317
  }
341
-
342
318
  this._closeDropdowns();
343
319
  this._closeFlyouts();
344
320
  const rect = item.getBoundingClientRect();
321
+ const sidebar = this._sidebar;
322
+ const sidebarRect = sidebar?.getBoundingClientRect() || rect;
345
323
  const gap = 4;
346
- const inlineStart =
347
- this._root.dir === "rtl"
348
- ? this._window.innerWidth - rect.left + gap
349
- : rect.right + gap;
350
324
  const flyout = submenu.cloneNode(true);
351
325
  flyout.removeAttribute("id");
352
326
  flyout.classList.remove("collapse", "show", "collapsing");
@@ -354,29 +328,23 @@ export default class Sidebar {
354
328
  flyout.dataset.sidebarFlyout = "";
355
329
  flyout.removeAttribute("style");
356
330
  flyout.style.setProperty("--moo-sidebar-flyout-block-start", `${Math.round(rect.top)}px`);
357
- flyout.style.setProperty(
358
- "--moo-sidebar-flyout-inline-start",
359
- `${Math.round(inlineStart)}px`
360
- );
361
- const root = this._element.closest(".moo-ui") || this._document.body;
362
- 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`);
363
337
  this._flyout = flyout;
364
338
  this._flyoutOwner = item;
365
339
  item.classList.add("sidebar-menu-item--flyout-open");
366
340
  trigger?.setAttribute("aria-expanded", "true");
367
341
  }
368
-
369
342
  _tooltipAnchor(control) {
370
343
  return control.closest("li") || control;
371
344
  }
372
-
373
345
  _isIdentityTrigger(control) {
374
- return (
375
- control.closest(".sidebar-menu-item--account") ||
376
- control.classList.contains("sidebar-menu-button--workspace")
377
- );
346
+ return control.closest(".sidebar-menu-item--account") || control.classList.contains("sidebar-menu-button--workspace");
378
347
  }
379
-
380
348
  _disposeTooltip(control) {
381
349
  const Tooltip = this._bootstrap("Tooltip");
382
350
  if (!Tooltip || !control) {
@@ -390,38 +358,34 @@ export default class Sidebar {
390
358
  anchor.removeAttribute("data-bs-original-title");
391
359
  anchor.removeAttribute("aria-describedby");
392
360
  }
393
-
394
361
  _syncTooltips() {
395
362
  const Tooltip = this._bootstrap("Tooltip");
396
363
  if (!Tooltip) {
397
364
  return;
398
365
  }
399
366
  const collapsed = this._isCollapsed();
400
- const placement = this._root.dir === "rtl" ? "left" : "right";
367
+ const side = this._sidebar?.dataset.side || "left";
368
+ const placement = side === "right" ? "left" : "right";
401
369
  this._element.querySelectorAll("[data-sidebar-tooltip]").forEach((control) => {
402
370
  this._disposeTooltip(control);
403
- if (
404
- !collapsed ||
405
- this._isIdentityTrigger(control) ||
406
- control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")
407
- ) {
371
+ if (!collapsed || this._isIdentityTrigger(control) || control.closest(".sidebar-menu-item")?.querySelector(":scope > .sidebar-menu-sub")) {
408
372
  return;
409
373
  }
410
-
411
- // Bootstrap permits one plugin instance per element. A dropdown or
412
- // collapse trigger keeps that instance slot; its neutral <li> owns the
413
- // state-driven Sidebar tooltip instead.
414
374
  const anchor = this._tooltipAnchor(control);
415
375
  new Tooltip(anchor, {
416
376
  title: control.getAttribute("data-sidebar-tooltip"),
417
377
  placement,
418
- container: "body",
419
- 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"
420
385
  });
421
386
  this._tooltipAnchors.add(anchor);
422
387
  });
423
388
  }
424
-
425
389
  _bindEvents() {
426
390
  this._listen(this._sidebar, "shown.bs.offcanvas", () => this._syncControls());
427
391
  this._listen(this._sidebar, "hidden.bs.offcanvas", () => {
@@ -455,14 +419,11 @@ export default class Sidebar {
455
419
  this._listen(this._document, "keydown", (event) => this._handleShortcut(event));
456
420
  this._listen(this._window, "resize", () => {
457
421
  this._closeFlyouts();
458
- this._element
459
- .querySelectorAll("[data-sidebar-dropdown-positioned]")
460
- .forEach((item) => this._clearDropdownPosition(item));
422
+ this._element.querySelectorAll("[data-sidebar-dropdown-positioned]").forEach((item) => this._clearDropdownPosition(item));
461
423
  this._syncControls();
462
424
  this._syncTooltips();
463
425
  });
464
426
  }
465
-
466
427
  _dropdownControl(target) {
467
428
  if (!(target instanceof this._window.Element) || !this._element.contains(target)) {
468
429
  return null;
@@ -474,12 +435,9 @@ export default class Sidebar {
474
435
  '[data-bs-toggle="dropdown"][data-sidebar-tooltip]'
475
436
  ) || null;
476
437
  }
477
-
478
438
  _handleControlClick(event) {
479
439
  const target = event.target;
480
- const control = target instanceof this._window.Element
481
- ? target.closest("[data-sidebar-trigger], [data-sidebar-rail]")
482
- : null;
440
+ const control = target instanceof this._window.Element ? target.closest("[data-sidebar-trigger], [data-sidebar-rail]") : null;
483
441
  if (!control || !this._element.contains(control)) {
484
442
  return;
485
443
  }
@@ -500,12 +458,9 @@ export default class Sidebar {
500
458
  this._offcanvas.toggle();
501
459
  }
502
460
  }
503
-
504
461
  _handleSubmenuClick(event) {
505
462
  const target = event.target;
506
- const trigger = target instanceof this._window.Element
507
- ? target.closest(".sidebar-menu-sub-trigger")
508
- : null;
463
+ const trigger = target instanceof this._window.Element ? target.closest(".sidebar-menu-sub-trigger") : null;
509
464
  const item = trigger?.closest(".sidebar-menu-item");
510
465
  if (!trigger || !item || !this._element.contains(item) || !this._isCollapsed()) {
511
466
  return;
@@ -518,7 +473,6 @@ export default class Sidebar {
518
473
  this._openFlyout(item);
519
474
  }
520
475
  }
521
-
522
476
  _handleOutsideFlyoutClick(event) {
523
477
  if (!this._flyout) {
524
478
  return;
@@ -529,27 +483,15 @@ export default class Sidebar {
529
483
  this._closeFlyouts();
530
484
  }
531
485
  }
532
-
533
486
  _handleShortcut(event) {
534
487
  const target = event.target;
535
- const isEditable =
536
- target instanceof this._window.Element &&
537
- (target.matches("input, textarea, select") || target.isContentEditable);
538
- if (
539
- event.defaultPrevented ||
540
- event.isComposing ||
541
- isEditable ||
542
- !this._config.keyboard ||
543
- !(event.metaKey || event.ctrlKey) ||
544
- event.key.toLowerCase() !== "b" ||
545
- !this._isDesktop()
546
- ) {
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()) {
547
490
  return;
548
491
  }
549
- const preferred =
550
- this._document.querySelector(
551
- '[data-slot="sidebar-wrapper"][data-sidebar-key]'
552
- ) || 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"]');
553
495
  if (preferred !== this._element) {
554
496
  return;
555
497
  }
@@ -557,4 +499,9 @@ export default class Sidebar {
557
499
  this._closeFlyouts();
558
500
  this._toggle();
559
501
  }
560
- }
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.6 (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.6",
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 {