UNPKG

dockview-core

Version:

Zero dependency layout manager supporting tabs, groups, grids and splitviews for vanilla TypeScript

186 lines (185 loc) 8.28 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.LiveRegionModule = exports.LiveRegionService = void 0; var lifecycle_1 = require("../lifecycle"); var accessibilityMessages_1 = require("./accessibilityMessages"); var modules_1 = require("./modules"); /** Bulk transactions whose per-panel events should not each be announced. */ var isBulk = function (kind) { return kind === 'load' || kind === 'clear'; }; function createLiveRegion(politeness) { var el = document.createElement('div'); el.className = politeness === 'assertive' ? 'dv-live-region-assertive' : 'dv-live-region'; // assertive interrupts the SR (errors / cancellations); polite waits for a // pause (routine status). `alert` implies assertive, `status` implies polite. el.setAttribute('role', politeness === 'assertive' ? 'alert' : 'status'); el.setAttribute('aria-live', politeness); el.setAttribute('aria-atomic', 'true'); // Visually hidden but kept in the accessibility tree (never display:none / // visibility:hidden, which would drop it from AT). Standard clip pattern. Object.assign(el.style, { position: 'absolute', width: '1px', height: '1px', margin: '-1px', padding: '0', overflow: 'hidden', clip: 'rect(0 0 0 0)', clipPath: 'inset(50%)', whiteSpace: 'nowrap', border: '0', }); return el; } /** * Narrates layout state changes to screen readers via visually-hidden * `aria-live` regions. Free / core (WCAG 4.1.3). Announces panel open/close + * the shared `announce()` sink (the accessibility module narrates docking here too). * Two regions: a **polite** one for routine status and an **assertive** one * for errors/cancellations. The bulk load/clear burst is suppressed via the * mutation-transaction events, and an app can take over delivery entirely with * the `announcer` option. */ var LiveRegionService = /** @class */ (function (_super) { __extends(LiveRegionService, _super); function LiveRegionService(host) { var _this = _super.call(this) || this; _this._suppressDepth = 0; _this._locationSubs = new Map(); _this._host = host; _this._polite = createLiveRegion('polite'); _this._assertive = createLiveRegion('assertive'); host.element.appendChild(_this._polite); host.element.appendChild(_this._assertive); _this.addDisposables({ dispose: function () { return _this._polite.remove(); } }, { dispose: function () { return _this._assertive.remove(); } }, host.onDidAddPanel(function (panel) { return _this._announce(panel, 'open'); }), host.onDidRemovePanel(function (panel) { return _this._announce(panel, 'close'); }), // Bracket bulk transactions so a fromJSON / clear doesn't announce // every nested add/remove. host.onWillMutateLayout(function (e) { if (isBulk(e.kind)) { _this._suppressDepth++; } }), host.onDidMutateLayout(function (e) { if (isBulk(e.kind)) { _this._suppressDepth = Math.max(0, _this._suppressDepth - 1); } }), host.onDidMaximizedGroupChange(function (e) { var panel = e.group.activePanel; if (panel) { _this._announce(panel, e.isMaximized ? 'maximize' : 'restore'); } }), // Narrate a group floating / docking back / popping out. A group is // born in the grid then transitions, so track each group's previous // location and ignore the no-op initial `-> grid`. host.onDidAddGroup(function (group) { return _this._trackLocation(group); }), host.onDidRemoveGroup(function (group) { var _a; (_a = _this._locationSubs.get(group.id)) === null || _a === void 0 ? void 0 : _a.dispose(); _this._locationSubs.delete(group.id); }), { dispose: function () { _this._locationSubs.forEach(function (sub) { return sub.dispose(); }); _this._locationSubs.clear(); }, }); return _this; } LiveRegionService.prototype._trackLocation = function (group) { var _this = this; var prev = group.api.location.type; var sub = group.api.onDidLocationChange(function (e) { var next = e.location.type; if (next === prev) { return; } prev = next; var panel = group.activePanel; if (!panel) { return; } var kind = next === 'floating' ? 'float' : next === 'popout' ? 'popout' : 'dock'; _this._announce(panel, kind); }); this._locationSubs.set(group.id, sub); }; LiveRegionService.prototype.announce = function (message, politeness) { if (politeness === void 0) { politeness = 'polite'; } // Opt-out (read live so `updateOptions({ announcements })` applies). if (this._host.options.announcements === false || this._suppressDepth > 0 || !message) { return; } // Apps can route announcements into their own SR system instead of the // built-in regions (e.g. a shared app-wide live region). var announcer = this._host.options.announcer; if (announcer) { announcer({ message: message, politeness: politeness }); return; } // Clearing first forces SRs to re-announce an identical message. var region = politeness === 'assertive' ? this._assertive : this._polite; region.textContent = ''; region.textContent = message; }; LiveRegionService.prototype._announce = function (panel, kind) { var _a, _b; // The app may localise/override the message, suppress it (null / ''), // or fall through to the default (undefined). var custom = (_b = (_a = this._host.options).getAnnouncement) === null || _b === void 0 ? void 0 : _b.call(_a, { kind: kind, panel: panel }); if (custom === null || custom === '') { return; } this.announce(custom !== null && custom !== void 0 ? custom : this._defaultMessage(panel, kind)); }; LiveRegionService.prototype._defaultMessage = function (panel, kind) { var _a; var m = (0, accessibilityMessages_1.resolveMessages)(this._host.options.messages); var name = (_a = panel.title) !== null && _a !== void 0 ? _a : panel.id; switch (kind) { case 'open': return m.panelOpened(name); case 'close': return m.panelClosed(name); case 'maximize': return m.groupMaximized(name); case 'restore': return m.groupRestored(name); case 'float': return m.groupFloated(name); case 'dock': return m.groupDocked(name); case 'popout': return m.groupPoppedOut(name); } }; return LiveRegionService; }(lifecycle_1.CompositeDisposable)); exports.LiveRegionService = LiveRegionService; exports.LiveRegionModule = (0, modules_1.defineModule)({ name: 'LiveRegion', serviceKey: 'liveRegionService', create: function (host) { return new LiveRegionService(host); }, });