diff --git a/dt-assets/js/modular-list-bulk.js b/dt-assets/js/modular-list-bulk.js
index 7b055e334..4cac3fc6a 100644
--- a/dt-assets/js/modular-list-bulk.js
+++ b/dt-assets/js/modular-list-bulk.js
@@ -822,6 +822,11 @@
// For boolean fields, include even if false (false is a valid value)
if (fieldType === 'boolean') {
updatePayload[fieldKey] = fieldValue === true;
+ } else if (fieldType === 'user_select') {
+ const normalizedUser = normalizeUserSelectValue(fieldValue);
+ if (normalizedUser !== null) {
+ updatePayload[fieldKey] = normalizedUser;
+ }
} else {
updatePayload[fieldKey] = fieldValue;
}
@@ -918,6 +923,45 @@
return Array.isArray(value) ? value : [];
}
+ /**
+ * Normalize a user_select value to the "user-{id}" string format expected by
+ * the backend and conditional-removal logic.
+ *
+ * In practice we only see:
+ * - numbers from ComponentService.convertValue('DT-USERS-CONNECTION', ...)
+ * - strings like "user-5" or "5" from API/legacy data
+ */
+ function normalizeUserSelectValue(rawValue) {
+ if (rawValue === null || rawValue === undefined) {
+ return null;
+ }
+
+ // Numbers (ComponentService.convertValue for dt-users-connection in single
+ // mode returns a plain integer user ID, or 0/"" when empty)
+ if (typeof rawValue === 'number') {
+ return rawValue > 0 ? `user-${rawValue}` : null;
+ }
+
+ // Strings
+ if (typeof rawValue === 'string') {
+ const trimmed = rawValue.trim();
+ if (!trimmed) {
+ return null;
+ }
+ if (trimmed.startsWith('user-')) {
+ return trimmed;
+ }
+ if (/^\d+$/.test(trimmed)) {
+ return `user-${trimmed}`;
+ }
+ // Any other free-form string (e.g. display name) is not a valid payload
+ // for the backend user_select handler.
+ return null;
+ }
+
+ return null;
+ }
+
/**
* Update bulkEditSelectedFields for the share field from dt-users-connection value.
* Called on initial value and on change; lives at module scope to avoid redefining per render.
@@ -1100,32 +1144,6 @@
return null;
}
- // Special case: user_select (uses typeahead, not web component)
- if (fieldType === 'user_select') {
- const fieldId = `bulk_${fieldKey}`;
- const userInput = fieldWrapper.find(`.js-typeahead-${fieldId}`);
- if (userInput.length > 0) {
- const selectedUserId = userInput.data('selected-user-id');
- if (selectedUserId) {
- return `user-${selectedUserId}`;
- }
- // Fallback: check typeahead instance
- const typeaheadSelector = `.js-typeahead-${fieldId}`;
- const typeaheadInstance = window.Typeahead?.[typeaheadSelector];
- if (
- typeaheadInstance &&
- typeaheadInstance.items &&
- typeaheadInstance.items.length > 0
- ) {
- const selectedItem = typeaheadInstance.items[0];
- if (selectedItem && selectedItem.ID) {
- return `user-${selectedItem.ID}`;
- }
- }
- }
- return null;
- }
-
// Special case: communication_channel (dt-multi-text needs special formatting)
if (fieldType === 'communication_channel') {
const multiTextComponent =
@@ -2121,107 +2139,9 @@
console.error('ComponentService initialization error:', e);
}
}
-
- // Initialize field-specific handlers if needed
- initializeBulkEditFieldHandlers(fieldKey, fieldType);
});
}
- function initializeBulkEditFieldHandlers(fieldKey, fieldType) {
- // Special case: user_select uses typeahead (not a web component)
- if (fieldType === 'user_select') {
- const fieldId = `bulk_${fieldKey}`;
- const userInput = $(`.js-typeahead-${fieldId}`);
-
- if (userInput.length) {
- // Destroy existing typeahead instance if it exists (for restore scenarios)
- const typeaheadSelector = `.js-typeahead-${fieldId}`;
- if (window.Typeahead && window.Typeahead[typeaheadSelector]) {
- try {
- // Try to destroy the existing instance
- if (window.Typeahead[typeaheadSelector].destroy) {
- window.Typeahead[typeaheadSelector].destroy();
- }
- delete window.Typeahead[typeaheadSelector];
- } catch (e) {
- // If destroy fails, just delete the reference
- delete window.Typeahead[typeaheadSelector];
- }
- }
-
- // Initialize typeahead
- $.typeahead({
- input: `.js-typeahead-${fieldId}`,
- minLength: 0,
- maxItem: 0,
- accent: true,
- searchOnFocus: true,
- source: window.TYPEAHEADS.typeaheadUserSource(),
- templateValue: '{{name}}',
- template: function (query, item) {
- return `
-
-
- ${window.SHAREDFUNCTIONS.escapeHTML(item.name)}
-
- ${item.status_color ? `
` : ''}
- ${
- item.update_needed && item.update_needed > 0
- ? `
-
- ${window.SHAREDFUNCTIONS.escapeHTML(item.update_needed)}
- `
- : ''
- }
-
`;
- },
- dynamic: true,
- hint: true,
- emptyTemplate: window.SHAREDFUNCTIONS.escapeHTML(
- window.wpApiShare.translations.no_records_found,
- ),
- callback: {
- onClick: function (node, a, item, event) {
- event.preventDefault();
- this.hideLayout();
- this.resetInput();
-
- // Set the selected user value
- const resultContainer = $(`#${fieldId}-result-container`);
- resultContainer.html(
- `${window.SHAREDFUNCTIONS.escapeHTML(item.name)}`,
- );
-
- // Store the selected user ID in data attributes for later collection
- userInput.data('selected-user-id', item.ID);
- userInput.data('selected-user-name', item.name);
- resultContainer.data('selected-user-id', item.ID);
- resultContainer.data('selected-user-name', item.name);
- },
- onResult: function (node, query, result, resultCount) {
- const resultContainer = $(`#${fieldId}-result-container`);
- if (resultCount > 0) {
- resultContainer.html(
- `${resultCount} ${window.wpApiShare.translations.user_found || 'user(s) found'}`,
- );
- } else {
- resultContainer.html('');
- }
- },
- onHideLayout: function () {
- $(`#${fieldId}-result-container`).html('');
- },
- },
- });
- }
- return;
- }
-
- // For all web components: ComponentService.initialize() handles initialization
- // The global dt:get-data listener handles data fetching
- // No per-field-type initialization needed
- }
-
/**
* Field Clear/Restore Handlers
*/
@@ -2361,20 +2281,33 @@
displayHtml += 'No option selected';
}
} else if (fieldType === 'user_select') {
- // user_select returns "user-{id}" format
+ // user_select returns "user-{id}" for payloads, but the dt-users-connection
+ // component exposes richer objects for display. Support both shapes.
if (valuesToRemove) {
- const userId = valuesToRemove.replace('user-', '');
- // Try to get user name from typeahead data or make a simple display
- const fieldId = `bulk_${fieldKey}`;
- const userInput = $(`.js-typeahead-${fieldId}`);
- let userName = `User ${userId}`;
- if (userInput.length > 0) {
- const storedName = userInput.data('selected-user-name');
- if (storedName) {
- userName = storedName;
+ let userId = null;
+ let userName = null;
+
+ if (Array.isArray(valuesToRemove)) {
+ const first = valuesToRemove[0];
+ if (first) {
+ userId = first.id || first.user_id || null;
+ userName = first.label || first.name || null;
}
+ } else if (typeof valuesToRemove === 'object') {
+ userId = valuesToRemove.id || valuesToRemove.user_id || null;
+ userName = valuesToRemove.label || valuesToRemove.name || null;
+ } else if (typeof valuesToRemove === 'string') {
+ userId = valuesToRemove.replace('user-', '');
+ }
+
+ if (userId) {
+ if (!userName) {
+ userName = `User ${userId}`;
+ }
+ displayHtml += `${window.SHAREDFUNCTIONS.escapeHTML(userName)}`;
+ } else {
+ displayHtml += 'No user selected';
}
- displayHtml += `${window.SHAREDFUNCTIONS.escapeHTML(userName)}`;
} else {
displayHtml += 'No user selected';
}
@@ -2420,9 +2353,18 @@
if (component && component.value) {
rawValueWithLabels = component.value;
}
+ } else if (fieldType === 'user_select') {
+ const usersComponent = fieldWrapper.find('dt-users-connection')[0];
+ if (usersComponent && usersComponent.value) {
+ const items = normalizeShareComponentItems(usersComponent.value);
+ if (items.length > 0) {
+ rawValueWithLabels = items;
+ }
+ }
}
let hasValues = false;
+ let normalizedUserValueToRemove = null;
if (
currentValue !== null &&
currentValue !== undefined &&
@@ -2435,18 +2377,25 @@
) {
const values = currentValue?.values || currentValue;
hasValues = Array.isArray(values) && values.length > 0;
- } else if (fieldType === 'key_select' || fieldType === 'user_select') {
+ } else if (fieldType === 'key_select') {
hasValues = true;
+ } else if (fieldType === 'user_select') {
+ normalizedUserValueToRemove = normalizeUserSelectValue(currentValue);
+ hasValues = !!normalizedUserValueToRemove;
}
}
if (supportsSelectiveRemoval && hasValues) {
fieldData.operation = 'remove';
- fieldData.valuesToRemove = currentValue;
+ fieldData.valuesToRemove =
+ fieldType === 'user_select'
+ ? normalizedUserValueToRemove
+ : currentValue;
fieldData.rawValueWithLabels = rawValueWithLabels;
const displayValue =
- fieldType === 'connection' && rawValueWithLabels
+ (fieldType === 'connection' || fieldType === 'user_select') &&
+ rawValueWithLabels
? rawValueWithLabels
: currentValue;
const displayHtml = renderValuesToRemoveDisplay(
diff --git a/dt-assets/js/shared-functions.js b/dt-assets/js/shared-functions.js
index 53617ed07..e760a4fc3 100644
--- a/dt-assets/js/shared-functions.js
+++ b/dt-assets/js/shared-functions.js
@@ -1040,29 +1040,7 @@ window.SHAREDFUNCTIONS = {
}
case 'user_select': {
- // user_select uses legacy typeahead (not a web component)
- const fieldId = id;
- return ``;
+ return ``;
}
case 'communication_channel':
diff --git a/dt-assets/scss/_list.scss b/dt-assets/scss/_list.scss
index ba1d59a96..89f4c40d3 100644
--- a/dt-assets/scss/_list.scss
+++ b/dt-assets/scss/_list.scss
@@ -691,6 +691,7 @@ table.js-list {
dt-datetime label,
dt-number label,
dt-tags label,
+ dt-users-connection label,
dt-user-select label,
dt-connection label,
dt-location label {