DRY duplicated helpers (escapeAttr, belongsToForm, isSubmitControl, isEditableTarget) into web/js/core/app-form-utils.js and update 7 consumers. Add aria-live="polite" to flash messages, async messages, and search results for screen reader announcements. Add prefers-reduced-motion support to CSS tooltips. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
55 lines
1.9 KiB
JavaScript
55 lines
1.9 KiB
JavaScript
/**
|
|
* Shared form utility helpers — used by confirm-actions, detail-action-policy,
|
|
* and detail-page-factory to avoid duplicated submit/form-ownership logic.
|
|
*/
|
|
|
|
/** Escape a string for safe use inside a CSS attribute selector value. */
|
|
export const escapeAttr = (value) => {
|
|
const raw = String(value || '');
|
|
if (typeof CSS !== 'undefined' && CSS && typeof CSS.escape === 'function') {
|
|
return CSS.escape(raw);
|
|
}
|
|
return raw.replace(/["\\]/g, '\\$&');
|
|
};
|
|
|
|
/** Check whether a control belongs to a given form (by .form, form="" attr, or containment). */
|
|
export const belongsToForm = (control, form) => {
|
|
if (!(control instanceof HTMLElement) || !(form instanceof HTMLFormElement)) {
|
|
return false;
|
|
}
|
|
const owner = control.form;
|
|
if (owner instanceof HTMLFormElement) {
|
|
return owner === form;
|
|
}
|
|
const controlFormAttr = String(control.getAttribute('form') || '').trim();
|
|
const formId = String(form.id || '').trim();
|
|
if (controlFormAttr !== '' && formId !== '') {
|
|
return controlFormAttr === formId;
|
|
}
|
|
return form.contains(control);
|
|
};
|
|
|
|
/** Check whether an element is a submit button or submit input. */
|
|
export const isSubmitControl = (control) => {
|
|
if (!(control instanceof HTMLElement)) {
|
|
return false;
|
|
}
|
|
if (control instanceof HTMLButtonElement) {
|
|
const type = String(control.getAttribute('type') || 'submit').trim().toLowerCase();
|
|
return type === '' || type === 'submit';
|
|
}
|
|
if (control instanceof HTMLInputElement) {
|
|
const type = String(control.type || '').trim().toLowerCase();
|
|
return type === 'submit' || type === 'image';
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/** Check whether the event target is an editable element (input, textarea, select, contentEditable). */
|
|
export const isEditableTarget = (target) => {
|
|
if (!target) { return false; }
|
|
if (target.isContentEditable) { return true; }
|
|
const tag = target.tagName;
|
|
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
};
|