71 lines
3.1 KiB
JavaScript
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);
|