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 | })(); |