@wpmoo/ui 1.3.0 → 1.4.1

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 (38) hide show
  1. package/ASSET_LICENSE.md +1 -1
  2. package/README.md +28 -4
  3. package/certification.json +2 -1
  4. package/dist/assets/css/moo-ui.css +159 -14
  5. package/dist/assets/css/moo-ui.min.css +2 -2
  6. package/dist/assets/css/moo.css +147 -2
  7. package/dist/assets/css/moo.min.css +2 -2
  8. package/dist/js/chart.js +1 -1
  9. package/dist/js/chart.min.js +1 -1
  10. package/dist/js/combobox.js +1 -1
  11. package/dist/js/context-menu.js +1 -1
  12. package/dist/js/datatable.js +1 -1
  13. package/dist/js/datepicker.js +3 -1
  14. package/dist/js/datepicker.min.js +2 -2
  15. package/dist/js/moo-ui.js +306 -1
  16. package/dist/js/moo-ui.min.js +2 -2
  17. package/dist/js/overlay-scrollbar.js +308 -0
  18. package/dist/js/sheet.js +1 -1
  19. package/dist/js/sidebar.js +1 -1
  20. package/dist/js/slider.js +1 -1
  21. package/dist/js/state.js +1 -1
  22. package/dist/js/table-of-contents.js +1 -1
  23. package/dist/js/toc.js +1 -1
  24. package/dist/release-manifest.json +4 -4
  25. package/package.json +3 -1
  26. package/scss/components/_datepicker.scss +5 -0
  27. package/scss/components/_field.scss +3 -0
  28. package/scss/components/_select.scss +4 -0
  29. package/scss/foundations/_globals.scss +1 -0
  30. package/scss/foundations/_scope.scss +2 -0
  31. package/scss/mixins/_banner.scss +1 -1
  32. package/scss/moo-core.scss +2 -0
  33. package/scss/moo-ui.scss +3 -0
  34. package/scss/settings/_component_variables.scss +2 -0
  35. package/scss/settings/_forms.scss +1 -1
  36. package/scss/utilities/_overlay_scrollbar.scss +68 -0
  37. package/scss/utilities/_overlay_scrollbar_native.scss +1 -0
  38. package/scss/utilities/_text_alignment.scss +29 -0
