Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/www/js/closer.js

4.7 KiB, 1 run

created by r2519314175:1349, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1/* ============================================================
2 Daimond — the closer.
3 ------------------------------------------------------------
4 One cross, built one way, for every dialog, popover, drawer and
5 sheet the app puts over the top of itself.
6
7 It exists because the app had five of them. A panel head wrote
8 its × in static markup, the tile dialog drew one in JS, the
9 phone sheet used the '✕' character, the admin drawer used '×',
10 and the appearance menu — the one the user actually reported —
11 had none at all. Five spellings of one control drift, and four
12 of the five were under the thumb's floor.
13
14 The rules the mechanism keeps:
15
16 A CROSS CLOSES THE ONE THING IT SITS ON. Never something
17 larger. `name` says which thing, out loud, so a screen reader
18 hears "Close Appearance and layout" rather than a fourth
19 button called Close.
20
21 THE INK IS DRAWN, NOT TYPED. A glyph's ink is not centred in
22 its own em box and which box it gets depends on whichever font
23 the platform found it in; a path on the 24-unit grid is
24 centred by geometry.
25
26 THE TARGET IS A THUMB'S, THE INK IS AN EYE'S. `.ui-close` is
27 28px on a pointer and 44px where the pointer is coarse (see
28 app.css), while the drawn cross stays 16px in both. The floor
29 is the point of this file: on a phone the appearance menu left
30 19px of screen either side of itself to tap, which is not a
31 way out.
32
33 Classic, and loaded before everything that builds a surface, so
34 `window.DaimondCloser` is there whether the caller is a module
35 (daimond.js) or a classic script (workspace.js, mobile.js).
36 ============================================================ */
37(function () {
38 'use strict';
39
40 /// The app's cross. The same path the static markup uses (www/index.html,
41 /// every panel head), so a closer built in JS is the identical mark to one
42 /// written in HTML.
43 var SVG = '<svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>';
44
45 function t(k, v) { return window.DaimondI18n ? window.DaimondI18n.t(k, v) : k; }
46
47 /// Mark a string so a language change repaints it. Only worth doing for a
48 /// string the tables can be traced back to: an interpolated one ("Close
49 /// Appearance and layout") has no key of its own and is set plainly.
50 function say(node, attr, text) {
51 if (window.DaimondI18n && window.DaimondI18n.mark) window.DaimondI18n.mark(node, attr, text);
52 else if (attr === 'title') node.title = text;
53 else node.setAttribute(attr, text);
54 }
55
56 /// The closer for one surface.
57 ///
58 /// # Arguments
59 /// * `opts.name` - What this closes, in the user's own words. It becomes the
60 /// spoken name, so a reader hears which of the app's crosses this is.
61 /// * `opts.onClose` - Called when it is pressed. The event is stopped first:
62 /// a closer inside a popover must not also read as a click outside one.
63 /// * `opts.cls` - Extra classes, for a surface with its own hooks.
64 function make(opts) {
65 opts = opts || {};
66 var b = document.createElement('button');
67 b.type = 'button'; // never submit an enclosing form
68 b.className = 'ui-close' + (opts.cls ? ' ' + opts.cls : '');
69 b.innerHTML = SVG; // trusted markup, built here
70 say(b, 'title', t('common.close'));
71 b.setAttribute('aria-label', opts.name
72 ? t('common.close_named', { name: opts.name })
73 : t('common.close'));
74 if (typeof opts.onClose === 'function') {
75 b.addEventListener('click', function (e) {
76 e.preventDefault();
77 e.stopPropagation();
78 opts.onClose(e);
79 });
80 }
81 return b;
82 }
83
84 /// A title row: what the surface is, and the way out of it, in the corner
85 /// every other closer in the app holds.
86 ///
87 /// The title is not decoration. The appearance menu was identified only by
88 /// the button you had pressed to open it, which on a phone is behind the
89 /// menu — so the cross had nothing on screen to say what it would close.
90 ///
91 /// `opts.titleEl` puts an element the caller already built (a dialog's own
92 /// `h2`) in the row instead of a fresh span, so a card does not end up
93 /// naming itself twice. `opts.closeCls` adds a class to the cross, for the
94 /// hooks a surface's own code and its verifiers reach for.
95 function head(title, opts) {
96 opts = opts || {};
97 var row = document.createElement('div');
98 row.className = 'ui-head' + (opts.cls ? ' ' + opts.cls : '');
99 var t0 = opts.titleEl;
100 if (!t0) {
101 t0 = document.createElement('div');
102 t0.className = 'ui-head-title';
103 t0.textContent = title || '';
104 }
105 row.appendChild(t0);
106 row.appendChild(make({ name: opts.name || title, onClose: opts.onClose, cls: opts.closeCls }));
107 return row;
108 }
109
110 window.DaimondCloser = {
111 SVG: SVG,
112 make: make,
113 head: head,
114 };
115})();