diff --git a/index.html b/index.html index 902e7921..4a94408a 100644 --- a/index.html +++ b/index.html @@ -452,25 +452,68 @@ }; function findActiveClause(root, path) { - let clauses = getChildClauses(root); path = path || []; - for (let $clause of clauses) { - let rect = $clause.getBoundingClientRect(); + let visibleClauses = getVisibleClauses(root, path); + let midpoint = Math.floor(window.innerHeight / 2); + + for (let [$clause, path] of visibleClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); + let isFullyVisibleAboveTheFold = + clauseTop > 0 && clauseTop < midpoint && clauseBottom < window.innerHeight; + if (isFullyVisibleAboveTheFold) { + return path; + } + } + + visibleClauses.sort(([, pathA], [, pathB]) => pathB.length - pathA.length); + for (let [$clause, path] of visibleClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); let $header = $clause.querySelector('h1'); + let clauseStyles = getComputedStyle($clause); let marginTop = Math.max( - parseInt(getComputedStyle($clause)['margin-top']), + 0, + parseInt(clauseStyles['margin-top']), parseInt(getComputedStyle($header)['margin-top']) ); - - if (rect.top - marginTop <= 1 && rect.bottom > 0) { - return findActiveClause($clause, path.concat($clause)) || path; + let marginBottom = Math.max(0, parseInt(clauseStyles['margin-bottom'])); + let crossesMidpoint = + clauseTop - marginTop <= midpoint && clauseBottom + marginBottom >= midpoint; + if (crossesMidpoint) { + return path; } } return path; } +function getVisibleClauses(root, path) { + let childClauses = getChildClauses(root); + path = path || []; + + let result = []; + + let seenVisibleClause = false; + for (let $clause of childClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); + let isPartiallyVisible = + (clauseTop > 0 && clauseTop < window.innerHeight) || + (clauseBottom > 0 && clauseBottom < window.innerHeight) || + (clauseTop < 0 && clauseBottom > window.innerHeight); + + if (isPartiallyVisible) { + seenVisibleClause = true; + let innerPath = path.concat($clause); + result.push([$clause, innerPath]); + result.push(...getVisibleClauses($clause, innerPath)); + } else if (seenVisibleClause) { + break; + } + } + + return result; +} + function* getChildClauses(root) { for (let el of root.children) { switch (el.nodeName) { @@ -1162,12 +1205,40 @@ return [...menu.$menu.querySelectorAll('.active')].map(getTocPath).filter(p => p != null); } -function loadStateFromSessionStorage() { - if (!window.sessionStorage || typeof menu === 'undefined' || window.navigating) { +function initTOCExpansion(visibleItemLimit) { + // Initialize to a reasonable amount of TOC expansion: + // * Expand any full-breadth nesting level up to visibleItemLimit. + // * Expand any *single-item* level while under visibleItemLimit (even if that pushes over it). + + // Limit to initialization by bailing out if any parent item is already expanded. + const tocItems = Array.from(document.querySelectorAll('#menu-toc li')); + if (tocItems.some(li => li.classList.contains('active') && li.querySelector('li'))) { + return; + } + + const selfAndSiblings = maybe => Array.from(maybe?.parentNode.children ?? []); + let currentLevelItems = selfAndSiblings(tocItems[0]); + let availableCount = visibleItemLimit - currentLevelItems.length; + while (availableCount > 0 && currentLevelItems.length) { + const nextLevelItems = currentLevelItems.flatMap(li => selfAndSiblings(li.querySelector('li'))); + availableCount -= nextLevelItems.length; + if (availableCount > 0 || currentLevelItems.length === 1) { + // Expand parent items of the next level down (i.e., current-level items with children). + for (const ol of new Set(nextLevelItems.map(li => li.parentNode))) { + ol.closest('li').classList.add('active'); + } + } + currentLevelItems = nextLevelItems; + } +} + +function initState() { + if (typeof menu === 'undefined' || window.navigating) { return; } - if (sessionStorage.referencePaneState != null) { - let state = JSON.parse(sessionStorage.referencePaneState); + const storage = typeof sessionStorage !== 'undefined' ? sessionStorage : Object.create(null); + if (storage.referencePaneState != null) { + let state = JSON.parse(storage.referencePaneState); if (state != null) { if (state.type === 'ref') { let entry = menu.search.biblio.byId[state.id]; @@ -1181,39 +1252,36 @@ referencePane.showSDOsBody(sdos, state.id); } } - delete sessionStorage.referencePaneState; + delete storage.referencePaneState; } } - if (sessionStorage.activeTocPaths != null) { - document - .getElementById('menu-toc') - .querySelectorAll('.active') - .forEach(e => { - e.classList.remove('active'); - }); - let active = JSON.parse(sessionStorage.activeTocPaths); + if (storage.activeTocPaths != null) { + document.querySelectorAll('#menu-toc li.active').forEach(li => li.classList.remove('active')); + let active = JSON.parse(storage.activeTocPaths); active.forEach(activateTocPath); - delete sessionStorage.activeTocPaths; + delete storage.activeTocPaths; + } else { + initTOCExpansion(20); } - if (sessionStorage.searchValue != null) { - let value = JSON.parse(sessionStorage.searchValue); + if (storage.searchValue != null) { + let value = JSON.parse(storage.searchValue); menu.search.$searchBox.value = value; menu.search.search(value); - delete sessionStorage.searchValue; + delete storage.searchValue; } - if (sessionStorage.tocScroll != null) { - let tocScroll = JSON.parse(sessionStorage.tocScroll); + if (storage.tocScroll != null) { + let tocScroll = JSON.parse(storage.tocScroll); menu.$toc.scrollTop = tocScroll; - delete sessionStorage.tocScroll; + delete storage.tocScroll; } } -document.addEventListener('DOMContentLoaded', loadStateFromSessionStorage); +document.addEventListener('DOMContentLoaded', initState); -window.addEventListener('pageshow', loadStateFromSessionStorage); +window.addEventListener('pageshow', initState); window.addEventListener('beforeunload', () => { if (!window.sessionStorage || typeof menu === 'undefined') { @@ -2633,8 +2701,8 @@

Time Zone Canonicalization proposal

This proposal aims to:

This specification consists of two parts:

@@ -2665,10 +2733,10 @@

1 Amendments to the ECMAScript® 2024 Language S

1.1 SystemTimeZoneIdentifier ( )

The implementation-defined abstract operation SystemTimeZoneIdentifier takes no arguments and returns a String. - It returns a String representing the host environment's current time zone, which is either a String representing a UTC offset for which IsTimeZoneOffsetString returns true, or a primary time zone identifier. + It returns a String representing the host environment's current time zone, which is either a String representing a UTC offset for which IsTimeZoneOffsetString returns true, or a primary time zone identifier. It performs the following steps when called:

-
  1. If the implementation only supports the UTC time zone, return "UTC".
  2. Let systemTimeZoneString be the String representing the host environment's current time zone, either a primary time zone identifier or an offset time zone identifier.
  3. If IsTimeZoneOffsetString(systemTimeZoneString) is true, return CanonicalizeTimeZoneOffsetString(systemTimeZoneString).
  4. Return systemTimeZoneString.
+
  1. If the implementation only supports the UTC time zone, return "UTC".
  2. Let systemTimeZoneString be the String representing the host environment's current time zone, either a primary time zone identifier or an offset time zone identifier.
  3. If IsTimeZoneOffsetString(systemTimeZoneString) is true, return CanonicalizeTimeZoneOffsetString(systemTimeZoneString).
  4. Return systemTimeZoneString.

[...]

@@ -2745,15 +2813,15 @@

2.1 Time Zone Identifiers

The IANA Time Zone Database is typically updated between five and ten times per year. These updates may add new Zone or Link names, may change Zones to Links, and may change the UTC offsets and transitions associated with any Zone. ECMAScript implementations are recommended to include updates to the IANA Time Zone Database as soon as possible. - Such prompt action ensures that ECMAScript programs can accurately perform time-zone-sensitive calculations and can use newly-added available named time zone identifiers supplied by external input or the host environment. + Such prompt action ensures that ECMAScript programs can accurately perform time-zone-sensitive calculations and can use newly-added available named time zone identifiers supplied by external input or the host environment.

Although the IANA Time Zone Database maintainers strive for stability, in rare cases identifiers will be renamed. For example, the IANA Time Zone Database's 2022b release added "Europe/Kyiv" as a new Zone and demoted "Europe/Kiev" to be a Link to the new Zone. - To reduce disruption from these renaming changes, ECMAScript implementations are encouraged to initially add the new name as a non-primary time zone identifier that resolves to the old primary identifier. - Then, after a waiting period, implementations are recommended to promote the new Zone to a primary time zone identifier while simultaneously demoting the deprecated name to non-primary. + To reduce disruption from these renaming changes, ECMAScript implementations are encouraged to initially add the new name as a non-primary time zone identifier that resolves to the old primary identifier. + Then, after a waiting period, implementations are recommended to promote the new Zone to a primary time zone identifier while simultaneously demoting the deprecated name to non-primary. The recommended waiting period is two years after the IANA Time Zone Database release containing the changes. This is long enough to allow most other systems that ECMAScript programs may interact with to "catch up" with the change so that the new zone is recognized. Note that this waiting period should only apply to cases where an existing Zone is replaced by a new Zone name. @@ -2762,7 +2830,7 @@

2.1 Time Zone Identifiers

- If implementations revise time zone information during the lifetime of an agent, then which identifiers are supported, the primary time zone identifier associated with any identifier, and the UTC offsets and transitions associated with any Zone, must be consistent with results previously observed by that agent. + If implementations revise time zone information during the lifetime of an agent, then which identifiers are supported, the primary time zone identifier associated with any identifier, and the UTC offsets and transitions associated with any Zone, must be consistent with results previously observed by that agent. Due to the complexity of supporting this requirement, it is recommended that implementations maintain a fully consistent copy of the IANA Time Zone Database for the lifetime of each agent.

@@ -2799,7 +2867,7 @@

Copyright Notice

© 2023 Justin Grant, Richard Gibson

Software License

-

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

+

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: