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

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.
@@ -1,3 +1,8 @@
1
+ /*!
2
+ * Moo UI combobox.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
3
+ * Copyright 2026 WPMoo (https://wpmoo.org)
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
1
6
  const instances = new WeakMap();
2
7
  const normalize = (value) => value.trim().toLowerCase();
3
8
 
@@ -30,36 +35,36 @@ export default class Combobox {
30
35
  throw new TypeError("Combobox requires an input, menu, and at least one option.");
31
36
  }
32
37
 
33
- this._isMultiple = element.dataset.mooComboboxMultiple === "true";
38
+ this._isMultiple = element.dataset.comboboxMultiple === "true";
34
39
  this._hidden = this._isMultiple ? null : element.querySelector('input[type="hidden"]');
35
40
  this._chipValue = element.querySelector(".combobox-value");
36
- this._valueStore = element.querySelector("[data-moo-combobox-value-store]");
37
- this._chipIconTemplate = element.querySelector("[data-moo-combobox-chip-icon]");
38
- this._clearTrigger = element.querySelector("[data-moo-combobox-clear]");
41
+ this._valueStore = element.querySelector("[data-combobox-value-store]");
42
+ this._chipIconTemplate = element.querySelector("[data-combobox-chip-icon]");
43
+ this._clearTrigger = element.querySelector("[data-combobox-clear]");
39
44
  this._indicator = element.querySelector(".combobox-indicator");
40
- this._groups = Array.from(element.querySelectorAll("[data-moo-combobox-group]"));
41
- this._separators = Array.from(element.querySelectorAll("[data-moo-combobox-separator]"));
45
+ this._groups = Array.from(element.querySelectorAll("[data-combobox-group]"));
46
+ this._separators = Array.from(element.querySelectorAll("[data-combobox-separator]"));
42
47
  this._listeners = [];
43
48
  this._generated = [];
44
49
  this._startsOpen = this._menu.classList.contains("show");
45
50
 
46
- this._empty = this._menu.querySelector("[data-moo-combobox-empty]");
51
+ this._empty = this._menu.querySelector("[data-combobox-empty]");
47
52
  if (!this._empty) {
48
53
  this._empty = this._document.createElement("li");
49
54
  this._empty.className = "combobox-empty";
50
55
  this._empty.hidden = true;
51
- this._empty.dataset.mooComboboxEmpty = "true";
56
+ this._empty.dataset.comboboxEmpty = "true";
52
57
  this._empty.textContent = this._config.noResultsText;
53
58
  this._menu.appendChild(this._empty);
54
59
  this._generated.push(this._empty);
55
60
  }
56
61
 
57
- this._liveRegion = element.querySelector("[data-moo-combobox-live]");
62
+ this._liveRegion = element.querySelector("[data-combobox-live]");
58
63
  if (!this._liveRegion) {
59
64
  this._liveRegion = this._document.createElement("span");
60
65
  this._liveRegion.className = "visually-hidden";
61
66
  this._liveRegion.setAttribute("aria-live", "polite");
62
- this._liveRegion.dataset.mooComboboxLive = "true";
67
+ this._liveRegion.dataset.comboboxLive = "true";
63
68
  element.appendChild(this._liveRegion);
64
69
  this._generated.push(this._liveRegion);
65
70
  }
@@ -67,8 +72,8 @@ export default class Combobox {
67
72
  instances.set(element, this);
68
73
  this._bindEvents();
69
74
  this._toggleClear(
70
- this._input.dataset.mooComboboxSelected === "true" ||
71
- element.dataset.mooComboboxSelected === "true"
75
+ this._input.dataset.comboboxSelected === "true" ||
76
+ element.dataset.comboboxSelected === "true"
72
77
  );
73
78
  this._syncMultipleValue();
74
79
  this._filterOptions({ open: this._startsOpen, activate: this._startsOpen });
@@ -85,8 +90,8 @@ export default class Combobox {
85
90
  this._closeMenu(false);
86
91
  this._generated.forEach((element) => element.remove());
87
92
  this._generated = [];
88
- this._chipValue?.querySelectorAll("[data-moo-combobox-generated]").forEach((item) => item.remove());
89
- this._valueStore?.querySelectorAll("[data-moo-combobox-generated]").forEach((item) => item.remove());
93
+ this._chipValue?.querySelectorAll("[data-combobox-generated]").forEach((item) => item.remove());
94
+ this._valueStore?.querySelectorAll("[data-combobox-generated]").forEach((item) => item.remove());
90
95
  instances.delete(this._element);
91
96
  }
92
97
 
@@ -108,7 +113,7 @@ export default class Combobox {
108
113
  }
109
114
 
110
115
  _optionGroupIsHidden(option) {
111
- return option.closest("[data-moo-combobox-group]")?.hidden || false;
116
+ return option.closest("[data-combobox-group]")?.hidden || false;
112
117
  }
113
118
 
114
119
  _visibleOptions() {
@@ -173,8 +178,8 @@ export default class Combobox {
173
178
 
174
179
  _toggleClear(selected) {
175
180
  const value = selected ? "true" : "false";
176
- this._element.dataset.mooComboboxSelected = value;
177
- this._input.dataset.mooComboboxSelected = value;
181
+ this._element.dataset.comboboxSelected = value;
182
+ this._input.dataset.comboboxSelected = value;
178
183
  if (this._clearTrigger) {
179
184
  this._clearTrigger.hidden = !selected;
180
185
  if (this._indicator) {
@@ -189,7 +194,7 @@ export default class Combobox {
189
194
  const chip = this._document.createElement("span");
190
195
  chip.className = "badge text-bg-secondary combobox-chip";
191
196
  chip.dataset.value = value;
192
- chip.dataset.mooComboboxGenerated = "true";
197
+ chip.dataset.comboboxGenerated = "true";
193
198
  const labelElement = this._document.createElement("span");
194
199
  labelElement.className = "combobox-chip__label";
195
200
  labelElement.textContent = label;
@@ -198,7 +203,7 @@ export default class Combobox {
198
203
  remove.className = "combobox-chip__remove";
199
204
  remove.type = "button";
200
205
  remove.dataset.value = value;
201
- remove.dataset.mooComboboxChipRemove = "true";
206
+ remove.dataset.comboboxChipRemove = "true";
202
207
  remove.setAttribute("aria-label", `Remove ${label}`);
203
208
  remove.append(this._chipIconTemplate?.content.cloneNode(true) || "x");
204
209
  chip.appendChild(remove);
@@ -215,13 +220,13 @@ export default class Combobox {
215
220
  }
216
221
  if (this._valueStore) {
217
222
  this._valueStore.replaceChildren();
218
- const name = this._valueStore.dataset.mooComboboxName || "";
223
+ const name = this._valueStore.dataset.comboboxName || "";
219
224
  selected.forEach((option) => {
220
225
  const field = this._document.createElement("input");
221
226
  field.type = "hidden";
222
227
  field.name = name;
223
228
  field.value = option.dataset.value || "";
224
- field.dataset.mooComboboxGenerated = "true";
229
+ field.dataset.comboboxGenerated = "true";
225
230
  this._valueStore.appendChild(field);
226
231
  });
227
232
  }
@@ -279,8 +284,26 @@ export default class Combobox {
279
284
  this._input.value = "";
280
285
  }
281
286
 
287
+ _activeOption() {
288
+ const activeId = this._input.getAttribute("aria-activedescendant");
289
+ if (!activeId) {
290
+ return null;
291
+ }
292
+ return this._visibleOptions().find((option) => option.id === activeId) || null;
293
+ }
294
+
295
+ _activateFirstOptionIfNeeded() {
296
+ if (!this._activeOption()) {
297
+ this._setActiveOption(this._visibleOptions()[0] || null);
298
+ }
299
+ }
300
+
282
301
  _setActiveOption(option) {
283
- this._options.forEach((candidate) => candidate.toggleAttribute("aria-current", candidate === option));
302
+ this._options.forEach((candidate) => {
303
+ const active = candidate === option;
304
+ candidate.toggleAttribute("aria-current", active);
305
+ candidate.classList.toggle("active", active);
306
+ });
284
307
  if (!option) {
285
308
  this._input.removeAttribute("aria-activedescendant");
286
309
  return;
@@ -338,17 +361,17 @@ export default class Combobox {
338
361
  this._empty.hidden = count !== 0;
339
362
  this._liveRegion.textContent = count === 0 ? "No results" : `${count} result${count === 1 ? "" : "s"}`;
340
363
  if (activate) {
341
- this._setActiveOption(this._visibleOptions()[0] || null);
364
+ this._activateFirstOptionIfNeeded();
342
365
  }
343
366
  }
344
367
 
345
368
  _bindEvents() {
346
369
  this._listen(this._input, "focus", () => {
347
370
  this._openMenu();
348
- if (!this._isMultiple && this._input.dataset.mooComboboxSelected === "true") {
371
+ if (!this._isMultiple && this._input.dataset.comboboxSelected === "true") {
349
372
  this._input.select();
350
373
  }
351
- this._setActiveOption(this._visibleOptions()[0] || null);
374
+ this._activateFirstOptionIfNeeded();
352
375
  });
353
376
  this._listen(this._input, "input", () => {
354
377
  if (!this._isMultiple) {
@@ -356,10 +379,20 @@ export default class Combobox {
356
379
  }
357
380
  this._filterOptions();
358
381
  });
359
- this._listen(this._input, "blur", () => this._clearStaleSelection());
360
- this._listen(this._input, "click", () => this._openMenu());
382
+ this._listen(this._input, "blur", (event) => {
383
+ const nextTarget = event.relatedTarget;
384
+ if (nextTarget instanceof this._window.Node && this._element.contains(nextTarget)) {
385
+ return;
386
+ }
387
+ this._clearStaleSelection();
388
+ });
389
+ this._listen(this._input, "click", () => {
390
+ this._openMenu();
391
+ this._setActiveOption(this._visibleOptions()[0] || null);
392
+ });
361
393
  this._listen(this._input, "keydown", (event) => this._handleKeydown(event));
362
394
  this._options.forEach((option) => {
395
+ this._listen(option, "keydown", (event) => this._handleKeydown(event));
363
396
  this._listen(option, "click", () => {
364
397
  this._chooseOption(option);
365
398
  if (this._isMultiple) {
@@ -387,7 +420,7 @@ export default class Combobox {
387
420
  _handleRootClick(event) {
388
421
  const target = event.target;
389
422
  const trigger = target instanceof this._window.Element
390
- ? target.closest("[data-moo-combobox-chip-remove]")
423
+ ? target.closest("[data-combobox-chip-remove]")
391
424
  : null;
392
425
  if (trigger) {
393
426
  event.preventDefault();
@@ -407,6 +440,8 @@ export default class Combobox {
407
440
  const current = available.findIndex(
408
441
  (option) => option.id === this._input.getAttribute("aria-activedescendant")
409
442
  );
443
+ const isNextKey = event.key === "ArrowDown" || event.key === "Down";
444
+ const isPreviousKey = event.key === "ArrowUp" || event.key === "Up";
410
445
  if (this._isMultiple && event.key === "Backspace" && this._input.value === "") {
411
446
  const selected = this._selectedOptions();
412
447
  const last = selected[selected.length - 1];
@@ -414,12 +449,15 @@ export default class Combobox {
414
449
  event.preventDefault();
415
450
  this._removeChip(last.dataset.value || "");
416
451
  }
417
- } else if ((event.key === "ArrowDown" || event.key === "ArrowUp") && available.length) {
452
+ } else if ((isNextKey || isPreviousKey) && available.length) {
418
453
  event.preventDefault();
419
454
  this._openMenu();
420
- const offset = event.key === "ArrowDown" ? 1 : -1;
455
+ const offset = isNextKey ? 1 : -1;
421
456
  const next = current === -1 ? 0 : (current + offset + available.length) % available.length;
422
457
  this._setActiveOption(available[next]);
458
+ if (event.target !== this._input) {
459
+ this._input.focus({ preventScroll: true });
460
+ }
423
461
  } else if (event.key === "Enter") {
424
462
  const option = available[current];
425
463
  if (option) {
@@ -431,7 +469,7 @@ export default class Combobox {
431
469
  }
432
470
  } else if (event.key === "Escape") {
433
471
  this._closeMenu();
434
- this._input.blur();
472
+ this._input.focus();
435
473
  } else if (event.key === "Tab") {
436
474
  this._closeMenu();
437
475
  }
@@ -1,3 +1,8 @@
1
+ /*!
2
+ * Moo UI context-menu.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
3
+ * Copyright 2026 WPMoo (https://wpmoo.org)
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
1
6
  const instances = new WeakMap();
2
7
 
3
8
  function pointerRect(event) {
@@ -174,6 +179,19 @@ export default class ContextMenu {
174
179
  this._openMethod = "pointer";
175
180
  }
176
181
 
182
+ _handleFallbackKeydown(event) {
183
+ if (event.key !== " " && event.key !== "Spacebar") {
184
+ return;
185
+ }
186
+ event.preventDefault();
187
+ event.stopPropagation();
188
+ if (this._menu.classList.contains("show")) {
189
+ this._dropdown.hide();
190
+ return;
191
+ }
192
+ this._open(this._fallback, null, "fallback-keyboard");
193
+ }
194
+
177
195
  _handleMenuClick(event) {
178
196
  const item = menuItemFromEvent(event);
179
197
  if (!item) {
@@ -217,6 +235,7 @@ export default class ContextMenu {
217
235
  this._listen(this._element, "contextmenu", (event) => this._handleContextMenu(event));
218
236
  this._listen(this._element, "keydown", (event) => this._handleKeydown(event));
219
237
  this._listen(this._fallback, "click", () => this._handleFallbackClick());
238
+ this._listen(this._fallback, "keydown", (event) => this._handleFallbackKeydown(event));
220
239
  this._listen(this._menu, "click", (event) => this._handleMenuClick(event));
221
240
  this._listen(this._fallback, "show.bs.dropdown", (event) => {
222
241
  if (!this._trigger("show", true)) {
@@ -231,7 +250,9 @@ export default class ContextMenu {
231
250
  // highlight instead of a focus ring competing with it. A keyboard-
232
251
  // driven open (Shift+F10/ContextMenu key) still lands on the first
233
252
  // item, matching that same native arrow-key-open convention.
234
- if (this._openMethod === "keyboard") {
253
+ if (this._openMethod === "fallback-keyboard") {
254
+ this._fallback.focus();
255
+ } else if (this._openMethod === "keyboard") {
235
256
  this._menuItems()[0]?.focus();
236
257
  } else {
237
258
  this._menu.focus();
@@ -1,3 +1,8 @@
1
+ /*!
2
+ * Moo UI datatable.js v1.0.0-rc.3 (https://ui.wpmoo.org/)
3
+ * Copyright 2026 WPMoo (https://wpmoo.org)
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
1
6
  const instances = new WeakMap();
2
7
 
3
8
  function normalize(value) {
@@ -47,6 +52,8 @@ export default class DataTable {
47
52
  this._searchTerm = "";
48
53
  this._tooltips = [];
49
54
  this._filterMode = element.dataset.datatableFilterMode || "inline";
55
+ this._reparentedRowMenus = new Map();
56
+ this._reparentedRowMenuByTrigger = new WeakMap();
50
57
 
51
58
  this._rows = Array.from(this._tbody.querySelectorAll(":scope > tr[data-datatable-row]")).map((tr, index) => ({
52
59
  element: tr,
@@ -63,9 +70,12 @@ export default class DataTable {
63
70
  this._render();
64
71
  this._initBulkTooltips();
65
72
  this._initViewToggle();
73
+ this._initRowActionDropdowns();
66
74
  }
67
75
 
68
76
  dispose() {
77
+ this._disposeRowActionDropdowns();
78
+ this._restoreRowActionMenus();
69
79
  this._listeners.forEach(({ target, type, handler, options }) => {
70
80
  target.removeEventListener(type, handler, options);
71
81
  });
@@ -87,6 +97,141 @@ export default class DataTable {
87
97
  this._listeners.push({ target, type, handler, options });
88
98
  }
89
99
 
100
+ // Row menus are authored next to their trigger so the public HTML stays
101
+ // inspectable, but when opened they are temporarily moved under body.
102
+ // That keeps the menu out of the scroll wrapper's layout math, so Safari
103
+ // cannot clip it at the rounded frame edge or shift the visible table
104
+ // slice while Popper positions it against the viewport.
105
+ // Table and card views both wrap row actions in .table-row-actions, but
106
+ // .table-responsive only contains the table view; card triggers must be
107
+ // selected separately so both views get the same fixed Popper config.
108
+ _rowActionTriggers() {
109
+ return this._element.querySelectorAll(
110
+ ".table-responsive .table-row-actions [data-bs-toggle=\"dropdown\"], [data-datatable-card] .table-row-actions [data-bs-toggle=\"dropdown\"]"
111
+ );
112
+ }
113
+
114
+ _initRowActionDropdowns() {
115
+ const Dropdown = this._bootstrap("Dropdown");
116
+ if (!Dropdown) {
117
+ return;
118
+ }
119
+ this._rowActionTriggers().forEach((trigger) => {
120
+ 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
+ }),
141
+ });
142
+ });
143
+ }
144
+
145
+ // dispose() alone leaves any open row-action Dropdown's show/aria-expanded/
146
+ // data-bs-popper state and Bootstrap instance behind; hide and dispose each
147
+ // one first so a subsequent re-init starts from a clean state.
148
+ _disposeRowActionDropdowns() {
149
+ const Dropdown = this._bootstrap("Dropdown");
150
+ if (!Dropdown) {
151
+ return;
152
+ }
153
+ this._rowActionTriggers().forEach((trigger) => {
154
+ const instance = Dropdown.getInstance(trigger);
155
+ if (!instance) {
156
+ return;
157
+ }
158
+ instance.hide();
159
+ instance.dispose();
160
+ });
161
+ }
162
+
163
+ _rowActionMenuForTrigger(trigger) {
164
+ if (!trigger?.closest?.(".table-row-actions")) {
165
+ return null;
166
+ }
167
+ return trigger.closest(".dropdown")?.querySelector(":scope > .dropdown-menu") || null;
168
+ }
169
+
170
+ _rowActionOwnerIdForTrigger(trigger) {
171
+ const rowId = trigger?.closest?.("tr[data-datatable-row]")?.id;
172
+ if (rowId) {
173
+ return rowId;
174
+ }
175
+ return trigger?.closest?.("[data-datatable-card]")?.getAttribute("data-datatable-card-for") || "";
176
+ }
177
+
178
+ _reparentRowActionMenu(trigger) {
179
+ const menu = this._rowActionMenuForTrigger(trigger);
180
+ if (!menu || this._reparentedRowMenus.has(menu)) {
181
+ return;
182
+ }
183
+ const ownerId = this._rowActionOwnerIdForTrigger(trigger);
184
+ if (ownerId) {
185
+ menu.setAttribute("data-datatable-row-action-owner", ownerId);
186
+ }
187
+ if (trigger.id) {
188
+ menu.setAttribute("data-datatable-row-action-trigger", trigger.id);
189
+ }
190
+ this._reparentedRowMenus.set(menu, {
191
+ parent: menu.parentNode,
192
+ nextSibling: menu.nextSibling,
193
+ trigger,
194
+ });
195
+ this._reparentedRowMenuByTrigger.set(trigger, menu);
196
+ this._document.body.appendChild(menu);
197
+ }
198
+
199
+ _restoreRowActionMenuForTrigger(trigger) {
200
+ const menu = this._reparentedRowMenuByTrigger.get(trigger) || this._rowActionMenuForTrigger(trigger);
201
+ if (menu) {
202
+ this._restoreRowActionMenu(menu);
203
+ }
204
+ }
205
+
206
+ _restoreRowActionMenu(menu) {
207
+ const original = this._reparentedRowMenus.get(menu);
208
+ if (!original) {
209
+ return;
210
+ }
211
+ const { parent, nextSibling, trigger } = original;
212
+ if (parent?.isConnected) {
213
+ if (nextSibling?.parentNode === parent) {
214
+ parent.insertBefore(menu, nextSibling);
215
+ } else {
216
+ parent.appendChild(menu);
217
+ }
218
+ } else {
219
+ menu.remove();
220
+ }
221
+ if (trigger) {
222
+ this._reparentedRowMenuByTrigger.delete(trigger);
223
+ }
224
+ menu.removeAttribute("data-datatable-row-action-owner");
225
+ menu.removeAttribute("data-datatable-row-action-trigger");
226
+ this._reparentedRowMenus.delete(menu);
227
+ }
228
+
229
+ _restoreRowActionMenus() {
230
+ Array.from(this._reparentedRowMenus.keys()).forEach((menu) => {
231
+ this._restoreRowActionMenu(menu);
232
+ });
233
+ }
234
+
90
235
  _bootstrap(name) {
91
236
  return this._window.bootstrap?.[name] || null;
92
237
  }
@@ -504,35 +649,62 @@ export default class DataTable {
504
649
  if (!template) {
505
650
  return;
506
651
  }
507
- template.parentNode
508
- .querySelectorAll("[data-datatable-page-number], [data-datatable-page-ellipsis]")
509
- .forEach((node) => node.remove());
510
652
 
511
653
  const pages = this._pageWindow(this._currentPage, pageCount);
512
- const fragment = this._document.createDocumentFragment();
513
- pages.forEach((page) => {
514
- const li = this._document.createElement("li");
515
- li.className = "page-item";
516
- if (page === "…") {
517
- li.dataset.datatablePageEllipsis = "";
518
- li.innerHTML = '<span class="page-link">…</span>';
519
- } else {
520
- li.dataset.datatablePageNumber = String(page);
521
- if (page === this._currentPage) {
522
- li.classList.add("active");
523
- li.setAttribute("aria-current", "page");
654
+ const existing = Array.from(
655
+ template.parentNode.querySelectorAll(
656
+ "[data-datatable-page-number], [data-datatable-page-ellipsis]"
657
+ )
658
+ );
659
+ const existingWindow = existing.map((node) =>
660
+ node.dataset.datatablePageEllipsis === undefined
661
+ ? Number(node.dataset.datatablePageNumber)
662
+ : "…"
663
+ );
664
+ // Same node-reuse rationale as the row-reorder skip above: rebuilding
665
+ // every page-number button on each click destroys and recreates the one
666
+ // the user just clicked, which blurs it (losing the focus ring and any
667
+ // keyboard position). The page window is unchanged on most clicks (any
668
+ // dataset small enough to show every page, or a prev/next inside an
669
+ // already-visible window), so only rebuild when the window itself
670
+ // differs; otherwise just resync which node is active below.
671
+ const windowChanged =
672
+ existingWindow.length !== pages.length ||
673
+ existingWindow.some((page, index) => page !== pages[index]);
674
+
675
+ if (windowChanged) {
676
+ existing.forEach((node) => node.remove());
677
+ const fragment = this._document.createDocumentFragment();
678
+ pages.forEach((page) => {
679
+ const li = this._document.createElement("li");
680
+ li.className = "page-item";
681
+ if (page === "…") {
682
+ li.dataset.datatablePageEllipsis = "";
683
+ li.innerHTML = '<span class="page-link">…</span>';
684
+ } else {
685
+ li.dataset.datatablePageNumber = String(page);
686
+ const button = this._document.createElement("button");
687
+ button.type = "button";
688
+ button.className = "page-link";
689
+ button.textContent = String(page);
690
+ button.setAttribute("aria-label", `Go to page ${page}`);
691
+ button.dataset.datatablePageGo = String(page);
692
+ li.appendChild(button);
524
693
  }
525
- const button = this._document.createElement("button");
526
- button.type = "button";
527
- button.className = "page-link";
528
- button.textContent = String(page);
529
- button.setAttribute("aria-label", `Go to page ${page}`);
530
- button.dataset.datatablePageGo = String(page);
531
- li.appendChild(button);
694
+ fragment.appendChild(li);
695
+ });
696
+ template.parentNode.insertBefore(fragment, template);
697
+ }
698
+
699
+ template.parentNode.querySelectorAll("[data-datatable-page-number]").forEach((li) => {
700
+ const isActive = Number(li.dataset.datatablePageNumber) === this._currentPage;
701
+ li.classList.toggle("active", isActive);
702
+ if (isActive) {
703
+ li.setAttribute("aria-current", "page");
704
+ } else {
705
+ li.removeAttribute("aria-current");
532
706
  }
533
- fragment.appendChild(li);
534
707
  });
535
- template.parentNode.insertBefore(fragment, template);
536
708
  }
537
709
 
538
710
  _renderPageSize() {
@@ -1040,11 +1212,13 @@ export default class DataTable {
1040
1212
  // no-op for as long as the dropdown stays open, however it gets
1041
1213
  // re-triggered.
1042
1214
  this._listen(this._element, "show.bs.dropdown", (event) => {
1215
+ this._reparentRowActionMenu(event.target);
1043
1216
  const tooltip = this._bootstrap("Tooltip")?.getInstance(event.target);
1044
1217
  tooltip?.hide();
1045
1218
  tooltip?.disable();
1046
1219
  });
1047
1220
  this._listen(this._element, "hidden.bs.dropdown", (event) => {
1221
+ this._restoreRowActionMenuForTrigger(event.target);
1048
1222
  this._bootstrap("Tooltip")?.getInstance(event.target)?.enable();
1049
1223
  if (event.target.matches("[data-datatable-filter-menu-trigger]")) {
1050
1224
  this._showFilterPickerPanel();