Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/overlay-eager-padding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[perf] Components that import Popover no longer load BottomSheet's container padding styles and focus helper up front. Popover loads a BottomSheet on demand for its phone presentation, and a bundler that splits code put the modules both of them use into one chunk that loads with the component, keeping every export either side uses: about 3.5 KB minified in Popover, Carousel, DateRangeInput, DropdownMenu, Table and the other components that import Popover or DropdownMenu. `paddingStyles` and `overlayPaddingReset` now have their own modules, still exported from `@astryxdesign/core/Layout/padding.stylex`, and `isFocusDetached` has its own module, still exported from `@astryxdesign/core/utils`. Rendering is unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: "about 3.5 KB minified" fits Carousel, ContextMenu, Markdown and Table, but Popover saves about 2.9 KB, DateRangeInput about 2.9 KB, DropdownMenu about 2.6 KB and DateInput only 66 B. "Up to about 3.8 KB minified (about 1 KB gzip)" would be accurate.


@vjeux
2 changes: 1 addition & 1 deletion packages/core/src/Layer/useLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
import {preserveAnimations} from './preserveAnimations';
import {useLayerContext} from './LayerContext';
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
import {overlayPaddingReset} from '../Layout/padding.stylex';
import {overlayPaddingReset} from '../Layout/overlayPaddingReset.stylex';

