fix: web/ quick wins — CSS cleanup, ARIA tabs, and file header comments

CSS:
- Remove duplicate li::before block in app-search.css
- Fix typo: search-reuslt → search-result in app-search.css
- Remove commented-out CSS rules in app-flash.css
- Add descriptive header comment to all 27 CSS component files

JS:
- Complete WAI-ARIA Tabs pattern: generate IDs, add aria-controls on
  tab buttons and aria-labelledby on tab panels (app-tabs.js)
- Add JSDoc header comments to ~33 JS files (core + components)
- Add explanatory block comment to app-boot.js (why classic IIFE)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 22:21:37 +01:00
parent ee5930d728
commit d9f07dcd63
62 changed files with 147 additions and 23 deletions

View File

@@ -1,3 +1,6 @@
/**
* Renders removable pill chips for currently active grid filters.
*/
const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({
'&': '&amp;',
'<': '&lt;',

View File

@@ -1,3 +1,6 @@
/**
* Shows/hides bulk action buttons based on Grid.js row selection state.
*/
import { warnOnce } from '../core/app-dom.js';
export function bindBulkVisibility({

View File

@@ -1,3 +1,6 @@
/**
* Toggles color input between custom value and default/inherited color.
*/
import { warnOnce } from '../core/app-dom.js';
const toggles = document.querySelectorAll('[data-color-default-toggle]');

View File

@@ -1,3 +1,6 @@
/**
* Declarative confirm-before-action — intercepts clicks and submits on [data-confirm-message] elements.
*/
import { confirmDialog, requestSubmitWithFallback } from '../core/app-confirm-dialog.js';
const CONFIRM_ATTR = 'data-confirm-message';

View File

@@ -1,3 +1,6 @@
/**
* High-contrast mode toggle with localStorage persistence.
*/
import { requireEl } from '../core/app-dom.js';
const STORAGE_KEY = 'app-contrast';

View File

@@ -1,3 +1,6 @@
/**
* Click-to-copy badge — copies text to clipboard with visual feedback.
*/
const COPY_SELECTOR = '.badge[data-copy="true"]';
const COPIED_CLASS = 'is-copied';

View File

@@ -1,3 +1,6 @@
/**
* Shows/hides custom field option list based on field type selection.
*/
import { warnOnce } from '../core/app-dom.js';
const TYPES_WITH_OPTIONS = new Set(['select', 'multiselect']);

View File

@@ -1,3 +1,6 @@
/**
* Initializes detail page open-state persistence and action policy.
*/
import { initPersistedDetailsGroup } from '../core/app-details-open-state.js';
const controllerRegistry = new WeakMap();

View File

@@ -1,3 +1,6 @@
/**
* Slide-in filter drawer with focus trap, scroll lock, and apply/reset/discard lifecycle.
*/
import { warnOnce } from '../core/app-dom.js';
export function initFilterDrawer(options = {}) {

View File

@@ -1,3 +1,6 @@
/**
* Auto-dismisses flash notices after their data-flash-timeout expires.
*/
export function initFlashAutoDismiss(options = {}) {
const {
selector = '.flash-stack .notice[data-flash-timeout]',

View File

@@ -1,3 +1,6 @@
/**
* Triggers FSLightbox refresh after dynamic content changes.
*/
const refreshFsLightbox = () => {
if (typeof window.requestFsLightboxRefresh === 'function') {
return window.requestFsLightboxRefresh();

View File

@@ -1,3 +1,6 @@
/**
* Global search sidebar — debounced fetch with live result counts and Ctrl+K shortcut.
*/
import { requireEl, optionalEl, warnOnce } from '../core/app-dom.js';
const input = requireEl('#side-search', { module: 'global-search' });

View File

@@ -1,3 +1,6 @@
/**
* Cascading multi-select — filters child options based on parent selection.
*/
import { getMultiSelectInstance } from './app-multiselect-init.js';
import { warnOnce } from '../core/app-dom.js';

View File

@@ -1,3 +1,6 @@
/**
* Initializes MultiSelect.js instances from select[multiple] elements.
*/
import { warnOnce } from '../core/app-dom.js';
let multiSelectLoader = null;

View File

@@ -1,3 +1,6 @@
/**
* Browser history integration — handles back/forward navigation state.
*/
const updateHistoryButtons = () => {
const back = document.querySelector('#global-back');
const forward = document.querySelector('#global-forward');

View File

@@ -1,3 +1,6 @@
/**
* Editor.js integration — initializes block editor for CMS page content.
*/
import EditorJS from '../../vendor/editorjs/editorjs.mjs';
import { warnOnce } from '../core/app-dom.js';

View File

@@ -1,3 +1,6 @@
/**
* Real-time password validation hints — checks length, uppercase, digit, special char.
*/
import { warnOnce } from '../core/app-dom.js';
const rules = {

View File

@@ -1,3 +1,6 @@
/**
* Show/hide password toggle button injected next to password inputs.
*/
export function initPasswordToggles() {
const inputs = document.querySelectorAll('input[type="password"]');
if (!inputs.length) {return;}

View File

@@ -1,3 +1,6 @@
/**
* Tracks settings page interactions for telemetry.
*/
const initTelemetrySettings = () => {
const toggle = document.querySelector('[data-telemetry-enabled-toggle]');
const samplingFieldset = document.querySelector('[data-telemetry-sampling-fieldset]');

View File

@@ -1,3 +1,6 @@
/**
* Wires up the standard detail page factory with titlebar and form state.
*/
import { autoInitStandardDetailPages } from '../core/app-detail-page-factory.js';
export function initStandardDetailPages(root = document) {

View File

@@ -90,13 +90,18 @@ export function initTabs(root = document) {
};
// ----------------------------------------------------------------
// ARIA: role="tablist" on the nav, not the outer container
// ARIA: role, aria-controls, aria-labelledby (WAI-ARIA Tabs pattern)
// ----------------------------------------------------------------
const nav = container.querySelector('.app-tabs-nav');
const tabIdPrefix = container.id || `tabs-${Math.random().toString(36).slice(2, 8)}`;
// Initialize tabs
tabs.forEach(tab => {
const tabName = tab.dataset.tab;
const tabId = `${tabIdPrefix}-tab-${tabName}`;
const panelId = `${tabIdPrefix}-panel-${tabName}`;
tab.setAttribute('role', 'tab');
tab.id = tabId;
tab.setAttribute('aria-controls', panelId);
tab.addEventListener('click', (e) => {
e.preventDefault();
activateTab(tab.dataset.tab);
@@ -106,7 +111,12 @@ export function initTabs(root = document) {
});
panels.forEach(panel => {
const panelName = panel.dataset.tabPanel;
const panelId = `${tabIdPrefix}-panel-${panelName}`;
const tabId = `${tabIdPrefix}-tab-${panelName}`;
panel.setAttribute('role', 'tabpanel');
panel.id = panelId;
panel.setAttribute('aria-labelledby', tabId);
});
if (nav) {

View File

@@ -1,3 +1,6 @@
/**
* Shows/hides SSO configuration fields based on tenant SSO toggle.
*/
const roots = document.querySelectorAll('[data-tenant-sso-root]');
const syncControlState = (container, show) => {

View File

@@ -1,3 +1,6 @@
/**
* Tenant context switcher dropdown with logo display.
*/
import { showAsyncFlash } from './app-async-flash.js';
import { optionalEl, warnOnce } from '../core/app-dom.js';

View File

@@ -1,3 +1,6 @@
/**
* Theme switcher — light/dark toggle and menu with server-side persistence.
*/
import { optionalEl, warnOnce } from '../core/app-dom.js';
const setTheme = (theme) => {

View File

@@ -1,3 +1,6 @@
/**
* Aside panel switcher — opens/closes named sidebar panels.
*/
import { requireEl, warnOnce } from '../core/app-dom.js';
import { initPersistedDetailsGroup } from '../core/app-details-open-state.js';

View File

@@ -1,3 +1,6 @@
/**
* Toggles visibility of the detail page aside column.
*/
import { optionalEl, warnOnce } from '../core/app-dom.js';
export function initDetailsAsideToggle(options = {}) {

View File

@@ -1,3 +1,6 @@
/**
* Sidebar collapse/show toggle with localStorage persistence and Ctrl+B shortcut.
*/
import { requireEl, warnOnce } from '../core/app-dom.js';
export function initSidebarToggle(options = {}) {