aipackage/cezen-portal/experience.js

71 lines
3.1 KiB
JavaScript

/** Shared, dependency-free customer experience primitives for Nexus One AI. */
(function (global) {
'use strict';
var STATES = ['loading', 'empty', 'ready', 'degraded', 'failed', 'restricted', 'unavailable', 'stale'];
var SEVERITIES = ['info', 'warning', 'critical'];
function canonicalState(value) {
value = String(value || '').toLowerCase().replace(/[_ ]+/g, '-');
var aliases = { ok: 'ready', healthy: 'ready', limited: 'degraded', warning: 'degraded', error: 'failed', critical: 'failed', not_ready: 'failed' };
value = aliases[value] || value;
return STATES.indexOf(value) >= 0 ? value : 'unavailable';
}
function announce(message, priority) {
var id = priority === 'assertive' ? 'cezen-live-assertive' : 'cezen-live-polite';
var region = document.getElementById(id);
if (!region) {
region = document.createElement('div');
region.id = id;
region.className = 'sr-only';
region.setAttribute('aria-live', priority === 'assertive' ? 'assertive' : 'polite');
region.setAttribute('aria-atomic', 'true');
document.body.appendChild(region);
}
region.textContent = '';
global.setTimeout(function () { region.textContent = String(message || ''); }, 20);
}
function renderState(element, payload) {
if (!element) return;
payload = payload || {};
var state = canonicalState(payload.state || payload.status);
var severity = SEVERITIES.indexOf(payload.severity) >= 0 ? payload.severity : (state === 'failed' ? 'critical' : state === 'degraded' || state === 'stale' ? 'warning' : 'info');
element.classList.remove.apply(element.classList, STATES.map(function (item) { return 'cx-state-' + item; }));
element.classList.add('cx-state', 'cx-state-' + state);
element.setAttribute('data-state', state);
element.setAttribute('data-severity', severity);
element.setAttribute('role', severity === 'critical' ? 'alert' : 'status');
element.textContent = payload.summary || state.replace(/-/g, ' ');
}
function formatFreshness(observedAt, now) {
if (!observedAt) return 'Freshness unknown';
var observed = new Date(observedAt).getTime();
var current = now ? new Date(now).getTime() : Date.now();
if (!isFinite(observed)) return 'Freshness unknown';
var seconds = Math.max(0, Math.round((current - observed) / 1000));
if (seconds < 60) return 'Observed ' + seconds + ' seconds ago';
if (seconds < 3600) return 'Observed ' + Math.floor(seconds / 60) + ' minutes ago';
return 'Observed ' + Math.floor(seconds / 3600) + ' hours ago';
}
function focusFirstError(container) {
var target = (container || document).querySelector('[aria-invalid="true"], .field-error, .cx-error');
if (!target) return false;
if (!target.hasAttribute('tabindex')) target.setAttribute('tabindex', '-1');
target.focus();
return true;
}
global.CezenExperience = Object.freeze({
states: STATES.slice(),
canonicalState: canonicalState,
announce: announce,
renderState: renderState,
formatFreshness: formatFreshness,
focusFirstError: focusFirstError
});
})(window);