const styles = stylex.create({
// Base reset for all layers
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Layout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type {

// Overlay padding-variable reset (applied on overlay roots so the container
// padding system does not cross a fixed/top-layer boundary)
export {overlayPaddingReset} from './padding.stylex';
export {overlayPaddingReset} from './overlayPaddingReset.stylex';

// Edge compensation utility
export {edgeCompSlot, EDGE_COMP_ATTR} from './edgeCompensation.stylex';
Expand Down
73 changes: 73 additions & 0 deletions packages/core/src/Layout/overlayPaddingReset.stylex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

/**
* @file overlayPaddingReset.stylex.ts
* @input Uses @stylexjs/stylex
* @output overlayPaddingReset: the container padding boundary at an overlay's root
* @position Layout utility, re-exported by padding.stylex.ts and Layout/index.ts.
* Its own module so an overlay's eager code (useLayer) imports it without
* padding.stylex.ts: a lazily loaded overlay that uses the container padding
* styles there then keeps them out of the eager chunk it shares with this
* module.
*/

import * as stylex from '@stylexjs/stylex';

/**
* Padding-variable reset for overlay roots (Dialog, BottomSheet, Drawer,
* MobileNav, Lightbox, and every layer surface).
*
* ## Why an overlay needs this
*
* The container padding system talks to descendants through inherited custom
* properties: a padded container announces its padding, and children read the
* value either to apply it or to cancel it with a negative margin
* (`Section`, `Divider`, `Layout`, `Table`).
*
* Inheritance follows the DOM, but an overlay leaves its parent's visual box —
* a fixed/top-layer `<dialog>` is a DOM descendant of the padded page while
* being nowhere near it on screen. It inherits values describing padding that
* is not there, and its content compensates against phantom space: a `Section`
* inside a 640px sheet rendered 672px wide and hung off both edges (#5208).
*
* Two families leak, and they need opposite treatments:
*
* - `--container-padding-*` -> `0px`. Descendants SUBTRACT these (bleed
* margins). The overlay root has no padding of its own to escape, so the
* honest answer is zero. Nested containers that do set padding (a Dialog's
* content wrapper) re-announce their own values below this point, so
* legitimate edge-to-edge bleed inside the overlay is unaffected.
* - `--layout-padding-*` and `--_section-padding-propagated` -> `initial`.
* Descendants ADD these, so zeroing them would strip padding rather than
* restore it. `initial` makes each guaranteed-invalid, so readers fall
* through their own `var(…, fallback)` chain and land on the theme default —
* which is what an overlay at the top of the tree should show.
*
* `initial` is also why propagation moved off `--astryx-section-padding`: that
* name is public theme surface, set at the theme root, and making it invalid
* here would blank the theme's own section padding inside every overlay.
*
* Apply on the overlay's outermost styled element.
*
* @example
* ```
* <dialog {...stylex.props(styles.dialog, overlayPaddingReset.reset)} />
* ```
*/
export const overlayPaddingReset = stylex.create({
reset: {
// Subtracted by descendants — the overlay root has no padding to escape.
'--container-padding-inline-start': '0px',
'--container-padding-inline-end': '0px',
'--container-padding-block-start': '0px',
'--container-padding-block-end': '0px',
// Added by descendants — fall through to each reader's own default.
'--layout-padding-outer-x': 'initial',
'--layout-padding-outer-y': 'initial',
'--layout-padding-own-outer-x': 'initial',
'--layout-padding-own-outer-y': 'initial',
'--layout-padding-inner-x': 'initial',
'--layout-padding-inner-y': 'initial',
'--_section-padding-propagated': 'initial',
},
});
5 changes: 4 additions & 1 deletion packages/core/src/Layout/overlayPaddingReset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,10 @@ describe('overlayPaddingReset', () => {
// this; assert it where it is decided instead. (Verified in a browser: a
// theme's 20px still reaches a Section inside a Dialog nested under a
// 40px page Section.)
const source = readFileSync(join(__dirname, 'padding.stylex.ts'), 'utf8');
const source = readFileSync(
join(__dirname, 'overlayPaddingReset.stylex.ts'),
'utf8',
);
const reset = source.slice(
source.indexOf('export const overlayPaddingReset'),
);
Expand Down
142 changes: 9 additions & 133 deletions packages/core/src/Layout/padding.stylex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
/**
* @file padding.stylex.ts
* @input Uses @stylexjs/stylex, spacing from theme
* @output StyleX styles for numeric padding prop on layout components
* @output StyleX styles for numeric padding prop on layout components, the
* container padding variables, and (re-exported) paddingStyles and
* overlayPaddingReset
* @position Layout utility; used by Card, Section, and Layout sub-components
*/

import * as stylex from '@stylexjs/stylex';
import {spacingVars} from '../theme/tokens.stylex';
import type {SpacingStep} from '../utils/types';

// In their own modules so an overlay's eager code imports them without this
// one, and a lazily loaded overlay that uses the container padding styles
// below keeps those out of the chunk it shares with that code.
export {paddingStyles} from './paddingStyles.stylex';
export {overlayPaddingReset} from './overlayPaddingReset.stylex';

/**
* Maps numeric SpacingStep values to SpacingToken keys used by the container system.
*/
Expand All @@ -28,79 +36,6 @@ export const spacingStepToToken: Record<SpacingStep, string> = {
10: 'spacing10',
};

/**
* Padding styles for all sides using spacing tokens.
* Each key applies uniform padding to all four sides.
*/
export const paddingStyles = stylex.create({
0: {
paddingInlineStart: spacingVars['--spacing-0'],
paddingInlineEnd: spacingVars['--spacing-0'],
paddingBlockStart: spacingVars['--spacing-0'],
paddingBlockEnd: spacingVars['--spacing-0'],
},
0.5: {
paddingInlineStart: spacingVars['--spacing-0-5'],
paddingInlineEnd: spacingVars['--spacing-0-5'],
paddingBlockStart: spacingVars['--spacing-0-5'],
paddingBlockEnd: spacingVars['--spacing-0-5'],
},
1: {
paddingInlineStart: spacingVars['--spacing-1'],
paddingInlineEnd: spacingVars['--spacing-1'],
paddingBlockStart: spacingVars['--spacing-1'],
paddingBlockEnd: spacingVars['--spacing-1'],
},
1.5: {
paddingInlineStart: spacingVars['--spacing-1-5'],
paddingInlineEnd: spacingVars['--spacing-1-5'],
paddingBlockStart: spacingVars['--spacing-1-5'],
paddingBlockEnd: spacingVars['--spacing-1-5'],
},
2: {
paddingInlineStart: spacingVars['--spacing-2'],
paddingInlineEnd: spacingVars['--spacing-2'],
paddingBlockStart: spacingVars['--spacing-2'],
paddingBlockEnd: spacingVars['--spacing-2'],
},
3: {
paddingInlineStart: spacingVars['--spacing-3'],
paddingInlineEnd: spacingVars['--spacing-3'],
paddingBlockStart: spacingVars['--spacing-3'],
paddingBlockEnd: spacingVars['--spacing-3'],
},
4: {
paddingInlineStart: spacingVars['--spacing-4'],
paddingInlineEnd: spacingVars['--spacing-4'],
paddingBlockStart: spacingVars['--spacing-4'],
paddingBlockEnd: spacingVars['--spacing-4'],
},
5: {
paddingInlineStart: spacingVars['--spacing-5'],
paddingInlineEnd: spacingVars['--spacing-5'],
paddingBlockStart: spacingVars['--spacing-5'],
paddingBlockEnd: spacingVars['--spacing-5'],
},
6: {
paddingInlineStart: spacingVars['--spacing-6'],
paddingInlineEnd: spacingVars['--spacing-6'],
paddingBlockStart: spacingVars['--spacing-6'],
paddingBlockEnd: spacingVars['--spacing-6'],
},
8: {
paddingInlineStart: spacingVars['--spacing-8'],
paddingInlineEnd: spacingVars['--spacing-8'],
paddingBlockStart: spacingVars['--spacing-8'],
paddingBlockEnd: spacingVars['--spacing-8'],
},
10: {
paddingInlineStart: spacingVars['--spacing-10'],
paddingInlineEnd: spacingVars['--spacing-10'],
paddingBlockStart: spacingVars['--spacing-10'],
paddingBlockEnd: spacingVars['--spacing-10'],
},
});

/**
* Container padding inline CSS variable styles for edge compensation.
* Sets --container-padding-inline-start and --container-padding-inline-end so
Expand Down Expand Up @@ -474,62 +409,3 @@ export const sectionPaddingPropagationStyles = stylex.create({
8: {'--_section-padding-propagated': spacingVars['--spacing-8']},
10: {'--_section-padding-propagated': spacingVars['--spacing-10']},
});

/**
* Padding-variable reset for overlay roots (Dialog, BottomSheet, Drawer,
* MobileNav, Lightbox, and every layer surface).
*
* ## Why an overlay needs this
*
* The container padding system talks to descendants through inherited custom
* properties: a padded container announces its padding, and children read the
* value either to apply it or to cancel it with a negative margin
* (`Section`, `Divider`, `Layout`, `Table`).
*
* Inheritance follows the DOM, but an overlay leaves its parent's visual box —
* a fixed/top-layer `<dialog>` is a DOM descendant of the padded page while
* being nowhere near it on screen. It inherits values describing padding that
* is not there, and its content compensates against phantom space: a `Section`
* inside a 640px sheet rendered 672px wide and hung off both edges (#5208).
*
* Two families leak, and they need opposite treatments:
*
* - `--container-padding-*` -> `0px`. Descendants SUBTRACT these (bleed
* margins). The overlay root has no padding of its own to escape, so the
* honest answer is zero. Nested containers that do set padding (a Dialog's
* content wrapper) re-announce their own values below this point, so
* legitimate edge-to-edge bleed inside the overlay is unaffected.
* - `--layout-padding-*` and `--_section-padding-propagated` -> `initial`.
* Descendants ADD these, so zeroing them would strip padding rather than
* restore it. `initial` makes each guaranteed-invalid, so readers fall
* through their own `var(…, fallback)` chain and land on the theme default —
* which is what an overlay at the top of the tree should show.
*
* `initial` is also why propagation moved off `--astryx-section-padding`: that
* name is public theme surface, set at the theme root, and making it invalid
* here would blank the theme's own section padding inside every overlay.
*
* Apply on the overlay's outermost styled element.
*
* @example
* ```
* <dialog {...stylex.props(styles.dialog, overlayPaddingReset.reset)} />
* ```
*/
export const overlayPaddingReset = stylex.create({
reset: {
// Subtracted by descendants — the overlay root has no padding to escape.
'--container-padding-inline-start': '0px',
'--container-padding-inline-end': '0px',
'--container-padding-block-start': '0px',
'--container-padding-block-end': '0px',
// Added by descendants — fall through to each reader's own default.
'--layout-padding-outer-x': 'initial',
'--layout-padding-outer-y': 'initial',
'--layout-padding-own-outer-x': 'initial',
'--layout-padding-own-outer-y': 'initial',
'--layout-padding-inner-x': 'initial',
'--layout-padding-inner-y': 'initial',
'--_section-padding-propagated': 'initial',
},
});
87 changes: 87 additions & 0 deletions packages/core/src/Layout/paddingStyles.stylex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

/**
* @file paddingStyles.stylex.ts
* @input Uses @stylexjs/stylex, spacing from theme
* @output paddingStyles: uniform padding on all four sides by SpacingStep
* @position Layout utility, re-exported by padding.stylex.ts. Its own module so an
* overlay's eager code (usePopover) imports it without padding.stylex.ts:
* a lazily loaded overlay that uses the container padding styles there then
* keeps them out of the eager chunk it shares with this module.
*/

import * as stylex from '@stylexjs/stylex';
import {spacingVars} from '../theme/tokens.stylex';

/**
* Padding styles for all sides using spacing tokens.
* Each key applies uniform padding to all four sides.
*/
export const paddingStyles = stylex.create({
0: {
paddingInlineStart: spacingVars['--spacing-0'],
paddingInlineEnd: spacingVars['--spacing-0'],
paddingBlockStart: spacingVars['--spacing-0'],
paddingBlockEnd: spacingVars['--spacing-0'],
},
0.5: {
paddingInlineStart: spacingVars['--spacing-0-5'],
paddingInlineEnd: spacingVars['--spacing-0-5'],
paddingBlockStart: spacingVars['--spacing-0-5'],
paddingBlockEnd: spacingVars['--spacing-0-5'],
},
1: {
paddingInlineStart: spacingVars['--spacing-1'],
paddingInlineEnd: spacingVars['--spacing-1'],
paddingBlockStart: spacingVars['--spacing-1'],
paddingBlockEnd: spacingVars['--spacing-1'],
},
1.5: {
paddingInlineStart: spacingVars['--spacing-1-5'],
paddingInlineEnd: spacingVars['--spacing-1-5'],
paddingBlockStart: spacingVars['--spacing-1-5'],
paddingBlockEnd: spacingVars['--spacing-1-5'],
},
2: {
paddingInlineStart: spacingVars['--spacing-2'],
paddingInlineEnd: spacingVars['--spacing-2'],
paddingBlockStart: spacingVars['--spacing-2'],
paddingBlockEnd: spacingVars['--spacing-2'],
},
3: {
paddingInlineStart: spacingVars['--spacing-3'],
paddingInlineEnd: spacingVars['--spacing-3'],
paddingBlockStart: spacingVars['--spacing-3'],
paddingBlockEnd: spacingVars['--spacing-3'],
},
4: {
paddingInlineStart: spacingVars['--spacing-4'],
paddingInlineEnd: spacingVars['--spacing-4'],
paddingBlockStart: spacingVars['--spacing-4'],
paddingBlockEnd: spacingVars['--spacing-4'],
},
5: {
paddingInlineStart: spacingVars['--spacing-5'],
paddingInlineEnd: spacingVars['--spacing-5'],
paddingBlockStart: spacingVars['--spacing-5'],
paddingBlockEnd: spacingVars['--spacing-5'],
},
6: {
paddingInlineStart: spacingVars['--spacing-6'],
paddingInlineEnd: spacingVars['--spacing-6'],
paddingBlockStart: spacingVars['--spacing-6'],
paddingBlockEnd: spacingVars['--spacing-6'],
},
8: {
paddingInlineStart: spacingVars['--spacing-8'],
paddingInlineEnd: spacingVars['--spacing-8'],
paddingBlockStart: spacingVars['--spacing-8'],
paddingBlockEnd: spacingVars['--spacing-8'],
},
10: {
paddingInlineStart: spacingVars['--spacing-10'],
paddingInlineEnd: spacingVars['--spacing-10'],
paddingBlockStart: spacingVars['--spacing-10'],
paddingBlockEnd: spacingVars['--spacing-10'],
},
});
8 changes: 3 additions & 5 deletions packages/core/src/Popover/Popover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1225,12 +1225,10 @@ describe('Popover', () => {

describe('surface padding', () => {
// StyleX's dev runtime names the style object a class came from
// ("padding__paddingStyles.3"), which is the padding rung the surface
// was handed.
// ("paddingStyles__paddingStyles.3", after its module), which is the
// padding rung the surface was handed.
function paddingRungOf(surface: Element | null): string | null {
const match = surface?.className.match(
/padding__paddingStyles\.([\d.]+)/,
);
const match = surface?.className.match(/__paddingStyles\.([\d.]+)/);
return match ? match[1] : null;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Popover/usePopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {useDevWarning} from '../hooks/useDevWarning';
import {focusOutlineProps} from '../utils/focusOutline.stylex';
import {mergeProps} from '../utils/mergeProps';
import {themeProps} from '../utils/themeProps';
import {paddingStyles} from '../Layout/padding.stylex';
import {paddingStyles} from '../Layout/paddingStyles.stylex';
import type {SpacingStep} from '../utils/types';
import {stableClassName} from '../naming';

Expand Down
Loading
Loading