@wpmoo/ui 1.0.0-rc.3 → 1.0.0-rc.5

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 (82) hide show
  1. package/ASSET_LICENSE.md +3 -1
  2. package/README.md +55 -123
  3. package/certification.json +14 -2
  4. package/dist/assets/css/moo-ui.css +189 -122
  5. package/dist/assets/css/moo-ui.min.css +3 -3
  6. package/dist/assets/css/moo.css +72 -17
  7. package/dist/assets/css/moo.min.css +3 -3
  8. package/dist/js/chart.js +1 -1
  9. package/dist/js/chart.min.js +1 -1
  10. package/dist/js/combobox.js +71 -18
  11. package/dist/js/context-menu.js +1 -1
  12. package/dist/js/datatable.js +135 -25
  13. package/dist/js/datepicker.js +1 -1
  14. package/dist/js/datepicker.min.js +1 -1
  15. package/dist/js/moo-ui.js +19047 -0
  16. package/dist/js/moo-ui.min.js +27 -0
  17. package/dist/js/sidebar.js +1 -1
  18. package/dist/js/slider.js +1 -1
  19. package/package.json +18 -6
  20. package/scss/_components.scss +73 -0
  21. package/scss/_settings.scss +8 -0
  22. package/scss/components/_accordion.scss +65 -0
  23. package/scss/components/_alert.scss +94 -0
  24. package/scss/components/_avatar.scss +154 -0
  25. package/scss/components/_badge.scss +33 -0
  26. package/scss/components/_breadcrumb.scss +130 -0
  27. package/scss/components/_button.scss +228 -0
  28. package/scss/components/_button_group.scss +81 -0
  29. package/scss/components/_card.scss +142 -0
  30. package/scss/components/_checkbox.scss +115 -0
  31. package/scss/components/_close_button.scss +59 -0
  32. package/scss/components/_collapsible.scss +43 -0
  33. package/scss/components/_combobox.scss +258 -0
  34. package/scss/components/_context-menu.scss +28 -0
  35. package/scss/components/_datatable.scss +1082 -0
  36. package/scss/components/_datepicker.scss +236 -0
  37. package/scss/components/_dialog.scss +66 -0
  38. package/scss/components/_dropdown.scss +202 -0
  39. package/scss/components/_field.scss +125 -0
  40. package/scss/components/_input.scss +35 -0
  41. package/scss/components/_input_group.scss +201 -0
  42. package/scss/components/_kbd.scss +10 -0
  43. package/scss/components/_menubar.scss +16 -0
  44. package/scss/components/_navigation.scss +41 -0
  45. package/scss/components/_pagination.scss +70 -0
  46. package/scss/components/_popover.scss +47 -0
  47. package/scss/components/_progress.scss +7 -0
  48. package/scss/components/_radio_group.scss +9 -0
  49. package/scss/components/_select.scss +7 -0
  50. package/scss/components/_separator.scss +15 -0
  51. package/scss/components/_sheet.scss +32 -0
  52. package/scss/components/_sidebar.scss +7 -0
  53. package/scss/components/_skeleton.scss +17 -0
  54. package/scss/components/_slider.scss +214 -0
  55. package/scss/components/_spinner.scss +17 -0
  56. package/scss/components/_switch.scss +25 -0
  57. package/scss/components/_table.scss +7 -0
  58. package/scss/components/_tabs.scss +139 -0
  59. package/scss/components/_textarea.scss +7 -0
  60. package/scss/components/_toast.scss +119 -0
  61. package/scss/components/_toggle_group.scss +19 -0
  62. package/scss/components/_tooltip.scss +83 -0
  63. package/scss/components/sidebar/_collapsed.scss +187 -0
  64. package/scss/components/sidebar/_identity.scss +72 -0
  65. package/scss/components/sidebar/_inset.scss +59 -0
  66. package/scss/components/sidebar/_layout.scss +176 -0
  67. package/scss/components/sidebar/_menus.scss +236 -0
  68. package/scss/foundations/_core_global_primitives.scss +5 -0
  69. package/scss/foundations/_core_state_layer.scss +25 -0
  70. package/scss/foundations/_focus.scss +139 -0
  71. package/scss/foundations/_overlay_backdrop.scss +19 -0
  72. package/scss/moo-core.scss +47 -0
  73. package/scss/moo-ui.scss +50 -0
  74. package/scss/settings/_bootstrap_overrides.scss +11 -0
  75. package/scss/settings/_component_variables.scss +480 -0
  76. package/scss/settings/_forms.scss +65 -0
  77. package/scss/settings/_palette.scss +132 -0
  78. package/scss/themes/_scoped_core.scss +239 -0
  79. package/scss/themes/_standalone_root.scss +119 -0
  80. package/scss/utilities/_scroll_fade.scss +193 -0
  81. package/scss/utilities/_scroll_fade_primitives.scss +43 -0
  82. package/THIRD_PARTY_NOTICES.md +0 -53
package/dist/js/chart.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI chart.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
2
+ * Moo UI chart.js v1.0.0-rc.5 (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
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI chart.min.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
2
+ * Moo UI chart.min.js v1.0.0-rc.5 (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
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI combobox.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
2
+ * Moo UI combobox.js v1.0.0-rc.5 (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
  */
@@ -162,7 +162,10 @@ export default class Combobox {
162
162
  this._menu.classList.remove("show");
163
163
  this._input.setAttribute("aria-expanded", "false");
164
164
  this._input.removeAttribute("aria-activedescendant");
165
- this._options.forEach((option) => option.removeAttribute("aria-current"));
165
+ this._options.forEach((option) => {
166
+ option.removeAttribute("aria-current");
167
+ option.classList.remove("active");
168
+ });
166
169
  if (wasOpen && emit) {
167
170
  this._trigger("hidden");
168
171
  }
@@ -292,16 +295,35 @@ export default class Combobox {
292
295
  return this._visibleOptions().find((option) => option.id === activeId) || null;
293
296
  }
294
297
 
295
- _activateFirstOptionIfNeeded() {
298
+ _selectedOption() {
299
+ if (this._isMultiple) {
300
+ return null;
301
+ }
302
+ return (
303
+ this._visibleOptions().find(
304
+ (option) => option.getAttribute("aria-selected") === "true"
305
+ ) || null
306
+ );
307
+ }
308
+
309
+ _preferredActiveOption() {
310
+ return this._selectedOption() || this._visibleOptions()[0] || null;
311
+ }
312
+
313
+ _activatePreferredOptionIfNeeded() {
296
314
  if (!this._activeOption()) {
297
- this._setActiveOption(this._visibleOptions()[0] || null);
315
+ this._setActiveOption(this._preferredActiveOption());
298
316
  }
299
317
  }
300
318
 
301
- _setActiveOption(option) {
319
+ _setActiveOption(option, { scroll = true } = {}) {
302
320
  this._options.forEach((candidate) => {
303
321
  const active = candidate === option;
304
- candidate.toggleAttribute("aria-current", active);
322
+ if (active) {
323
+ candidate.setAttribute("aria-current", "true");
324
+ } else {
325
+ candidate.removeAttribute("aria-current");
326
+ }
305
327
  candidate.classList.toggle("active", active);
306
328
  });
307
329
  if (!option) {
@@ -309,12 +331,36 @@ export default class Combobox {
309
331
  return;
310
332
  }
311
333
  this._input.setAttribute("aria-activedescendant", option.id);
312
- const optionRect = option.getBoundingClientRect();
313
- const menuRect = this._menu.getBoundingClientRect();
314
- if (optionRect.top < menuRect.top) {
315
- this._menu.scrollTop -= menuRect.top - optionRect.top;
316
- } else if (optionRect.bottom > menuRect.bottom) {
317
- this._menu.scrollTop += optionRect.bottom - menuRect.bottom;
334
+ if (scroll) {
335
+ const optionRect = option.getBoundingClientRect();
336
+ const menuRect = this._menu.getBoundingClientRect();
337
+ if (optionRect.top < menuRect.top) {
338
+ this._menu.scrollTop -= menuRect.top - optionRect.top;
339
+ } else if (optionRect.bottom > menuRect.bottom) {
340
+ this._menu.scrollTop += optionRect.bottom - menuRect.bottom;
341
+ }
342
+ }
343
+ }
344
+
345
+ _activateOptionFromPointer(option, event) {
346
+ if (
347
+ event.pointerType === "touch" ||
348
+ option.disabled ||
349
+ option.getAttribute("aria-disabled") === "true" ||
350
+ option === this._activeOption()
351
+ ) {
352
+ return;
353
+ }
354
+ this._setActiveOption(option, { scroll: false });
355
+ }
356
+
357
+ _restoreSelectedActiveOption() {
358
+ if (!this._menu.classList.contains("show")) {
359
+ return;
360
+ }
361
+ const selected = this._selectedOption();
362
+ if (selected) {
363
+ this._setActiveOption(selected, { scroll: false });
318
364
  }
319
365
  }
320
366
 
@@ -346,7 +392,7 @@ export default class Combobox {
346
392
  this._emitChange();
347
393
  }
348
394
 
349
- _filterOptions({ open = true, activate = true } = {}) {
395
+ _filterOptions({ open = true, activate = true, resetActive = false } = {}) {
350
396
  if (open) {
351
397
  this._openMenu();
352
398
  }
@@ -360,8 +406,11 @@ export default class Combobox {
360
406
  this._syncGroupVisibility();
361
407
  this._empty.hidden = count !== 0;
362
408
  this._liveRegion.textContent = count === 0 ? "No results" : `${count} result${count === 1 ? "" : "s"}`;
409
+ if (resetActive) {
410
+ this._setActiveOption(null, { scroll: false });
411
+ }
363
412
  if (activate) {
364
- this._activateFirstOptionIfNeeded();
413
+ this._activatePreferredOptionIfNeeded();
365
414
  }
366
415
  }
367
416
 
@@ -371,13 +420,13 @@ export default class Combobox {
371
420
  if (!this._isMultiple && this._input.dataset.comboboxSelected === "true") {
372
421
  this._input.select();
373
422
  }
374
- this._activateFirstOptionIfNeeded();
423
+ this._activatePreferredOptionIfNeeded();
375
424
  });
376
425
  this._listen(this._input, "input", () => {
377
426
  if (!this._isMultiple) {
378
427
  this._clearSelection();
379
428
  }
380
- this._filterOptions();
429
+ this._filterOptions({ resetActive: true });
381
430
  });
382
431
  this._listen(this._input, "blur", (event) => {
383
432
  const nextTarget = event.relatedTarget;
@@ -388,11 +437,14 @@ export default class Combobox {
388
437
  });
389
438
  this._listen(this._input, "click", () => {
390
439
  this._openMenu();
391
- this._setActiveOption(this._visibleOptions()[0] || null);
440
+ this._setActiveOption(this._preferredActiveOption());
392
441
  });
393
442
  this._listen(this._input, "keydown", (event) => this._handleKeydown(event));
394
443
  this._options.forEach((option) => {
395
444
  this._listen(option, "keydown", (event) => this._handleKeydown(event));
445
+ this._listen(option, "pointerenter", (event) => {
446
+ this._activateOptionFromPointer(option, event);
447
+ });
396
448
  this._listen(option, "click", () => {
397
449
  this._chooseOption(option);
398
450
  if (this._isMultiple) {
@@ -407,8 +459,9 @@ export default class Combobox {
407
459
  this._clearSelection();
408
460
  this._input.value = "";
409
461
  this._input.focus();
410
- this._filterOptions();
462
+ this._filterOptions({ resetActive: true });
411
463
  });
464
+ this._listen(this._menu, "pointerleave", () => this._restoreSelectedActiveOption());
412
465
  this._listen(this._element, "click", (event) => this._handleRootClick(event));
413
466
  this._listen(this._document, "click", (event) => {
414
467
  if (event.target instanceof this._window.Node && !this._element.contains(event.target)) {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI context-menu.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
2
+ * Moo UI context-menu.js v1.0.0-rc.5 (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
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI datatable.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
2
+ * Moo UI datatable.js v1.0.0-rc.5 (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
  */
@@ -54,6 +54,8 @@ export default class DataTable {
54
54
  this._filterMode = element.dataset.datatableFilterMode || "inline";
55
55
  this._reparentedRowMenus = new Map();
56
56
  this._reparentedRowMenuByTrigger = new WeakMap();
57
+ this._reparentedSortMenus = new Map();
58
+ this._reparentedSortMenuByTrigger = new WeakMap();
57
59
 
58
60
  this._rows = Array.from(this._tbody.querySelectorAll(":scope > tr[data-datatable-row]")).map((tr, index) => ({
59
61
  element: tr,
@@ -71,11 +73,14 @@ export default class DataTable {
71
73
  this._initBulkTooltips();
72
74
  this._initViewToggle();
73
75
  this._initRowActionDropdowns();
76
+ this._initSortDropdowns();
74
77
  }
75
78
 
76
79
  dispose() {
77
80
  this._disposeRowActionDropdowns();
81
+ this._disposeSortDropdowns();
78
82
  this._restoreRowActionMenus();
83
+ this._restoreSortMenus();
79
84
  this._listeners.forEach(({ target, type, handler, options }) => {
80
85
  target.removeEventListener(type, handler, options);
81
86
  });
@@ -118,30 +123,52 @@ export default class DataTable {
118
123
  }
119
124
  this._rowActionTriggers().forEach((trigger) => {
120
125
  Dropdown.getOrCreateInstance(trigger, {
121
- popperConfig: (defaultConfig) => ({
122
- ...defaultConfig,
123
- strategy: "fixed",
124
- modifiers: [
125
- ...(defaultConfig.modifiers || []),
126
- {
127
- name: "flip",
128
- options: {
129
- fallbackPlacements: ["top-end", "top-start", "bottom-end", "bottom-start"],
130
- },
131
- },
132
- {
133
- name: "preventOverflow",
134
- options: {
135
- boundary: "viewport",
136
- padding: 8,
137
- },
138
- },
139
- ],
140
- }),
126
+ popperConfig: (defaultConfig) => this._clippingSafePopperConfig(defaultConfig),
141
127
  });
142
128
  });
143
129
  }
144
130
 
131
+ _sortTriggers() {
132
+ return this._element.querySelectorAll(
133
+ ".datatable-sort [data-bs-toggle=\"dropdown\"]"
134
+ );
135
+ }
136
+
137
+ _initSortDropdowns() {
138
+ const Dropdown = this._bootstrap("Dropdown");
139
+ if (!Dropdown) {
140
+ return;
141
+ }
142
+ this._sortTriggers().forEach((trigger) => {
143
+ Dropdown.getOrCreateInstance(trigger, {
144
+ popperConfig: (defaultConfig) => this._clippingSafePopperConfig(defaultConfig),
145
+ });
146
+ });
147
+ }
148
+
149
+ _clippingSafePopperConfig(defaultConfig) {
150
+ return {
151
+ ...defaultConfig,
152
+ strategy: "fixed",
153
+ modifiers: [
154
+ ...(defaultConfig.modifiers || []),
155
+ {
156
+ name: "flip",
157
+ options: {
158
+ fallbackPlacements: ["top-end", "top-start", "bottom-end", "bottom-start"],
159
+ },
160
+ },
161
+ {
162
+ name: "preventOverflow",
163
+ options: {
164
+ boundary: "viewport",
165
+ padding: 8,
166
+ },
167
+ },
168
+ ],
169
+ };
170
+ }
171
+
145
172
  // dispose() alone leaves any open row-action Dropdown's show/aria-expanded/
146
173
  // data-bs-popper state and Bootstrap instance behind; hide and dispose each
147
174
  // one first so a subsequent re-init starts from a clean state.
@@ -160,6 +187,21 @@ export default class DataTable {
160
187
  });
161
188
  }
162
189
 
190
+ _disposeSortDropdowns() {
191
+ const Dropdown = this._bootstrap("Dropdown");
192
+ if (!Dropdown) {
193
+ return;
194
+ }
195
+ this._sortTriggers().forEach((trigger) => {
196
+ const instance = Dropdown.getInstance(trigger);
197
+ if (!instance) {
198
+ return;
199
+ }
200
+ instance.hide();
201
+ instance.dispose();
202
+ });
203
+ }
204
+
163
205
  _rowActionMenuForTrigger(trigger) {
164
206
  if (!trigger?.closest?.(".table-row-actions")) {
165
207
  return null;
@@ -232,6 +274,68 @@ export default class DataTable {
232
274
  });
233
275
  }
234
276
 
277
+ _sortMenuForTrigger(trigger) {
278
+ if (!trigger?.closest?.(".datatable-sort")) {
279
+ return null;
280
+ }
281
+ return (
282
+ this._reparentedSortMenuByTrigger.get(trigger) ||
283
+ trigger?.closest?.(".dropdown")?.querySelector(":scope > .dropdown-menu") ||
284
+ null
285
+ );
286
+ }
287
+
288
+ // Sort menus are authored inside their table header, but must leave the
289
+ // overflow frame while open just like row-action menus do.
290
+ _reparentSortMenu(trigger) {
291
+ const menu = this._sortMenuForTrigger(trigger);
292
+ if (!menu || this._reparentedSortMenus.has(menu)) {
293
+ return;
294
+ }
295
+ this._reparentedSortMenus.set(menu, {
296
+ parent: menu.parentNode,
297
+ nextSibling: menu.nextSibling,
298
+ trigger,
299
+ owner: trigger.closest(".datatable-sort"),
300
+ });
301
+ this._reparentedSortMenuByTrigger.set(trigger, menu);
302
+ this._document.body.appendChild(menu);
303
+ }
304
+
305
+ _restoreSortMenuForTrigger(trigger) {
306
+ const menu = this._reparentedSortMenuByTrigger.get(trigger) || this._sortMenuForTrigger(trigger);
307
+ if (menu) {
308
+ this._restoreSortMenu(menu);
309
+ }
310
+ }
311
+
312
+ _restoreSortMenu(menu) {
313
+ const original = this._reparentedSortMenus.get(menu);
314
+ if (!original) {
315
+ return;
316
+ }
317
+ const { parent, nextSibling, trigger } = original;
318
+ if (parent?.isConnected) {
319
+ if (nextSibling?.parentNode === parent) {
320
+ parent.insertBefore(menu, nextSibling);
321
+ } else {
322
+ parent.appendChild(menu);
323
+ }
324
+ } else {
325
+ menu.remove();
326
+ }
327
+ if (trigger) {
328
+ this._reparentedSortMenuByTrigger.delete(trigger);
329
+ }
330
+ this._reparentedSortMenus.delete(menu);
331
+ }
332
+
333
+ _restoreSortMenus() {
334
+ Array.from(this._reparentedSortMenus.keys()).forEach((menu) => {
335
+ this._restoreSortMenu(menu);
336
+ });
337
+ }
338
+
235
339
  _bootstrap(name) {
236
340
  return this._window.bootstrap?.[name] || null;
237
341
  }
@@ -785,9 +889,13 @@ export default class DataTable {
785
889
  return;
786
890
  }
787
891
  const menu = trigger.closest(".dropdown-menu");
788
- const key = menu
789
- ?.closest(".datatable-sort")
790
- ?.querySelector("[data-datatable-sort-key]")?.dataset.datatableSortKey;
892
+ const sort =
893
+ menu?.closest(".datatable-sort") ||
894
+ this._reparentedSortMenus.get(menu)?.owner;
895
+ if (!sort || !this._element.contains(sort)) {
896
+ return;
897
+ }
898
+ const key = sort.querySelector("[data-datatable-sort-key]")?.dataset.datatableSortKey;
791
899
  if (!key) {
792
900
  return;
793
901
  }
@@ -1175,7 +1283,6 @@ export default class DataTable {
1175
1283
  });
1176
1284
 
1177
1285
  this._listen(this._element, "click", (event) => {
1178
- this._handleSortAction(event);
1179
1286
  this._handleFilterPickerClick(event);
1180
1287
  this._handleFilterOptionClick(event);
1181
1288
  this._handleFilterToggleClick(event);
@@ -1190,6 +1297,7 @@ export default class DataTable {
1190
1297
  this._handleSelectRow(event);
1191
1298
  }
1192
1299
  });
1300
+ this._listen(this._document, "click", (event) => this._handleSortAction(event));
1193
1301
 
1194
1302
  // Bound to the document, not this._element: a checkbox click does not
1195
1303
  // reliably move focus into the table (WebKit never focuses form
@@ -1213,12 +1321,14 @@ export default class DataTable {
1213
1321
  // re-triggered.
1214
1322
  this._listen(this._element, "show.bs.dropdown", (event) => {
1215
1323
  this._reparentRowActionMenu(event.target);
1324
+ this._reparentSortMenu(event.target);
1216
1325
  const tooltip = this._bootstrap("Tooltip")?.getInstance(event.target);
1217
1326
  tooltip?.hide();
1218
1327
  tooltip?.disable();
1219
1328
  });
1220
1329
  this._listen(this._element, "hidden.bs.dropdown", (event) => {
1221
1330
  this._restoreRowActionMenuForTrigger(event.target);
1331
+ this._restoreSortMenuForTrigger(event.target);
1222
1332
  this._bootstrap("Tooltip")?.getInstance(event.target)?.enable();
1223
1333
  if (event.target.matches("[data-datatable-filter-menu-trigger]")) {
1224
1334
  this._showFilterPickerPanel();
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI datepicker.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
2
+ * Moo UI datepicker.js v1.0.0-rc.5 (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
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Moo UI datepicker.min.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
2
+ * Moo UI datepicker.min.js v1.0.0-rc.5 (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
  */