@@ -37774,14 +37774,82 @@ const FIXED_BAR_SPACE_CSS = /* css */ `
3777437774// they overlap: the room to give back is the largest of them, not their sum,
3777537775// and one leaving must leave the others' room in place.
3777637776const sizeMapByArea = new Map();
37777-
37777+ // What is currently on <html> for each area (absent = the variable is not set).
37778+ // Writing the value that is already there would invalidate layout for nothing,
37779+ // and several bars sharing an edge means most calls compute the same largest
37780+ // size again — the ones for the smaller bars, and the second half of a page
37781+ // transition.
37782+ const writtenValueByArea = new Map();
37783+
37784+ // A size that must land before the next paint: a render changed the bar, so
37785+ // the room it takes is given back in that same commit and the content is never
37786+ // painted under it.
3777837787/**
3777937788 * @param {"top"|"bottom"|"left"|"right"} area
3778037789 * @param {Element} barElement - Which bar this size belongs to.
3778137790 * @param {number|null} size - In px; `null` gives that bar's room back to the
3778237791 * content.
3778337792 */
3778437793const setFixedBarSpace = (area, barElement, size) => {
37794+ dropPendingSize(area, barElement);
37795+ storeSize(area, barElement, size);
37796+ writeSpace(area);
37797+ };
37798+
37799+ // A size nothing asked for, coming from a ResizeObserver. Writing the variable
37800+ // resizes an ANCESTOR of the bars — the scroll container takes its padding
37801+ // from it — and mutating layout from inside a resize callback is what makes
37802+ // the browser report "ResizeObserver loop completed with undelivered
37803+ // notifications". So the write waits for the frame that resize produced.
37804+ // Queued here rather than deferred by each bar on its own, so the bars sharing
37805+ // an edge resolve to a single write instead of one per bar.
37806+ /**
37807+ * @param {"top"|"bottom"|"left"|"right"} area
37808+ * @param {Element} barElement - Which bar this size belongs to.
37809+ * @param {number} size - In px.
37810+ */
37811+ const requestFixedBarSpace = (area, barElement, size) => {
37812+ let pendingSizeMap = pendingSizeMapByArea.get(area);
37813+ if (!pendingSizeMap) {
37814+ pendingSizeMap = new Map();
37815+ pendingSizeMapByArea.set(area, pendingSizeMap);
37816+ }
37817+ pendingSizeMap.set(barElement, size);
37818+ if (flushFrame !== null) {
37819+ return;
37820+ }
37821+ flushFrame = requestAnimationFrame(flushPendingSizes);
37822+ };
37823+
37824+ const pendingSizeMapByArea = new Map();
37825+ let flushFrame = null;
37826+
37827+ const flushPendingSizes = () => {
37828+ flushFrame = null;
37829+ for (const [area, pendingSizeMap] of pendingSizeMapByArea) {
37830+ for (const [barElement, size] of pendingSizeMap) {
37831+ storeSize(area, barElement, size);
37832+ }
37833+ writeSpace(area);
37834+ }
37835+ pendingSizeMapByArea.clear();
37836+ };
37837+
37838+ // What the bar itself just said wins over what its observer had queued about
37839+ // it: a bar unmounting gives its room back, and a size queued for it before
37840+ // that must not put it back.
37841+ const dropPendingSize = (area, barElement) => {
37842+ const pendingSizeMap = pendingSizeMapByArea.get(area);
37843+ if (!pendingSizeMap) {
37844+ return;
37845+ }
37846+ pendingSizeMap.delete(barElement);
37847+ if (pendingSizeMap.size === 0) {
37848+ pendingSizeMapByArea.delete(area);
37849+ }
37850+ };
37851+
37852+ const storeSize = (area, barElement, size) => {
3778537853 let sizeMap = sizeMapByArea.get(area);
3778637854 if (!sizeMap) {
3778737855 sizeMap = new Map();
@@ -37792,7 +37860,10 @@ const setFixedBarSpace = (area, barElement, size) => {
3779237860 } else {
3779337861 sizeMap.set(barElement, size);
3779437862 }
37863+ };
3779537864
37865+ const writeSpace = (area) => {
37866+ const sizeMap = sizeMapByArea.get(area);
3779637867 let largestSize = 0;
3779737868 for (const barSize of sizeMap.values()) {
3779837869 if (barSize > largestSize) {
@@ -37802,10 +37873,18 @@ const setFixedBarSpace = (area, barElement, size) => {
3780237873 const property = `--navi-fixed-bar-space-${area}`;
3780337874 const { style } = document.documentElement;
3780437875 if (sizeMap.size === 0) {
37805- style.removeProperty(property);
37806- } else {
37807- style.setProperty(property, `${largestSize}px`);
37876+ if (writtenValueByArea.has(area)) {
37877+ writtenValueByArea.delete(area);
37878+ style.removeProperty(property);
37879+ }
37880+ return;
37881+ }
37882+ const value = `${largestSize}px`;
37883+ if (writtenValueByArea.get(area) === value) {
37884+ return;
3780837885 }
37886+ writtenValueByArea.set(area, value);
37887+ style.setProperty(property, value);
3780937888};
3781037889
3781137890installImportMetaCssBuild(import.meta);/**
@@ -37981,27 +38060,40 @@ const FixedBar = ({
3798138060 // rebuilt as a calc() expression, so a size coming from anywhere — a prop, a
3798238061 // theme variable, the content itself — is reserved just the same, and each
3798338062 // `env()` inset stays the browser's business alone.
37984- // And measured again whenever it changes: a ResizeObserver on the bar covers
37985- // in one go a size prop that changes, content arriving or leaving, a font
37986- // loading late, a rotation moving the notch.
3798738063 const vertical = area === "left" || area === "right";
3798838064 const {
3798938065 ref
3799038066 } = props;
38067+ const measureSpace = barElement => {
38068+ const {
38069+ width,
38070+ height
38071+ } = barElement.getBoundingClientRect();
38072+ return vertical ? width : height;
38073+ };
38074+
38075+ // Anything a render can change — a size prop, the children, a theme variable
38076+ // — is measured in that same commit, before paint: no frame where the
38077+ // content sits under the bar, and nothing written from inside an observer.
38078+ useLayoutEffect(() => {
38079+ const barElement = ref.current;
38080+ if (!barElement) {
38081+ return;
38082+ }
38083+ setFixedBarSpace(area, barElement, measureSpace(barElement));
38084+ });
38085+
38086+ // What no render caused: a font loading late, content arriving from outside,
38087+ // a rotation moving the notch. Measuring here is safe; the write is what has
38088+ // to wait, and fixed_bar_space.js is the one that holds it back.
3799138089 useLayoutEffect(() => {
3799238090 const barElement = ref.current;
3799338091 if (!barElement) {
3799438092 return undefined;
3799538093 }
37996- const publishSize = () => {
37997- const {
37998- width,
37999- height
38000- } = barElement.getBoundingClientRect();
38001- setFixedBarSpace(area, barElement, vertical ? width : height);
38002- };
38003- publishSize();
38004- const resizeObserver = new ResizeObserver(publishSize);
38094+ const resizeObserver = new ResizeObserver(() => {
38095+ requestFixedBarSpace(area, barElement, measureSpace(barElement));
38096+ });
3800538097 resizeObserver.observe(barElement);
3800638098 return () => {
3800738099 resizeObserver.disconnect();
0 commit comments