We provide a new method, `at()`, that is on the prototype of the built-in indexable objects: Array, String, and TypedArrays objects. The method supports relative indexing from the end when passed a negative index.
17 |
18 |
19 |
20 |
Additions to Properties of the Array Prototype Object
21 |
22 |
23 |
Array.prototype.at ( _index_ )
24 |
25 | 1. Let _O_ be ? ToObject(*this* value).
26 | 1. Let _len_ be ? LengthOfArrayLike(_O_).
27 | 1. Let _relativeIndex_ be ? ToInteger(_index_).
28 | 1. If _relativeIndex_ ≥ 0, then
29 | 1. Let _k_ be _relativeIndex_.
30 | 1. Else,
31 | 1. Let _k_ be _len_ + _relativeIndex_.
32 | 1. If _k_ < 0 or _k_ ≥ _len_, then return *undefined*.
33 | 1. Return ? Get(_O_, ! ToString(_k_)).
34 |
35 |
36 |
37 |
38 |
39 |
Modifications to Properties of the Array Prototype Object
40 |
41 |
42 |
Array.prototype [ @@unscopables ]
43 |
The initial value of the @@unscopables data property is an object created by the following steps:
Additions to Properties of the String Prototype Object
64 |
65 |
66 |
String.prototype.at ( _index_ )
67 |
68 | 1. Let _O_ be ? RequireObjectCoercible(*this* value).
69 | 1. Let _S_ be ? ToString(_O_).
70 | 1. Let _len_ be the length of _S_.
71 | 1. Let _relativeIndex_ be ? ToInteger(_index_).
72 | 1. If _relativeIndex_ ≥ 0, then
73 | 1. Let _k_ be _relativeIndex_.
74 | 1. Else,
75 | 1. Let _k_ be _len_ + _relativeIndex_.
76 | 1. If _k_ < 0 or _k_ ≥ _len_, then return *undefined*.
77 | 1. Return the String value consisting of only the code unit at position _k_ in _S_.
78 |
79 |
80 |
81 |
82 |
83 |
Additions to Properties of the %TypedArray.prototype% Object
84 |
85 |
86 |
%TypedArray%.prototype.at ( _index_ )
87 |
88 | 1. Let _O_ be the *this* value.
89 | 1. Perform ? ValidateTypedArray(_O_).
90 | 1. Let _len_ be _O_.[[ArrayLength]].
91 | 1. Let _relativeIndex_ be ? ToInteger(_index_).
92 | 1. If _relativeIndex_ ≥ 0, then
93 | 1. Let _k_ be _relativeIndex_.
94 | 1. Else,
95 | 1. Let _k_ be _len_ + _relativeIndex_.
96 | 1. If _k_ < 0 or _k_ ≥ _len_, then return *undefined*.
97 | 1. Return ? Get(_O_, ! ToString(_k_)).
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Proposal for an `.at()` method on all the built-in indexables
2 |
3 | A TC39 proposal to add an .at() method to all the basic indexable classes (Array, String, TypedArray)
4 |
5 | **Stage: 4**
6 |
7 | **Champions: Tab Atkins, Shu-yu Guo**
8 |
9 | **Proposed Spec Text:**
10 |
11 | ToC
12 | ----
13 |
14 | 1. [Rationale](#rationale)
15 | 1. [Existing Methods](#existing-methods)
16 | 1. [Proposed Edits](#proposed-edits)
17 | 1. [Polyfill](#polyfill)
18 | 1. [Web Incompatibility History](#web-incompatibility-history)
19 | 1. [DOM Justifications](#dom-justifications)
20 | 1. [Convertable Interfaces](#convertable-interfaces)
21 | 1. [Possible Issues](#possible-issues)
22 | 1. [Possible DOM Compat Issues](#possible-dom-compat-issues)
23 |
24 | Rationale
25 | ---------
26 |
27 | For many years, programmers have asked for the ability to do "negative indexing" of JS Arrays, like you can do with Python. That is, asking for the ability to write `arr[-1]` instead of `arr[arr.length-1]`, where negative numbers count backwards from the last element.
28 |
29 | Unfortunately, JS's language design makes this impossible. The `[]` syntax is not specific to Arrays and Strings; it applies to all objects. Referring to a value by index, like `arr[1]`, actually just refers to the property of the object with the key "1", which is something that any object can have. So `arr[-1]` already "works" in today's code, but it returns the value of the "-1" property of the object, rather than returning an index counting back from the end.
30 |
31 | There have been many attempts to work around this; the most recent is a restricted proposal to make it easier to access just the last element of an array () via a `.last` property.
32 |
33 | This proposal instead adopts a more common approach, and suggests adding a `.at()` method to Array, String, and TypedArray, which takes an integer value and returns the item at that index, with the negative-number semantics as described above.
34 |
35 | This not only solves the long-standing request in an easy way, but also happens to solve a separate issue for [various DOM APIs, described below](#dom-justifications).
36 |
37 | ### Existing Methods
38 |
39 | Currently, to access a value from the end of an indexable object, the common practice is to write `arr[arr.length - N]`, where N is the Nth item from the end (starting at 1). This requires naming the indexable twice, additionally adds 7 more characters for the `.length`, and is hostile to anonymous values; you can't use this technique to grab the last item of the return value of a function unless you first store it in a temp variable.
40 |
41 | Another method that avoids some of those drawbacks, but has some performance drawbacks of its own, is `arr.slice(-N)[0]`. This avoids repeating the name, and thus is friendly to anonymous values as well. However, the spelling is a little weird, particularly the trailing `[0]` (since `.slice()` returns an Array). Also, a temporary array is created with all the contents of the source from the desired item to the end, only to be immediately thrown away after retrieving the first item.
42 |
43 | Note, however, the fact that `.slice()` (and related methods like `.splice()`) already have the notion of negative indexes, and resolve them exactly as desired.
44 |
45 | Possible Issues
46 | ---------------
47 |
48 | `.at()` might also be web incompatible for reasons yet unknown.
49 |
50 | Proposed Edits
51 | --------------
52 |
53 |
54 |
55 | Polyfill
56 | --------
57 |
58 | (Rough polyfill; correctly implements the behavior for well-behaved objects, but not guaranteed to match spec behavior precisely for edge cases, like calling the method on `undefined`.)
59 |
60 | ```js
61 | function at(n) {
62 | // ToInteger() abstract op
63 | n = Math.trunc(n) || 0;
64 | // Allow negative indexing from the end
65 | if (n < 0) n += this.length;
66 | // OOB access is guaranteed to return undefined
67 | if (n < 0 || n >= this.length) return undefined;
68 | // Otherwise, this is just normal property access
69 | return this[n];
70 | }
71 |
72 | const TypedArray = Reflect.getPrototypeOf(Int8Array);
73 | for (const C of [Array, String, TypedArray]) {
74 | Object.defineProperty(C.prototype, "at",
75 | { value: at,
76 | writable: true,
77 | enumerable: false,
78 | configurable: true });
79 | }
80 | ```
81 |
82 | ## Implementations
83 |
84 | * Spec-compliant polyfills using the old name of `.item()`: [Array.prototype.item](https://www.npmjs.com/package/array.prototype.item), [String.prototype.item](https://www.npmjs.com/package/string.prototype.item)
85 |
86 | ## Web Incompatibility History
87 |
88 | The original iteration of this proposal proposed the name of the method to be `.item()`. Unfortunately, this was found out to be not web compatible. Libraries, notably YUI2 and YUI3, were duck-typing objects to be DOM collections based on the presence of a `.item` property. Please see [#28](https://github.com/tc39/proposal-relative-indexing-method/issues/28), [#31](https://github.com/tc39/proposal-relative-indexing-method/issues/31), and [#32](https://github.com/tc39/proposal-relative-indexing-method/issues/32) for more details.
89 |
90 | Captured below is the original motivation for choosing the `.item()` name and the original concerns.
91 |
92 | ### DOM Justifications
93 |
94 | A recent addition to the WebIDL spec is `ObservableArray<>` (thanks @domenic!), a proxy over an Array that allows web APIs to expose something that to page authors looks exactly like an Array, but still allows the browser to intercept get/set/delete/etc of indexed properties, enforcing type checks and other requirements exactly like they do today with named properties.
95 |
96 | We plan to start using this for most APIs that want to expose a list of something, but we'd also like to, when possible, upgrade *older* APIs to use this as well; the fact that many older APIs use bespoke interfaces that badly and incompletely copy the Array interface is a consistent source of frustration for web authors.
97 |
98 | (For example, `document.querySelectorAll()` returns, not an Array, but a NodeList, which supports indexed properties and `.length`, and so can be treated as an Array in basic ways, but has only a tiny selection of the Array prototype methods. Popular methods like `.map()` are missing, requiring authors to write code like `[...document.querySelectorAll("a")].map(foo)`.)
99 |
100 | This upgrade can *almost* be done in-place, just swapping the various bespoke interfaces with ObservableArray, avoiding breaking anything that doesn't explicitly test the value's type. There is *one* exception: all of them have a `.item()` method, which returns the value at the passed index.
101 |
102 | (This is a remnant of the very old (1990s-era) belief that Java was a reasonable language to use on the web, and so APIs were designed in a "lowest common denominator" style for use in both JS and Java. Java didn't have the ability to use indexed properties at the time unless you were actually a Java array, so the .item() method was a compromise that worked identically in both languages.)
103 |
104 | It's highly likely there is code that relies on using `.item()` on these interfaces, and we don't want to risk breakage there.
105 |
106 | We *could* address this by subclassing ObservableArray and adding `.item()` in the subclass. However, that would mean the values aren't of type Array; various type-checking methods in the community looking for an Array would fail.
107 |
108 | Or we could just add `.item()` to ObservableArray itself, as it's a proxy wrapper around Array. This would be confusing and weird however, making it appear that Array had such a method even tho it's not on the prototype.
109 |
110 | The ideal solution for us, instead, is to add `.item()` to the Array prototype itself,
111 | and for completeness/consistency, to the other indexable types that support the same general suite of index-related properties like `.slice()`.
112 |
113 | As such, the name `.item()` is a requirement of this proposal; changing it to something else would still help authors, but would fail to satisfy the DOM needs.
114 |
115 | #### Convertable Interfaces
116 |
117 | Assuming this proposal is adopted, the following legacy interfaces should be upgradable into ObservableArray:
118 |
119 | * [NodeList](https://dom.spec.whatwg.org/#nodelist)
120 | * Possibly [DOMTokenList](https://dom.spec.whatwg.org/#domtokenlist) as a subclass
121 | * [CSSRuleList](https://drafts.csswg.org/cssom/#cssrulelist)
122 | * [StyleSheetList](https://drafts.csswg.org/cssom/#stylesheetlist)
123 | * Possibly [CSSStyleDeclaration](https://drafts.csswg.org/cssom/#cssstyledeclaration) and [MediaList](https://drafts.csswg.org/cssom/#medialist), as subclasses
124 | * [FileList](https://w3c.github.io/FileAPI/#dfn-filelist)
125 |
126 | (maybe others, list is ongoing)
127 |
128 | ### Possible Issues
129 |
130 | The obvious looming issue with this, as with any addition to the built-ins, is the possibility that the name `.item()` is already added to these classes' prototypes by a framework with an incompatible definition, and added using one of the fragile patterns that avoids clobbering built-in names, so that code depending on the framework's definition will then break when it's instead given the new built-in definition.
131 |
132 | I'm prepared to eat my words, but I suspect that any library adding a `.item()` method to Array or the other indexables is going to be giving it compatible or identical semantics to what's outlined here; I can't imagine what else such a method name could possibly correspond to.
133 |
134 | There's good evidence that we're probably safe here, tho: none of MooTools, Prototype, or Ext add `.item()` to Array; those are generally the most dangerous libraries for this kind of addition (see: [smooshgate](https://developers.google.com/web/updates/2018/03/smooshgate)), so if we're safe there it's much more likely we'll be safe in general.
135 |
136 | ### Possible DOM Compat Issues
137 |
138 | The .item() method defined by all of the interfaces [listed above](#convertable-interfaces) has a common structure:
139 |
140 | ```webidl
141 | SomeType? item(unsigned long index);
142 | ```
143 |
144 | If you follow the definition chain in WebIDL, you end up with a conversion algorithm for `unsigned long` into internal numbers that *mostly* matches what JS does for indexes in `.slice()`, with a few differences around the edges:
145 |
146 | * WebIDL treats Infinity and -Infinity as 0, while JS leaves them as is.
147 | * WebIDL modulos the value by 2^32 (using JS's "modulo" math op, so negatives become positive), while JS does not.
148 | * If the index is out of range, WebIDL returns `null`, while JS returns `undefined`.
149 |
150 | The first means that code that is accidentally passing infinities into `.item()` and relying on it returning the item at index 0 will break, as it will now get `undefined`. I find this very unlikely to be problematic.
151 |
152 | The second means that code passing extremely large numbers that are *just a little bit* larger than 2^32 (so the modulo brings them back into a reasonable index) and relying on that to return something from the list will break, as it will now get `undefined`. I also find this very unlikely to be problematic.
153 |
154 | The second also means that code relying on small negative numbers being modulo'd into the vicinity of 4 billion, and thus returning `null`, will break, as it will now return items from near the end of the list. I find this slightly likely, and believe we will need to do some instrumentation/testing to ensure it happens below our threshold for breakage. (There is a small chance that such code is *expecting* to recieve a value from the end of the list and is currently broken, and will be fixed by this change.)
155 |
156 | The third means that code which is testing for the presence of an item by *explicitly* comparing the return value with `null` will break, as it will now receive `undefined` and think a value was returned. I also find this slightly likely. In my experience, however, most such code is written either as `== null` or simply uses the truthiness of the return value (since a valid index will always return an object, which is truthy); both of these kinds of tests will continue to work after the change.
157 |
158 | ---
159 |
160 | We could potentially preview any of these changes before attempting to accept this proposal fully, so we know whether it's realistic to do such an upgrade, and thus whether the `.item()` name it a hard requirement or can be freely bikeshedded.
161 |
162 | In particular, testing negative indexes would be fairly simple, just requiring a change to `signed long` and an extra line in the algorithms of the methods.
163 |
164 | Testing returning `undefined` is also plausible; tho still slightly awkward to *express* in WebIDL (requiring the return type to be written as `any`), it's a tiny change to the algorithms of the methods.
165 |
--------------------------------------------------------------------------------
/docs/ecmarkup.css:
--------------------------------------------------------------------------------
1 | body {
2 | display: flex;
3 | font-size: 18px;
4 | line-height: 1.5;
5 | font-family: Cambria, Palatino Linotype, Palatino, Liberation Serif, serif;
6 | padding: 0;
7 | margin: 0;
8 | color: #111;
9 | }
10 |
11 | #spec-container {
12 | padding: 0 20px;
13 | flex-grow: 1;
14 | flex-basis: 66%;
15 | box-sizing: border-box;
16 | overflow: hidden;
17 | }
18 |
19 | body.oldtoc {
20 | margin: 0 auto;
21 | }
22 |
23 | a {
24 | text-decoration: none;
25 | color: #206ca7;
26 | }
27 |
28 | a:visited {
29 | color: #206ca7;
30 | }
31 |
32 | a:hover {
33 | text-decoration: underline;
34 | color: #239dee;
35 | }
36 |
37 |
38 | code {
39 | font-weight: bold;
40 | font-family: Consolas, Monaco, monospace;
41 | white-space: pre;
42 | }
43 |
44 | pre code {
45 | font-weight: inherit;
46 | }
47 |
48 | pre code.hljs {
49 | background-color: #fff;
50 | margin: 0;
51 | padding: 0;
52 | }
53 |
54 | ol.toc {
55 | list-style: none;
56 | padding-left: 0;
57 | }
58 |
59 | ol.toc ol.toc {
60 | padding-left: 2ex;
61 | list-style: none;
62 | }
63 |
64 | var {
65 | color: #2aa198;
66 | transition: background-color 0.25s ease;
67 | cursor: pointer;
68 | }
69 |
70 | var.referenced {
71 | background-color: #ffff33;
72 | }
73 |
74 | emu-const {
75 | font-family: sans-serif;
76 | }
77 |
78 | emu-val {
79 | font-weight: bold;
80 | }
81 |
82 | /* depth 1 */
83 | emu-alg ol,
84 | /* depth 4 */
85 | emu-alg ol ol ol ol,
86 | emu-alg ol.nested-thrice,
87 | emu-alg ol.nested-twice ol,
88 | emu-alg ol.nested-once ol ol {
89 | list-style-type: decimal;
90 | }
91 |
92 | /* depth 2 */
93 | emu-alg ol ol,
94 | emu-alg ol.nested-once,
95 | /* depth 5 */
96 | emu-alg ol ol ol ol ol,
97 | emu-alg ol.nested-four-times,
98 | emu-alg ol.nested-thrice ol,
99 | emu-alg ol.nested-twice ol ol,
100 | emu-alg ol.nested-once ol ol ol {
101 | list-style-type: lower-alpha;
102 | }
103 |
104 | /* depth 3 */
105 | emu-alg ol ol ol,
106 | emu-alg ol.nested-twice,
107 | emu-alg ol.nested-once ol,
108 | /* depth 6 */
109 | emu-alg ol ol ol ol ol ol,
110 | emu-alg ol.nested-lots,
111 | emu-alg ol.nested-four-times ol,
112 | emu-alg ol.nested-thrice ol ol,
113 | emu-alg ol.nested-twice ol ol ol,
114 | emu-alg ol.nested-once ol ol ol ol,
115 | /* depth 7+ */
116 | emu-alg ol.nested-lots ol {
117 | list-style-type: lower-roman;
118 | }
119 |
120 | emu-eqn {
121 | display: block;
122 | margin-left: 4em;
123 | }
124 |
125 | emu-eqn.inline {
126 | display: inline;
127 | margin: 0;
128 | }
129 |
130 | emu-eqn div:first-child {
131 | margin-left: -2em;
132 | }
133 |
134 | emu-note {
135 | margin: 1em 0;
136 | color: #666;
137 | border-left: 5px solid #ccc;
138 | display: flex;
139 | flex-direction: row;
140 | }
141 |
142 | emu-note > span.note {
143 | flex-basis: 100px;
144 | min-width: 100px;
145 | flex-grow: 0;
146 | flex-shrink: 1;
147 | text-transform: uppercase;
148 | padding-left: 5px;
149 | }
150 |
151 | emu-note[type=editor] {
152 | border-left-color: #faa;
153 | }
154 |
155 | emu-note > div.note-contents {
156 | flex-grow: 1;
157 | flex-shrink: 1;
158 | }
159 |
160 | emu-note > div.note-contents > p:first-of-type {
161 | margin-top: 0;
162 | }
163 |
164 | emu-note > div.note-contents > p:last-of-type {
165 | margin-bottom: 0;
166 | }
167 |
168 | emu-table td code {
169 | white-space: normal;
170 | }
171 |
172 | emu-figure {
173 | display: block;
174 | }
175 |
176 | emu-example {
177 | display: block;
178 | margin: 1em 3em;
179 | }
180 |
181 | emu-example figure figcaption {
182 | margin-top: 0.5em;
183 | text-align: left;
184 | }
185 |
186 | emu-figure figure,
187 | emu-example figure,
188 | emu-table figure {
189 | display: flex;
190 | flex-direction: column;
191 | align-items: center;
192 | }
193 |
194 | emu-production {
195 | display: block;
196 | }
197 |
198 | emu-grammar[type="example"] emu-production,
199 | emu-grammar[type="definition"] emu-production {
200 | margin-top: 1em;
201 | margin-bottom: 1em;
202 | margin-left: 5ex;
203 | }
204 |
205 | emu-grammar.inline, emu-production.inline,
206 | emu-grammar.inline emu-production emu-rhs, emu-production.inline emu-rhs,
207 | emu-grammar[collapsed] emu-production emu-rhs, emu-production[collapsed] emu-rhs {
208 | display: inline;
209 | padding-left: 1ex;
210 | margin-left: 0;
211 | }
212 |
213 | emu-grammar[collapsed] emu-production, emu-production[collapsed] {
214 | margin: 0;
215 | }
216 |
217 | emu-constraints {
218 | font-size: .75em;
219 | margin-right: 1ex;
220 | }
221 |
222 | emu-gann {
223 | margin-right: 1ex;
224 | }
225 |
226 | emu-gann emu-t:last-child,
227 | emu-gann emu-gprose:last-child,
228 | emu-gann emu-nt:last-child {
229 | margin-right: 0;
230 | }
231 |
232 | emu-geq {
233 | margin-left: 1ex;
234 | font-weight: bold;
235 | }
236 |
237 | emu-oneof {
238 | font-weight: bold;
239 | margin-left: 1ex;
240 | }
241 |
242 | emu-nt {
243 | display: inline-block;
244 | font-style: italic;
245 | white-space: nowrap;
246 | text-indent: 0;
247 | }
248 |
249 | emu-nt a, emu-nt a:visited {
250 | color: #333;
251 | }
252 |
253 | emu-rhs emu-nt {
254 | margin-right: 1ex;
255 | }
256 |
257 | emu-t {
258 | display: inline-block;
259 | font-family: monospace;
260 | font-weight: bold;
261 | white-space: nowrap;
262 | text-indent: 0;
263 | }
264 |
265 | emu-production emu-t {
266 | margin-right: 1ex;
267 | }
268 |
269 | emu-rhs {
270 | display: block;
271 | padding-left: 75px;
272 | text-indent: -25px;
273 | }
274 |
275 | emu-mods {
276 | font-size: .85em;
277 | vertical-align: sub;
278 | font-style: normal;
279 | font-weight: normal;
280 | }
281 |
282 | emu-params, emu-opt {
283 | margin-right: 1ex;
284 | font-family: monospace;
285 | }
286 |
287 | emu-params, emu-constraints {
288 | color: #2aa198;
289 | }
290 |
291 | emu-opt {
292 | color: #b58900;
293 | }
294 |
295 | emu-gprose {
296 | font-size: 0.9em;
297 | font-family: Helvetica, Arial, sans-serif;
298 | }
299 |
300 | emu-production emu-gprose {
301 | margin-right: 1ex;
302 | }
303 |
304 | h1.shortname {
305 | color: #f60;
306 | font-size: 1.5em;
307 | margin: 0;
308 | }
309 |
310 | h1.version {
311 | color: #f60;
312 | font-size: 1.5em;
313 | margin: 0;
314 | }
315 |
316 | h1.title {
317 | margin-top: 0;
318 | color: #f60;
319 | }
320 |
321 | h1.first {
322 | margin-top: 0;
323 | }
324 |
325 | h1, h2, h3, h4, h5, h6 {
326 | position: relative;
327 | }
328 |
329 | h1 .secnum {
330 | text-decoration: none;
331 | margin-right: 5px;
332 | }
333 |
334 | h1 span.title {
335 | order: 2;
336 | }
337 |
338 |
339 | h1 { font-size: 2.67em; margin-top: 2em; margin-bottom: 0; line-height: 1em;}
340 | h2 { font-size: 2em; }
341 | h3 { font-size: 1.56em; }
342 | h4 { font-size: 1.25em; }
343 | h5 { font-size: 1.11em; }
344 | h6 { font-size: 1em; }
345 |
346 | h1:hover span.utils {
347 | display: block;
348 | }
349 |
350 | span.utils {
351 | font-size: 18px;
352 | line-height: 18px;
353 | display: none;
354 | position: absolute;
355 | top: 100%;
356 | left: 0;
357 | right: 0;
358 | font-weight: normal;
359 | }
360 |
361 | span.utils:before {
362 | content: "⤷";
363 | display: inline-block;
364 | padding: 0 5px;
365 | }
366 |
367 | span.utils > * {
368 | display: inline-block;
369 | margin-right: 20px;
370 | }
371 |
372 | h1 span.utils span.anchor a,
373 | h2 span.utils span.anchor a,
374 | h3 span.utils span.anchor a,
375 | h4 span.utils span.anchor a,
376 | h5 span.utils span.anchor a,
377 | h6 span.utils span.anchor a {
378 | text-decoration: none;
379 | font-variant: small-caps;
380 | }
381 |
382 | h1 span.utils span.anchor a:hover,
383 | h2 span.utils span.anchor a:hover,
384 | h3 span.utils span.anchor a:hover,
385 | h4 span.utils span.anchor a:hover,
386 | h5 span.utils span.anchor a:hover,
387 | h6 span.utils span.anchor a:hover {
388 | color: #333;
389 | }
390 |
391 | emu-intro h1, emu-clause h1, emu-annex h1 { font-size: 2em; }
392 | emu-intro h2, emu-clause h2, emu-annex h2 { font-size: 1.56em; }
393 | emu-intro h3, emu-clause h3, emu-annex h3 { font-size: 1.25em; }
394 | emu-intro h4, emu-clause h4, emu-annex h4 { font-size: 1.11em; }
395 | emu-intro h5, emu-clause h5, emu-annex h5 { font-size: 1em; }
396 | emu-intro h6, emu-clause h6, emu-annex h6 { font-size: 0.9em; }
397 | emu-intro emu-intro h1, emu-clause emu-clause h1, emu-annex emu-annex h1 { font-size: 1.56em; }
398 | emu-intro emu-intro h2, emu-clause emu-clause h2, emu-annex emu-annex h2 { font-size: 1.25em; }
399 | emu-intro emu-intro h3, emu-clause emu-clause h3, emu-annex emu-annex h3 { font-size: 1.11em; }
400 | emu-intro emu-intro h4, emu-clause emu-clause h4, emu-annex emu-annex h4 { font-size: 1em; }
401 | emu-intro emu-intro h5, emu-clause emu-clause h5, emu-annex emu-annex h5 { font-size: 0.9em; }
402 | emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex h1 { font-size: 1.25em; }
403 | emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex h2 { font-size: 1.11em; }
404 | emu-intro emu-intro emu-intro h3, emu-clause emu-clause emu-clause h3, emu-annex emu-annex emu-annex h3 { font-size: 1em; }
405 | emu-intro emu-intro emu-intro h4, emu-clause emu-clause emu-clause h4, emu-annex emu-annex emu-annex h4 { font-size: 0.9em; }
406 | emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex h1 { font-size: 1.11em; }
407 | emu-intro emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex emu-annex h2 { font-size: 1em; }
408 | emu-intro emu-intro emu-intro emu-intro h3, emu-clause emu-clause emu-clause emu-clause h3, emu-annex emu-annex emu-annex emu-annex h3 { font-size: 0.9em; }
409 | emu-intro emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex emu-annex h1 { font-size: 1em; }
410 | emu-intro emu-intro emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex emu-annex emu-annex h2 { font-size: 0.9em; }
411 | emu-intro emu-intro emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex emu-annex emu-annex h1 { font-size: 0.9em }
412 |
413 | emu-clause, emu-intro, emu-annex {
414 | display: block;
415 | }
416 |
417 | /* Figures and tables */
418 | figure { display: block; margin: 1em 0 3em 0; }
419 | figure object { display: block; margin: 0 auto; }
420 | figure table.real-table { margin: 0 auto; }
421 | figure figcaption {
422 | display: block;
423 | color: #555555;
424 | font-weight: bold;
425 | text-align: center;
426 | }
427 |
428 | emu-table table {
429 | margin: 0 auto;
430 | }
431 |
432 | emu-table table, table.real-table {
433 | border-collapse: collapse;
434 | }
435 |
436 | emu-table td, emu-table th, table.real-table td, table.real-table th {
437 | border: 1px solid black;
438 | padding: 0.4em;
439 | vertical-align: baseline;
440 | }
441 | emu-table th, emu-table thead td, table.real-table th {
442 | background-color: #eeeeee;
443 | }
444 |
445 | /* Note: the left content edges of table.lightweight-table >tbody >tr >td
446 | and div.display line up. */
447 | table.lightweight-table {
448 | border-collapse: collapse;
449 | margin: 0 0 0 1.5em;
450 | }
451 | table.lightweight-table td, table.lightweight-table th {
452 | border: none;
453 | padding: 0 0.5em;
454 | vertical-align: baseline;
455 | }
456 |
457 | /* diff styles */
458 | ins {
459 | background-color: #e0f8e0;
460 | text-decoration: none;
461 | border-bottom: 1px solid #396;
462 | }
463 |
464 | del {
465 | background-color: #fee;
466 | }
467 |
468 | ins.block, del.block,
469 | emu-production > ins, emu-production > del,
470 | emu-grammar > ins, emu-grammar > del {
471 | display: block;
472 | }
473 | emu-rhs > ins, emu-rhs > del {
474 | display: inline;
475 | }
476 |
477 | tr.ins > td > ins {
478 | border-bottom: none;
479 | }
480 |
481 | tr.ins > td {
482 | background-color: #e0f8e0;
483 | }
484 |
485 | tr.del > td {
486 | background-color: #fee;
487 | }
488 |
489 | /* Menu Styles */
490 | #menu-toggle {
491 | font-size: 2em;
492 |
493 | position: fixed;
494 | top: 0;
495 | left: 0;
496 | width: 1.5em;
497 | height: 1.5em;
498 | z-index: 3;
499 | visibility: hidden;
500 | color: #1567a2;
501 | background-color: #fff;
502 |
503 | line-height: 1.5em;
504 | text-align: center;
505 | -webkit-touch-callout: none;
506 | -webkit-user-select: none;
507 | -khtml-user-select: none;
508 | -moz-user-select: none;
509 | -ms-user-select: none;
510 | user-select: none;;
511 |
512 | cursor: pointer;
513 | }
514 |
515 | #menu {
516 | display: flex;
517 | flex-direction: column;
518 | width: 33%; height: 100vh;
519 | max-width: 500px;
520 | box-sizing: border-box;
521 | background-color: #ddd;
522 | overflow: hidden;
523 | transition: opacity 0.1s linear;
524 | padding: 0 5px;
525 | position: fixed;
526 | left: 0; top: 0;
527 | border-right: 2px solid #bbb;
528 |
529 | z-index: 2;
530 | }
531 |
532 | #menu-spacer {
533 | flex-basis: 33%;
534 | max-width: 500px;
535 | flex-grow: 0;
536 | flex-shrink: 0;
537 | }
538 |
539 | #menu a {
540 | color: #1567a2;
541 | }
542 |
543 | #menu.active {
544 | display: flex;
545 | opacity: 1;
546 | z-index: 2;
547 | }
548 |
549 | #menu-pins {
550 | flex-grow: 1;
551 | display: none;
552 | }
553 |
554 | #menu-pins.active {
555 | display: block;
556 | }
557 |
558 | #menu-pins-list {
559 | margin: 0;
560 | padding: 0;
561 | counter-reset: pins-counter;
562 | }
563 |
564 | #menu-pins-list > li:before {
565 | content: counter(pins-counter);
566 | counter-increment: pins-counter;
567 | display: inline-block;
568 | width: 25px;
569 | text-align: center;
570 | border: 1px solid #bbb;
571 | padding: 2px;
572 | margin: 4px;
573 | box-sizing: border-box;
574 | line-height: 1em;
575 | background-color: #ccc;
576 | border-radius: 4px;
577 | }
578 | #menu-toc > ol {
579 | padding: 0;
580 | flex-grow: 1;
581 | }
582 |
583 | #menu-toc > ol li {
584 | padding: 0;
585 | }
586 |
587 | #menu-toc > ol , #menu-toc > ol ol {
588 | list-style-type: none;
589 | margin: 0;
590 | padding: 0;
591 | }
592 |
593 | #menu-toc > ol ol {
594 | padding-left: 0.75em;
595 | }
596 |
597 | #menu-toc li {
598 | text-overflow: ellipsis;
599 | overflow: hidden;
600 | white-space: nowrap;
601 | }
602 |
603 | #menu-toc .item-toggle {
604 | display: inline-block;
605 | transform: rotate(-45deg) translate(-5px, -5px);
606 | transition: transform 0.1s ease;
607 | text-align: center;
608 | width: 20px;
609 |
610 | color: #aab;
611 |
612 | -webkit-touch-callout: none;
613 | -webkit-user-select: none;
614 | -khtml-user-select: none;
615 | -moz-user-select: none;
616 | -ms-user-select: none;
617 | user-select: none;;
618 |
619 | cursor: pointer;
620 | }
621 |
622 | #menu-toc .item-toggle-none {
623 | display: inline-block;
624 | width: 20px;
625 | }
626 |
627 | #menu-toc li.active > .item-toggle {
628 | transform: rotate(45deg) translate(-5px, -5px);
629 | }
630 |
631 | #menu-toc li > ol {
632 | display: none;
633 | }
634 |
635 | #menu-toc li.active > ol {
636 | display: block;
637 | }
638 |
639 | #menu-toc li.revealed > a {
640 | background-color: #bbb;
641 | font-weight: bold;
642 | /*
643 | background-color: #222;
644 | color: #c6d8e4;
645 | */
646 | }
647 |
648 | #menu-toc li.revealed-leaf> a {
649 | color: #206ca7;
650 | /*
651 | background-color: #222;
652 | color: #c6d8e4;
653 | */
654 | }
655 |
656 | #menu-toc li.revealed > .item-toggle {
657 | transform: rotate(45deg) translate(-5px, -5px);
658 | }
659 |
660 | #menu-toc li.revealed > ol {
661 | display: block;
662 | }
663 |
664 | #menu-toc li > a {
665 | padding: 2px 5px;
666 | }
667 |
668 | #menu > * {
669 | margin-bottom: 5px;
670 | }
671 |
672 | .menu-pane-header {
673 | padding: 0 5px;
674 | text-transform: uppercase;
675 | background-color: #aaa;
676 | color: #335;
677 | font-weight: bold;
678 | letter-spacing: 2px;
679 | flex-grow: 0;
680 | flex-shrink: 0;
681 | font-size: 0.8em;
682 | }
683 |
684 | #menu-toc {
685 | display: flex;
686 | flex-direction: column;
687 | width: 100%;
688 | overflow: hidden;
689 | flex-grow: 1;
690 | }
691 |
692 | #menu-toc ol.toc {
693 | overflow-x: hidden;
694 | overflow-y: auto;
695 | }
696 |
697 | #menu-search {
698 | position: relative;
699 | flex-grow: 0;
700 | flex-shrink: 0;
701 | width: 100%;
702 |
703 | display: flex;
704 | flex-direction: column;
705 |
706 | max-height: 300px;
707 | }
708 |
709 | #menu-trace-list {
710 | display: none;
711 | }
712 |
713 | #menu-search-box {
714 | box-sizing: border-box;
715 | display: block;
716 | width: 100%;
717 | margin: 5px 0 0 0;
718 | font-size: 1em;
719 | padding: 2px;
720 | background-color: #bbb;
721 | border: 1px solid #999;
722 | }
723 |
724 | #menu-search-results {
725 | overflow-x: hidden;
726 | overflow-y: auto;
727 | }
728 |
729 | li.menu-search-result-clause:before {
730 | content: 'clause';
731 | width: 40px;
732 | display: inline-block;
733 | text-align: right;
734 | padding-right: 1ex;
735 | color: #666;
736 | font-size: 75%;
737 | }
738 | li.menu-search-result-op:before {
739 | content: 'op';
740 | width: 40px;
741 | display: inline-block;
742 | text-align: right;
743 | padding-right: 1ex;
744 | color: #666;
745 | font-size: 75%;
746 | }
747 |
748 | li.menu-search-result-prod:before {
749 | content: 'prod';
750 | width: 40px;
751 | display: inline-block;
752 | text-align: right;
753 | padding-right: 1ex;
754 | color: #666;
755 | font-size: 75%
756 | }
757 |
758 |
759 | li.menu-search-result-term:before {
760 | content: 'term';
761 | width: 40px;
762 | display: inline-block;
763 | text-align: right;
764 | padding-right: 1ex;
765 | color: #666;
766 | font-size: 75%
767 | }
768 |
769 | #menu-search-results ul {
770 | padding: 0 5px;
771 | margin: 0;
772 | }
773 |
774 | #menu-search-results li {
775 | white-space: nowrap;
776 | text-overflow: ellipsis;
777 | }
778 |
779 |
780 | #menu-trace-list {
781 | counter-reset: item;
782 | margin: 0 0 0 20px;
783 | padding: 0;
784 | }
785 | #menu-trace-list li {
786 | display: block;
787 | white-space: nowrap;
788 | }
789 |
790 | #menu-trace-list li .secnum:after {
791 | content: " ";
792 | }
793 | #menu-trace-list li:before {
794 | content: counter(item) " ";
795 | background-color: #222;
796 | counter-increment: item;
797 | color: #999;
798 | width: 20px;
799 | height: 20px;
800 | line-height: 20px;
801 | display: inline-block;
802 | text-align: center;
803 | margin: 2px 4px 2px 0;
804 | }
805 |
806 | @media (max-width: 1000px) {
807 | body {
808 | margin: 0;
809 | display: block;
810 | }
811 |
812 | #menu {
813 | display: none;
814 | padding-top: 3em;
815 | width: 450px;
816 | }
817 |
818 | #menu.active {
819 | position: fixed;
820 | height: 100%;
821 | left: 0;
822 | top: 0;
823 | right: 300px;
824 | }
825 |
826 | #menu-toggle {
827 | visibility: visible;
828 | }
829 |
830 | #spec-container {
831 | padding: 0 5px;
832 | }
833 |
834 | #references-pane-spacer {
835 | display: none;
836 | }
837 | }
838 |
839 | @media only screen and (max-width: 800px) {
840 | #menu {
841 | width: 100%;
842 | }
843 |
844 | h1 .secnum:empty {
845 | margin: 0; padding: 0;
846 | }
847 | }
848 |
849 |
850 | /* Toolbox */
851 | .toolbox {
852 | position: absolute;
853 | background: #ddd;
854 | border: 1px solid #aaa;
855 | display: none;
856 | color: #eee;
857 | padding: 5px;
858 | border-radius: 3px;
859 | }
860 |
861 | .toolbox.active {
862 | display: inline-block;
863 | }
864 |
865 | .toolbox a {
866 | text-decoration: none;
867 | padding: 0 5px;
868 | }
869 |
870 | .toolbox a:hover {
871 | text-decoration: underline;
872 | }
873 |
874 | .toolbox:after, .toolbox:before {
875 | top: 100%;
876 | left: 15px;
877 | border: solid transparent;
878 | content: " ";
879 | height: 0;
880 | width: 0;
881 | position: absolute;
882 | pointer-events: none;
883 | }
884 |
885 | .toolbox:after {
886 | border-color: rgba(0, 0, 0, 0);
887 | border-top-color: #ddd;
888 | border-width: 10px;
889 | margin-left: -10px;
890 | }
891 | .toolbox:before {
892 | border-color: rgba(204, 204, 204, 0);
893 | border-top-color: #aaa;
894 | border-width: 12px;
895 | margin-left: -12px;
896 | }
897 |
898 | #references-pane-container {
899 | position: fixed;
900 | bottom: 0;
901 | left: 0;
902 | right: 0;
903 | height: 250px;
904 | display: none;
905 | background-color: #ddd;
906 | z-index: 1;
907 | }
908 |
909 | #references-pane-table-container {
910 | overflow-x: hidden;
911 | overflow-y: auto;
912 | }
913 |
914 | #references-pane-spacer {
915 | flex-basis: 33%;
916 | max-width: 500px;
917 | }
918 |
919 | #references-pane {
920 | flex-grow: 1;
921 | overflow: hidden;
922 | display: flex;
923 | flex-direction: column;
924 | }
925 |
926 | #references-pane-container.active {
927 | display: flex;
928 | }
929 |
930 | #references-pane-close:after {
931 | content: '✖';
932 | float: right;
933 | cursor: pointer;
934 | }
935 |
936 | #references-pane table tr td:first-child {
937 | text-align: right;
938 | padding-right: 5px;
939 | }
940 |
941 | @media print {
942 | #menu-toggle {
943 | display: none;
944 | }
945 | }
946 |
--------------------------------------------------------------------------------
/docs/ecmarkup.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function Search(menu) {
4 | this.menu = menu;
5 | this.$search = document.getElementById('menu-search');
6 | this.$searchBox = document.getElementById('menu-search-box');
7 | this.$searchResults = document.getElementById('menu-search-results');
8 |
9 | this.loadBiblio();
10 |
11 | document.addEventListener('keydown', this.documentKeydown.bind(this));
12 |
13 | this.$searchBox.addEventListener('keydown', debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true }));
14 | this.$searchBox.addEventListener('keyup', debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true }));
15 |
16 | // Perform an initial search if the box is not empty.
17 | if (this.$searchBox.value) {
18 | this.search(this.$searchBox.value);
19 | }
20 | }
21 |
22 | Search.prototype.loadBiblio = function () {
23 | var $biblio = document.getElementById('menu-search-biblio');
24 | if (!$biblio) {
25 | this.biblio = [];
26 | } else {
27 | this.biblio = JSON.parse($biblio.textContent);
28 | this.biblio.clauses = this.biblio.filter(function (e) { return e.type === 'clause' });
29 | this.biblio.byId = this.biblio.reduce(function (map, entry) {
30 | map[entry.id] = entry;
31 | return map;
32 | }, {});
33 | }
34 | }
35 |
36 | Search.prototype.documentKeydown = function (e) {
37 | if (e.keyCode === 191) {
38 | e.preventDefault();
39 | e.stopPropagation();
40 | this.triggerSearch();
41 | }
42 | }
43 |
44 | Search.prototype.searchBoxKeydown = function (e) {
45 | e.stopPropagation();
46 | e.preventDefault();
47 | if (e.keyCode === 191 && e.target.value.length === 0) {
48 | e.preventDefault();
49 | } else if (e.keyCode === 13) {
50 | e.preventDefault();
51 | this.selectResult();
52 | }
53 | }
54 |
55 | Search.prototype.searchBoxKeyup = function (e) {
56 | if (e.keyCode === 13 || e.keyCode === 9) {
57 | return;
58 | }
59 |
60 | this.search(e.target.value);
61 | }
62 |
63 |
64 | Search.prototype.triggerSearch = function (e) {
65 | if (this.menu.isVisible()) {
66 | this._closeAfterSearch = false;
67 | } else {
68 | this._closeAfterSearch = true;
69 | this.menu.show();
70 | }
71 |
72 | this.$searchBox.focus();
73 | this.$searchBox.select();
74 | }
75 | // bit 12 - Set if the result starts with searchString
76 | // bits 8-11: 8 - number of chunks multiplied by 2 if cases match, otherwise 1.
77 | // bits 1-7: 127 - length of the entry
78 | // General scheme: prefer case sensitive matches with fewer chunks, and otherwise
79 | // prefer shorter matches.
80 | function relevance(result, searchString) {
81 | var relevance = 0;
82 |
83 | relevance = Math.max(0, 8 - result.match.chunks) << 7;
84 |
85 | if (result.match.caseMatch) {
86 | relevance *= 2;
87 | }
88 |
89 | if (result.match.prefix) {
90 | relevance += 2048
91 | }
92 |
93 | relevance += Math.max(0, 255 - result.entry.key.length);
94 |
95 | return relevance;
96 | }
97 |
98 | Search.prototype.search = function (searchString) {
99 | if (searchString === '') {
100 | this.displayResults([]);
101 | this.hideSearch();
102 | return;
103 | } else {
104 | this.showSearch();
105 | }
106 |
107 | if (searchString.length === 1) {
108 | this.displayResults([]);
109 | return;
110 | }
111 |
112 | var results;
113 |
114 | if (/^[\d\.]*$/.test(searchString)) {
115 | results = this.biblio.clauses.filter(function (clause) {
116 | return clause.number.substring(0, searchString.length) === searchString;
117 | }).map(function (clause) {
118 | return { entry: clause };
119 | });
120 | } else {
121 | results = [];
122 |
123 | for (var i = 0; i < this.biblio.length; i++) {
124 | var entry = this.biblio[i];
125 | if (!entry.key) {
126 | // biblio entries without a key aren't searchable
127 | continue;
128 | }
129 |
130 | var match = fuzzysearch(searchString, entry.key);
131 | if (match) {
132 | results.push({ entry: entry, match: match });
133 | }
134 | }
135 |
136 | results.forEach(function (result) {
137 | result.relevance = relevance(result, searchString);
138 | });
139 |
140 | results = results.sort(function (a, b) { return b.relevance - a.relevance });
141 |
142 | }
143 |
144 | if (results.length > 50) {
145 | results = results.slice(0, 50);
146 | }
147 |
148 | this.displayResults(results);
149 | }
150 | Search.prototype.hideSearch = function () {
151 | this.$search.classList.remove('active');
152 | }
153 |
154 | Search.prototype.showSearch = function () {
155 | this.$search.classList.add('active');
156 | }
157 |
158 | Search.prototype.selectResult = function () {
159 | var $first = this.$searchResults.querySelector('li:first-child a');
160 |
161 | if ($first) {
162 | document.location = $first.getAttribute('href');
163 | }
164 |
165 | this.$searchBox.value = '';
166 | this.$searchBox.blur();
167 | this.displayResults([]);
168 | this.hideSearch();
169 |
170 | if (this._closeAfterSearch) {
171 | this.menu.hide();
172 | }
173 | }
174 |
175 | Search.prototype.displayResults = function (results) {
176 | if (results.length > 0) {
177 | this.$searchResults.classList.remove('no-results');
178 |
179 | var html = '
';
180 |
181 | results.forEach(function (result) {
182 | var entry = result.entry;
183 | var id = entry.id;
184 | var cssClass = '';
185 | var text = '';
186 |
187 | if (entry.type === 'clause') {
188 | var number = entry.number ? entry.number + ' ' : '';
189 | text = number + entry.key;
190 | cssClass = 'clause';
191 | id = entry.id;
192 | } else if (entry.type === 'production') {
193 | text = entry.key;
194 | cssClass = 'prod';
195 | id = entry.id;
196 | } else if (entry.type === 'op') {
197 | text = entry.key;
198 | cssClass = 'op';
199 | id = entry.id || entry.refId;
200 | } else if (entry.type === 'term') {
201 | text = entry.key;
202 | cssClass = 'term';
203 | id = entry.id || entry.refId;
204 | }
205 |
206 | if (text) {
207 | html += '
We provide a new method, at(), that is on the prototype of the built-in indexable objects: Array, String, and TypedArrays objects. The method supports relative indexing from the end when passed a negative index.
1852 |
1853 |
1854 |
1855 |
1 Additions to Properties of the Array Prototype Object