@@ -0,0 +1,308 @@
1
+ /*!
2
+ * Moo UI overlay-scrollbar.js v1.4.1 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
6
+
7
+ const instances = /* @__PURE__ */ new WeakMap();
8
+ let nextId = 0;
9
+ class OverlayScrollbar {
10
+ static getInstance(owner, scrollElement = owner?.ownerDocument?.scrollingElement) {
11
+ const instance = scrollElement && instances.get(scrollElement);
12
+ return instance?._owner === owner ? instance : null;
13
+ }
14
+ static getOrCreateInstance(owner, options = {}) {
15
+ return OverlayScrollbar.getInstance(owner, options.scrollElement) || new OverlayScrollbar(owner, options);
16
+ }
17
+ constructor(owner, { scrollElement = null, hideDelay = 900, label = "Page scroll position" } = {}) {
18
+ if (!owner?.matches?.('.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]') || !owner.isConnected) {
19
+ throw new TypeError("OverlayScrollbar requires a connected, resolved .moo-ui theme owner.");
20
+ }
21
+ if (!Number.isFinite(hideDelay) || hideDelay < 0 || typeof label !== "string" || !label.trim()) {
22
+ throw new TypeError("OverlayScrollbar requires a nonnegative hideDelay and a nonempty label.");
23
+ }
24
+ const document = owner.ownerDocument;
25
+ const root = scrollElement ?? document.scrollingElement;
26
+ const isDocument = root === document.scrollingElement;
27
+ if (root?.nodeType !== 1 || !root.isConnected || root.ownerDocument !== document || !isDocument && (!owner.contains(root) || root.closest('.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]') !== owner)) {
28
+ throw new TypeError("OverlayScrollbar scrollElement must belong to the same resolved theme owner.");
29
+ }
30
+ const existing = instances.get(root);
31
+ if (existing) {
32
+ if (existing._owner === owner) return existing;
33
+ throw new Error("Only one OverlayScrollbar theme owner may control a scroll element.");
34
+ }
35
+ this._owner = owner;
36
+ this._document = document;
37
+ this._window = document.defaultView;
38
+ this._root = root;
39
+ this._isDocument = isDocument;
40
+ if (isDocument && root !== document.documentElement) {
41
+ throw new TypeError("OverlayScrollbar requires a standards-mode document scroll root.");
42
+ }
43
+ this._hideDelay = hideDelay;
44
+ this._listeners = [];
45
+ this._frame = 0;
46
+ this._timer = 0;
47
+ this._disposed = false;
48
+ this._originalNativeClass = this._root.classList.contains("scrollbar-overlay-native");
49
+ this._originalId = this._root.getAttribute("id");
50
+ this._forcedColors = this._window.matchMedia("(forced-colors: active)");
51
+ this._bar = document.createElement("div");
52
+ this._bar.className = "scrollbar-overlay";
53
+ this._bar.tabIndex = 0;
54
+ this._bar.setAttribute("role", "scrollbar");
55
+ this._bar.setAttribute("aria-label", label);
56
+ this._bar.setAttribute("aria-orientation", "vertical");
57
+ this._bar.setAttribute("aria-valuemin", "0");
58
+ this._thumb = document.createElement("div");
59
+ this._thumb.className = "scrollbar-overlay-thumb";
60
+ this._thumb.setAttribute("aria-hidden", "true");
61
+ this._bar.append(this._thumb);
62
+ owner.append(this._bar);
63
+ if (this._window.getComputedStyle(this._bar).position !== "fixed") {
64
+ this._bar.remove();
65
+ throw new Error("OverlayScrollbar requires the Moo UI overlay scrollbar stylesheet.");
66
+ }
67
+ if (!this._originalId) {
68
+ do {
69
+ this._generatedId = `scrollbar-overlay-page-${++nextId}`;
70
+ } while (document.getElementById(this._generatedId));
71
+ this._root.id = this._generatedId;
72
+ }
73
+ this._bar.setAttribute("aria-controls", this._root.id);
74
+ instances.set(root, this);
75
+ try {
76
+ this._listen(isDocument ? this._window : root, "scroll", () => {
77
+ this._reveal();
78
+ this._queue();
79
+ }, { passive: true });
80
+ if (!isDocument) {
81
+ this._listen(document, "scroll", () => this._queue(), { passive: true, capture: true });
82
+ }
83
+ this._listen(this._window, "resize", () => this._queue());
84
+ this._listen(document, "pointermove", (event) => this._onPointerMove(event), { passive: true });
85
+ this._listen(document, "pointerout", (event) => {
86
+ if (event.pointerType === "mouse" && !event.relatedTarget) {
87
+ this._nearEdge = false;
88
+ if (!this._drag) delete this._bar.dataset.scrollbarInteractive;
89
+ this._hideLater();
90
+ }
91
+ });
92
+ this._listen(this._bar, "pointerdown", (event) => this._onPointerDown(event));
93
+ this._listen(this._bar, "pointerup", () => this._finishDrag());
94
+ this._listen(this._bar, "pointercancel", () => this._finishDrag());
95
+ this._listen(this._bar, "lostpointercapture", () => this._finishDrag());
96
+ this._listen(this._bar, "focus", () => this._reveal());
97
+ this._listen(this._bar, "blur", () => this._hideLater());
98
+ this._listen(this._bar, "keydown", (event) => this._onKeyDown(event));
99
+ this._listen(this._forcedColors, "change", () => this.refresh());
100
+ this._listen(this._window, "beforeprint", () => {
101
+ this._printing = true;
102
+ this.refresh();
103
+ });
104
+ this._listen(this._window, "afterprint", () => {
105
+ this._printing = false;
106
+ this.refresh();
107
+ });
108
+ this._resize = new this._window.ResizeObserver(() => this._queue());
109
+ this._resize.observe(this._root);
110
+ this._resize.observe(document.body);
111
+ this._mutations = new this._window.MutationObserver(() => this._queue());
112
+ this._mutations.observe(this._root, { attributes: true, attributeFilter: ["class", "style", "dir"] });
113
+ if (!isDocument) {
114
+ this._mutations.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "style", "dir"] });
115
+ }
116
+ this._mutations.observe(document.body, {
117
+ attributes: true,
118
+ attributeFilter: ["class", "style"]
119
+ });
120
+ this._mutations.observe(owner, { attributes: true, attributeFilter: ["class", "style", "dir", "data-bs-theme"] });
121
+ this._contentMutations = new this._window.MutationObserver((records) => {
122
+ if (records.some((record) => record.target !== this._bar && !this._bar.contains(record.target))) this._queue();
123
+ });
124
+ this._contentMutations.observe(isDocument ? document.body : root, {
125
+ childList: true,
126
+ subtree: true,
127
+ attributes: true,
128
+ attributeFilter: ["class", "style", "hidden"]
129
+ });
130
+ this._listen(root, "load", () => this._queue(), { capture: true });
131
+ if (document.fonts) this._listen(document.fonts, "loadingdone", () => this._queue());
132
+ this.refresh();
133
+ } catch (error) {
134
+ this.dispose();
135
+ throw error;
136
+ }
137
+ }
138
+ refresh() {
139
+ if (this._disposed) return this;
140
+ const window = this._window;
141
+ const root = this._root;
142
+ const eligible = !this._forcedColors.matches && !this._printing;
143
+ if (eligible) root.classList.add("scrollbar-overlay-native");
144
+ const enabled = eligible && root.scrollWidth <= root.clientWidth + 1;
145
+ root.classList.toggle("scrollbar-overlay-native", this._originalNativeClass || enabled);
146
+ this._mutations?.takeRecords();
147
+ this._contentMutations?.takeRecords();
148
+ const locked = (this._isDocument ? [root, this._document.body] : [root]).some((element) => ["hidden", "clip"].includes(window.getComputedStyle(element).overflowY));
149
+ this._maximum = Math.max(0, root.scrollHeight - root.clientHeight);
150
+ const visible = this._position();
151
+ this._bar.hidden = !enabled || locked || !visible || this._maximum <= 0;
152
+ if (this._bar.hidden) {
153
+ if (this._document.activeElement === this._bar) this._bar.blur();
154
+ delete this._bar.dataset.scrollbarVisible;
155
+ delete this._bar.dataset.scrollbarInteractive;
156
+ return this;
157
+ }
158
+ const minimum = this._bar.clientWidth;
159
+ const height = this._bar.clientHeight;
160
+ const thumbHeight = Math.min(height, Math.max(minimum, height * root.clientHeight / root.scrollHeight));
161
+ this._travel = Math.max(0, height - thumbHeight);
162
+ const position = Math.min(this._maximum, Math.max(0, root.scrollTop));
163
+ this._thumb.style.height = `${thumbHeight}px`;
164
+ this._thumb.style.transform = `translateY(${this._travel * position / this._maximum}px)`;
165
+ this._bar.setAttribute("aria-valuemax", String(Math.round(this._maximum)));
166
+ this._bar.setAttribute("aria-valuenow", String(Math.round(position)));
167
+ return this;
168
+ }
169
+ _position() {
170
+ if (this._isDocument) return true;
171
+ const root = this._root;
172
+ const bounds = root.getBoundingClientRect();
173
+ const top = Math.max(0, bounds.top + root.clientTop);
174
+ const bottom = Math.min(this._window.innerHeight, bounds.top + root.clientTop + root.clientHeight);
175
+ const width = parseFloat(this._window.getComputedStyle(this._bar).inlineSize);
176
+ const rtl = this._window.getComputedStyle(root).direction === "rtl";
177
+ const left = bounds.left + root.clientLeft + (rtl ? 0 : root.clientWidth - width);
178
+ Object.assign(this._bar.style, {
179
+ insetInlineEnd: "auto",
180
+ bottom: "auto",
181
+ top: `${top}px`,
182
+ height: `${Math.max(0, bottom - top)}px`,
183
+ left: `${Math.max(0, Math.min(this._window.innerWidth - width, left))}px`,
184
+ direction: rtl ? "rtl" : "ltr"
185
+ });
186
+ return bounds.width > 0 && bottom > top && left < this._window.innerWidth && left + width > 0;
187
+ }
188
+ dispose() {
189
+ if (this._disposed) return;
190
+ this._disposed = true;
191
+ for (const [target, type, handler, options] of this._listeners) {
192
+ target.removeEventListener(type, handler, options);
193
+ }
194
+ this._listeners = [];
195
+ this._resize?.disconnect();
196
+ this._mutations?.disconnect();
197
+ this._contentMutations?.disconnect();
198
+ this._window.cancelAnimationFrame(this._frame);
199
+ this._window.clearTimeout(this._timer);
200
+ if (this._drag && this._bar.hasPointerCapture(this._drag.id)) this._bar.releasePointerCapture(this._drag.id);
201
+ this._drag = null;
202
+ this._bar.remove();
203
+ this._root.classList.toggle("scrollbar-overlay-native", this._originalNativeClass);
204
+ if (this._generatedId && this._root.id === this._generatedId) {
205
+ if (this._originalId === null) this._root.removeAttribute("id");
206
+ else this._root.setAttribute("id", this._originalId);
207
+ }
208
+ if (instances.get(this._root) === this) instances.delete(this._root);
209
+ }
210
+ _listen(target, type, handler, options) {
211
+ target.addEventListener(type, handler, options);
212
+ this._listeners.push([target, type, handler, options]);
213
+ }
214
+ _queue() {
215
+ if (this._disposed || this._frame) return;
216
+ this._frame = this._window.requestAnimationFrame(() => {
217
+ this._frame = 0;
218
+ this.refresh();
219
+ });
220
+ }
221
+ _reveal() {
222
+ if (this._disposed || this._bar.hidden) return;
223
+ this._bar.dataset.scrollbarVisible = "true";
224
+ this._hideLater();
225
+ }
226
+ _hideLater() {
227
+ this._window.clearTimeout(this._timer);
228
+ this._timer = 0;
229
+ if (this._disposed) return;
230
+ if (!this._bar.hasAttribute("data-scrollbar-visible")) return;
231
+ this._timer = this._window.setTimeout(() => {
232
+ this._timer = 0;
233
+ if (this._disposed || this._nearEdge || this._drag || this._bar.matches(":focus-visible")) return;
234
+ delete this._bar.dataset.scrollbarVisible;
235
+ }, this._hideDelay);
236
+ }
237
+ _onPointerMove(event) {
238
+ if (this._disposed) return;
239
+ if (this._drag?.id === event.pointerId) {
240
+ if (this._travel) this._scroll(this._drag.top + (event.clientY - this._drag.y) * this._maximum / this._travel);
241
+ return;
242
+ }
243
+ if (event.pointerType !== "mouse") {
244
+ this._nearEdge = false;
245
+ if (!this._drag) delete this._bar.dataset.scrollbarInteractive;
246
+ return;
247
+ }
248
+ const bounds = this._bar.getBoundingClientRect();
249
+ const nearEdge = !this._bar.hidden && event.clientX >= bounds.left && event.clientX <= bounds.right && event.clientY >= bounds.top && event.clientY <= bounds.bottom;
250
+ if (nearEdge) {
251
+ this._nearEdge = true;
252
+ this._bar.dataset.scrollbarInteractive = "true";
253
+ this._reveal();
254
+ } else if (this._nearEdge) {
255
+ this._nearEdge = false;
256
+ if (!this._drag) delete this._bar.dataset.scrollbarInteractive;
257
+ this._hideLater();
258
+ }
259
+ }
260
+ _onPointerDown(event) {
261
+ if (event.button !== 0 || this._bar.hidden || event.pointerType === "touch") return;
262
+ event.preventDefault();
263
+ this._reveal();
264
+ const thumb = this._thumb.getBoundingClientRect();
265
+ if (event.clientY >= thumb.top && event.clientY <= thumb.bottom) {
266
+ this._drag = { id: event.pointerId, y: event.clientY, top: this._root.scrollTop };
267
+ this._bar.setPointerCapture(event.pointerId);
268
+ } else {
269
+ const direction = event.clientY < this._thumb.getBoundingClientRect().top ? -1 : 1;
270
+ this._scroll(this._root.scrollTop + direction * this._root.clientHeight);
271
+ }
272
+ }
273
+ _finishDrag() {
274
+ if (!this._drag || this._disposed) return;
275
+ const id = this._drag.id;
276
+ this._drag = null;
277
+ if (!this._nearEdge) delete this._bar.dataset.scrollbarInteractive;
278
+ if (this._bar.hasPointerCapture(id)) this._bar.releasePointerCapture(id);
279
+ this._hideLater();
280
+ }
281
+ _onKeyDown(event) {
282
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
283
+ this.refresh();
284
+ const step = parseFloat(this._window.getComputedStyle(this._root).lineHeight) || 16;
285
+ const page = this._root.clientHeight;
286
+ const targets = {
287
+ ArrowUp: this._root.scrollTop - step,
288
+ ArrowDown: this._root.scrollTop + step,
289
+ PageUp: this._root.scrollTop - page,
290
+ PageDown: this._root.scrollTop + page,
291
+ Home: 0,
292
+ End: this._maximum,
293
+ " ": this._root.scrollTop + (event.shiftKey ? -page : page)
294
+ };
295
+ if (!Object.hasOwn(targets, event.key)) return;
296
+ event.preventDefault();
297
+ this._scroll(targets[event.key]);
298
+ this._reveal();
299
+ }
300
+ _scroll(top) {
301
+ const target = this._isDocument ? this._window : this._root;
302
+ target.scrollTo({ top: Math.min(this._maximum, Math.max(0, top)), behavior: "instant" });
303
+ this._queue();
304
+ }
305
+ }
306
+ export {
307
+ OverlayScrollbar as default
308
+ };
package/dist/js/sheet.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI sheet.js v1.3.0 (https://wpmoo.org/)
2
+ * Moo UI sheet.js v1.4.1 (https://wpmoo.org/)
3
3
  * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI sidebar.js v1.3.0 (https://wpmoo.org/)
2
+ * Moo UI sidebar.js v1.4.1 (https://wpmoo.org/)
3
3
  * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
package/dist/js/slider.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI slider.js v1.3.0 (https://wpmoo.org/)
2
+ * Moo UI slider.js v1.4.1 (https://wpmoo.org/)
3
3
  * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
package/dist/js/state.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI state.js v1.3.0 (https://wpmoo.org/)
2
+ * Moo UI state.js v1.4.1 (https://wpmoo.org/)
3
3
  * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI table-of-contents.js v1.3.0 (https://wpmoo.org/)
2
+ * Moo UI table-of-contents.js v1.4.1 (https://wpmoo.org/)
3
3
  * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
package/dist/js/toc.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI toc.js v1.3.0 (https://wpmoo.org/)
2
+ * Moo UI toc.js v1.4.1 (https://wpmoo.org/)
3
3
  * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
@@ -3,22 +3,22 @@
3
3
  {
4
4
  "export": "./moo.css",
5
5
  "path": "dist/assets/css/moo.css",
6
- "sha256": "113ff20c18bc88d30133082c3ab821f26abb1bb09d781e77ef8841372d671289"
6
+ "sha256": "81b2f71ca1d4aa1af217f36c6279b1bea6c5ac7ac16f7f8f94f726b561be0337"
7
7
  },
8
8
  {
9
9
  "export": "./moo-ui.css",
10
10
  "path": "dist/assets/css/moo-ui.css",
11
- "sha256": "c61d8fa2213ca0e59fba7db06db1adbf1554d60eee25ffde608f81feea875080"
11
+ "sha256": "b0a7f36b535b698e174d1499c282cfe01a101770e8f90c8a054707f042133a15"
12
12
  },
13
13
  {
14
14
  "export": "./state.js",
15
15
  "path": "dist/js/state.js",
16
- "sha256": "b23904775eccd1e501b097ce53ad5cd00ec4d4ca703cea784b60d3cf306804ac"
16
+ "sha256": "107502b54d1cb61d7b2b255475f0c14f213177820d4d06d159659697b00d875a"
17
17
  }
18
18
  ],
19
19
  "package": {
20
20
  "name": "@wpmoo/ui",
21
- "version": "1.3.0"
21
+ "version": "1.4.1"
22
22
  },
23
23
  "schemaVersion": 1
24
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/ui",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
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)",
@@ -39,6 +39,7 @@
39
39
  "./datatable.js": "./dist/js/datatable.js",
40
40
  "./slider.js": "./dist/js/slider.js",
41
41
  "./toc.js": "./dist/js/toc.js",
42
+ "./overlay-scrollbar.js": "./dist/js/overlay-scrollbar.js",
42
43
  "./table-of-contents.js": "./dist/js/table-of-contents.js",
43
44
  "./sheet.js": "./dist/js/sheet.js",
44
45
  "./chart.js": "./dist/js/chart.js",
@@ -68,6 +69,7 @@
68
69
  "dist/js/datatable.js",
69
70
  "dist/js/slider.js",
70
71
  "dist/js/toc.js",
72
+ "dist/js/overlay-scrollbar.js",
71
73
  "dist/js/table-of-contents.js",
72
74
  "dist/js/sheet.js",
73
75
  "dist/js/chart.js",
@@ -9,6 +9,7 @@
9
9
  }
10
10
 
11
11
  .moo-datepicker__trigger {
12
+ --bs-btn-font-weight: #{$input-font-weight};
12
13
  width: 100%;
13
14
  justify-content: flex-start;
14
15
  text-align: start;
@@ -20,6 +21,10 @@
20
21
  text-overflow: ellipsis;
21
22
  }
22
23
 
24
+ .moo-datepicker__trigger:not(:disabled):not(.is-invalid):not(:hover, :active, :focus-visible, [aria-expanded="true"]) [data-datepicker-empty="true"] {
25
+ color: $input-placeholder-color;
26
+ }
27
+
23
28
  .moo-datepicker__trigger.is-invalid {
24
29
  border-color: var(--bs-form-invalid-border-color);
25
30
  color: var(--bs-form-invalid-color);
@@ -6,6 +6,9 @@
6
6
 
7
7
  > .form-label {
8
8
  margin-bottom: 0;
9
+ }
10
+
11
+ .form-label {
9
12
  font-weight: $font-weight-medium;
10
13
  }
11
14
 
@@ -1,5 +1,9 @@
1
1
  /* Select */
2
2
  .form-select {
3
+ &:not(:disabled):not([multiple]):is(:not([size]), [size="1"]):has(option[data-placeholder="true"]:checked) {
4
+ color: $input-placeholder-color;
5
+ }
6
+
3
7
  option,
4
8
  optgroup {
5
9
  color: var(--bs-body-color);
@@ -1,4 +1,5 @@
1
1
  @import "../utilities/scroll_fade_primitives";
2
+ @include moo-overlay-scrollbar-native;
2
3
 
3
4
  @keyframes moo-spinner-rotate {
4
5
  to { transform: rotate(360deg) #{"/* rtl:ignore */"}; }
@@ -4,7 +4,9 @@
4
4
  @import "../foundations/focus";
5
5
  /* Utilities */
6
6
  @import "../utilities/border";
7
+ @include moo-text-alignment-utilities;
7
8
  @import "../utilities/scroll_fade";
9
+ @include moo-overlay-scrollbar;
8
10
  @import "../utilities/scroll_smooth";
9
11
  /* Application layout */
10
12
  @import "../layouts/app";
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI v1.3.0 (https://wpmoo.org/)
2
+ * Moo UI v1.4.1 (https://wpmoo.org/)
3
3
  * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  *
@@ -7,6 +7,8 @@
7
7
  @import "bootstrap/scss/maps";
8
8
  @import "bootstrap/scss/mixins";
9
9
  @import "bootstrap/scss/utilities";
10
+ @import "utilities/text_alignment";
11
+ @import "utilities/overlay_scrollbar";
10
12
  @import "themes/theme";
11
13
  /* Global styles */
12
14
  @import "foundations/globals";
package/scss/moo-ui.scss CHANGED
@@ -8,6 +8,8 @@
8
8
  @import "bootstrap/scss/maps";
9
9
  @import "bootstrap/scss/mixins";
10
10
  @import "bootstrap/scss/utilities";
11
+ @import "utilities/text_alignment";
12
+ @import "utilities/overlay_scrollbar";
11
13
  @import "themes/theme";
12
14
 
13
15
  /* Bootstrap layout and standalone-only components */
@@ -50,5 +52,6 @@
50
52
  @import "bootstrap/scss/utilities/api";
51
53
 
52
54
  @import "utilities/scroll_fade_primitives";
55
+ @import "utilities/overlay_scrollbar_native";
53
56
  @import "foundations/backdrop";
54
57
  @import "foundations/scope";
@@ -228,6 +228,8 @@ $moo-datatable-facet-trigger-min-height: 2rem !default;
228
228
  $moo-datatable-facet-item-height: 2rem !default;
229
229
  $moo-datatable-facet-options-max-height: $moo-datatable-facet-item-height * 10 !default;
230
230
  $moo-scrollbar-thin-size: 0.375rem !default;
231
+ $moo-scrollbar-overlay-thumb-size: $moo-scrollbar-thin-size !default;
232
+ $moo-scrollbar-overlay-hit-size: 1.5rem !default;
231
233
  $moo-datatable-scrollbar-size: $moo-scrollbar-thin-size !default;
232
234
  $moo-datatable-page-size-trigger-min-width: 3.25rem !default;
233
235
  $moo-datatable-page-size-menu-min-width: 4rem !default;
@@ -5,7 +5,7 @@ $input-line-height: 1.4285714286 !default;
5
5
  $input-bg: var(--moo-input-bg, transparent) !default;
6
6
  $input-border-color: var(--moo-border) !default;
7
7
  $input-border-radius: var(--bs-border-radius-lg) !default;
8
- $input-placeholder-color: var(--bs-tertiary-color) !default;
8
+ $input-placeholder-color: var(--bs-secondary-color) !default;
9
9
  $input-focus-bg: var(--moo-input-bg, transparent) !default;
10
10
  $input-disabled-color: var(--moo-disabled-foreground) !default;
11
11
  $form-select-disabled-color: $input-disabled-color !default;
@@ -0,0 +1,68 @@
1
+ @mixin moo-overlay-scrollbar-native($selector: "html.scrollbar-overlay-native") {
2
+ #{$selector} {
3
+ scrollbar-width: none;
4
+ }
5
+
6
+ #{$selector}::-webkit-scrollbar {
7
+ display: none;
8
+ }
9
+
10
+ @media print, (forced-colors: active) {
11
+ #{$selector} {
12
+ scrollbar-width: auto !important;
13
+ }
14
+
15
+ #{$selector}::-webkit-scrollbar {
16
+ display: revert !important;
17
+ }
18
+ }
19
+ }
20
+
21
+ @mixin moo-overlay-scrollbar {
22
+ @include moo-overlay-scrollbar-native(".scrollbar-overlay-native");
23
+
24
+ .scrollbar-overlay {
25
+ --moo-scrollbar-overlay-hit-size: #{$moo-scrollbar-overlay-hit-size};
26
+ --moo-scrollbar-overlay-thumb-size: #{$moo-scrollbar-overlay-thumb-size};
27
+ position: fixed;
28
+ inset-block: 0;
29
+ inset-inline-end: 0;
30
+ z-index: $zindex-fixed;
31
+ inline-size: var(--moo-scrollbar-overlay-hit-size);
32
+ color: var(--bs-secondary-color);
33
+ opacity: 0;
34
+ pointer-events: none;
35
+ touch-action: none;
36
+ @include transition($transition-fade);
37
+
38
+ &[data-scrollbar-visible="true"],
39
+ &:focus-visible {
40
+ opacity: 1;
41
+ }
42
+
43
+ &[data-scrollbar-interactive="true"],
44
+ &:focus-visible {
45
+ pointer-events: auto;
46
+ }
47
+
48
+ &:hover,
49
+ &:focus-visible {
50
+ color: var(--bs-emphasis-color);
51
+ }
52
+ }
53
+
54
+ .scrollbar-overlay-thumb {
55
+ position: absolute;
56
+ inset-block-start: 0;
57
+ inset-inline-end: map-get($spacers, 1);
58
+ inline-size: var(--moo-scrollbar-overlay-thumb-size);
59
+ background-color: currentColor;
60
+ border-radius: var(--bs-border-radius-pill);
61
+ }
62
+
63
+ @media print, (forced-colors: active) {
64
+ .scrollbar-overlay {
65
+ display: none !important;
66
+ }
67
+ }
68
+ }
@@ -0,0 +1 @@
1
+ @include moo-overlay-scrollbar-native;
@@ -0,0 +1,29 @@
1
+ $moo-text-alignment-utility: map-get($utilities, "text-align");
2
+ @if type-of($moo-text-alignment-utility) == "map" {
3
+ $utilities: map-merge(
4
+ $utilities,
5
+ (
6
+ "text-align": map-merge(
7
+ $moo-text-alignment-utility,
8
+ (
9
+ values: map-merge(
10
+ map-get($moo-text-alignment-utility, values),
11
+ (start: start, end: end)
12
+ ),
13
+ )
14
+ ),
15
+ )
16
+ );
17
+ }
18
+
19
+ @mixin moo-text-alignment-utilities {
20
+ $utility: map-get($utilities, "text-align");
21
+ @each $breakpoint in map-keys($grid-breakpoints) {
22
+ @include media-breakpoint-up($breakpoint) {
23
+ $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
24
+ @if type-of($utility) == "map" and (map-get($utility, responsive) or $infix == "") {
25
+ @include generate-utility($utility, $infix);
26
+ }
27
+ }
28
+ }
29
+ }