@astryxdesign/core 0.7.4-canary.f32572e → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +8 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -0
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +6 -11
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +7 -1
- package/dist/CommandPalette/CommandPaletteItem.d.ts +1 -0
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +5 -1
- package/dist/DateInput/DateInput.d.ts +6 -4
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +5 -1
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +5 -1
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +5 -1
- package/dist/DateInput/tokens.stylex.d.ts.map +1 -1
- package/dist/DateInput/tokens.stylex.js +3 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +7 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -2
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +9 -2
- package/package.json +4 -4
- package/src/BottomSheet/BottomSheet.spec.md +1 -0
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +25 -2
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +7 -6
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +221 -1
- package/src/BottomSheet/__tests__/BottomSheetNativeModal.a11y.browser.spec.ts +461 -0
- package/src/Chat/ChatMessageList.test.tsx +6 -2
- package/src/Chat/ChatMessageList.tsx +7 -1
- package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +20 -12
- package/src/CheckboxInput/CheckboxInput.tsx +6 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +4 -1
- package/src/CommandPalette/CommandPalette.test.tsx +4 -2
- package/src/CommandPalette/CommandPalette.tsx +6 -11
- package/src/CommandPalette/CommandPaletteInput.test.tsx +3 -1
- package/src/CommandPalette/CommandPaletteInput.tsx +10 -1
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +17 -5
- package/src/CommandPalette/CommandPaletteItem.test.tsx +65 -12
- package/src/CommandPalette/CommandPaletteItem.tsx +6 -1
- package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +250 -20
- package/src/DateInput/DateInput.doc.mjs +23 -5
- package/src/DateInput/DateInput.spec.md +52 -45
- package/src/DateInput/DateInput.tsx +13 -7
- package/src/DateInput/NativeDateField.tsx +6 -2
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateInput/__tests__/DateInputAudit.a11y.chromium.spec.ts +576 -0
- package/src/DateInput/tokens.stylex.ts +3 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +10 -1
- package/src/FileInput/FileInput.tsx +18 -2
- package/src/Overlay/Overlay.spec.md +304 -0
- package/src/Thumbnail/Thumbnail.test.tsx +6 -1
- package/src/Thumbnail/Thumbnail.tsx +12 -3
- package/src/__tests__/BusyOwnerAnnouncement.test.tsx +324 -2
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 7
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:Overlay
|
|
6
|
+
authority: current
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: cixzhang
|
|
10
|
+
approved_at: 2026-10-22
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [public-api, layout, accessibility]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/Overlay/Overlay.test.tsx,
|
|
16
|
+
apps/storybook/stories/Overlay.stories.tsx,
|
|
17
|
+
]
|
|
18
|
+
modules: []
|
|
19
|
+
families: []
|
|
20
|
+
design_specs: [design:bidirectionality]
|
|
21
|
+
architecture:
|
|
22
|
+
[
|
|
23
|
+
architecture:public-component-api,
|
|
24
|
+
architecture:component-theming-surface,
|
|
25
|
+
architecture:internationalization,
|
|
26
|
+
]
|
|
27
|
+
contributing: []
|
|
28
|
+
system_specs: [spec:AST-002/DEC-1, spec:AST-017/FR5, spec:AST-027/FR2]
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# Overlay component contract
|
|
32
|
+
|
|
33
|
+
<!-- Describe the system, not the project: present tense, what it does. No proposals, history, pull requests, or research in the record; see docs/contributing/spec-writing.md and report its rubric results in the pull request. -->
|
|
34
|
+
|
|
35
|
+
This record governs how several pieces of content are placed over an Overlay's
|
|
36
|
+
surface, each at a preset spot or at fractional coordinates, including the
|
|
37
|
+
stacking of a container that holds such content. Overlay's scrim, reveal modes,
|
|
38
|
+
own `position` values, and alignment remain described by its source, tests, and
|
|
39
|
+
`Overlay.doc.mjs` until a record claims them.
|
|
40
|
+
|
|
41
|
+
## Contract at a glance
|
|
42
|
+
|
|
43
|
+
| Area | Contract |
|
|
44
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| Public contract | New `OverlayRegion` component, exported beside Overlay: `position` (required) is a preset (`'fill' \| 'top' \| 'bottom' \| 'center' \| 'top-start' \| 'top-end' \| 'bottom-start' \| 'bottom-end'`) or open placement `{x, y, width?, height?}` in 0–1 fractions; plus `children`, `ref`, `xstyle`, `className`, `style`. |
|
|
46
|
+
| Behavior | Inside a fill Overlay, a preset region sits at that spot (corners follow the writing direction) and open placement sits at fractions of the media measured from its top-left corner, never mirrored. A sized open region stays inside the media. Regions scale with the media. |
|
|
47
|
+
| End-user impact | Several captions, tags, badges, or hotspots over one image or video each stay on their spot of the media at every size, in either text direction. |
|
|
48
|
+
| Builder impact | None by default. A builder places content with `OverlayRegion` instead of hand-written absolute positioning; moving a region stays the builder's code. |
|
|
49
|
+
| Compatibility/readiness | Additive: a new component and export; an Overlay without a region renders and stacks exactly as before. This record is current authority; `OverlayRegion`, its consumer docs, region tests, and region stories have not shipped yet. |
|
|
50
|
+
| Review checks | Reject open placement that mirrors in right-to-left, corner presets that do not, JavaScript measurement or observers, drag or resize behavior inside Overlay, a role or focus stop on the region, a theme target on it, or a region placed inside a strip. |
|
|
51
|
+
| Governing rules | FR1–FR11 below; `design:bidirectionality/DR3`, `DR5`, `DR6`; `architecture:component-theming-surface/INV4`; `spec:AST-017/FR5`; `spec:AST-027/FR2`. |
|
|
52
|
+
|
|
53
|
+
This table is a review projection; the body below is authoritative.
|
|
54
|
+
|
|
55
|
+
## Intent
|
|
56
|
+
|
|
57
|
+
Overlay layers content over a bounded surface, usually media, and places that
|
|
58
|
+
content as one block: the whole surface or a band at the top or bottom. Some
|
|
59
|
+
surfaces carry several pieces of content, each belonging to its own spot: two
|
|
60
|
+
captions drawn into an image, a badge in a corner beside a caption, a tag on a
|
|
61
|
+
face. A spot is either one of Overlay's familiar places or a point on the media
|
|
62
|
+
described as fractions of its width and height, because the media is shown at
|
|
63
|
+
many sizes and the spot must stay on the same pixels of it. Overlay owns that
|
|
64
|
+
placement because Overlay owns the surface it is measured against.
|
|
65
|
+
|
|
66
|
+
## Compatibility and migration
|
|
67
|
+
|
|
68
|
+
- Released default preserved: `yes`; an Overlay without a region renders and
|
|
69
|
+
stacks exactly as before. Only a container that holds a region isolates its
|
|
70
|
+
stacking (FR10).
|
|
71
|
+
- Compatibility class: additive component and export.
|
|
72
|
+
- Controlled/uncontrolled behavior: not applicable; a region holds no state.
|
|
73
|
+
- Migration decision: none required (`spec:AST-017/FR5`).
|
|
74
|
+
|
|
75
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
76
|
+
|
|
77
|
+
## Ownership boundary
|
|
78
|
+
|
|
79
|
+
**Owns**
|
|
80
|
+
|
|
81
|
+
- `OverlayRegion`: its `position` values, their geometry, clamping, scaling, and
|
|
82
|
+
reference box.
|
|
83
|
+
- The rule that open placement is measured in the media's own physical space.
|
|
84
|
+
- The rule that regions are placed only in fill overlays, and the fallback
|
|
85
|
+
everywhere else.
|
|
86
|
+
- The stacking isolation of a container that holds a region.
|
|
87
|
+
|
|
88
|
+
**Does not own / non-goals**
|
|
89
|
+
|
|
90
|
+
- Moving, dragging, or resizing a region, including pointer capture, keyboard
|
|
91
|
+
steps, snapping, and when a new position is reported — owned by the product
|
|
92
|
+
callsite (`component:Overlay/DEC-3`).
|
|
93
|
+
- What a region paints: borders, fills, handles, and text — owned by the
|
|
94
|
+
region's content.
|
|
95
|
+
- Making the base content fill the surface — owned by the caller, as for every
|
|
96
|
+
Overlay; fractions are measured against the surface, so media that does not
|
|
97
|
+
fill it does not line up with them.
|
|
98
|
+
- Overlay's own `position` and `align` — unchanged; a region does not read or
|
|
99
|
+
alter them beyond FR2.
|
|
100
|
+
- Placement relative to a trigger or outside the surface — owned by the layer
|
|
101
|
+
runtime (Popover, Tooltip, HoverCard).
|
|
102
|
+
|
|
103
|
+
## Public concepts
|
|
104
|
+
|
|
105
|
+
| Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
|
|
106
|
+
| -------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| Preset | `fill`, `top`, `bottom`, `center`, `top-start`, `top-end`, `bottom-start`, `bottom-end` | `fill`, `top`, and `bottom` are the places Overlay's own `position` names: the whole surface, or a full-width band at that edge. `center` and the corners hold content-sized boxes at that spot. | Fill overlays (FR2) | None; required prop | `component:Overlay` | stable | Other strings are rejected by the public type |
|
|
108
|
+
| Open placement | `{x, y, width?, height?}`; each a number from 0 to 1 | `x`, `y`: the region's top-left corner as fractions of its reference box, measured from that box's top-left corner. `width`, `height`: the region's size as fractions, extending right and down; omitted means it fits its content. | Fill overlays (FR2) | None; required prop | `component:Overlay` | stable | Values clamped into 0–1; a non-finite coordinate is treated as 0 and a non-finite size as omitted (FR6) |
|
|
109
|
+
|
|
110
|
+
## Behavioral and layout contract
|
|
111
|
+
|
|
112
|
+
| ID | Invariant | Basis | Draft review state |
|
|
113
|
+
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------------ |
|
|
114
|
+
| FR1 | `OverlayRegion` rendered directly in the content of an Overlay or `useOverlay` host with `position="fill"`, the default, MUST position against that host's scrim box, which is the whole base surface: 0 and 1 are the media's edges, and the box matches the Overlay's `ref`. Any element between the region and the scrim that establishes a containing block for absolutely positioned descendants becomes its reference box instead. In this record, the surface means the region's reference box. | `component:Overlay/DEC-1` | settled |
|
|
115
|
+
| FR2 | Regions are placed only in fill hosts. A region rendered anywhere else MUST NOT be placed: in the content of an Overlay or `useOverlay` host whose `position` is `"top"` or `"bottom"`, in an Overlay's `children`, or with no Overlay or `useOverlay` host at all, it renders its content as ordinary in-flow content where it stands (in a strip, that content reveals and slides with the strip) and MUST emit one development warning per mount naming the fill-only rule. | `component:Overlay/DEC-4` | settled |
|
|
116
|
+
| FR3 | A preset region MUST sit at its spot inside Overlay's content inset: `fill` covers the inset box; `top` and `bottom` span its full width at that edge with content height; `center` is content-sized at its center; each corner is content-sized at that corner. Content larger than the inset box is clipped by the container, not shifted. | `component:Overlay/DEC-2` | settled |
|
|
117
|
+
| FR4 | Corner presets MUST resolve `start` and `end` against the writing direction of the region's own context: `top-start` is the top-left corner in left-to-right and the top-right corner in right-to-left. `fill`, `top`, `bottom`, and `center` are symmetric and do not change with direction. | `design:bidirectionality/DR3` | settled |
|
|
118
|
+
| FR5 | Open placement MUST measure `x` from the surface's left edge and `y` from its top edge in every writing direction, and MUST NOT mirror in right-to-left. The region's own content follows the ambient direction. | `design:bidirectionality/DR5` | settled |
|
|
119
|
+
| FR6 | Open coordinates and sizes MUST be clamped into 0–1 before placement. A non-finite coordinate MUST resolve to 0, and a non-finite size MUST resolve to omitted. | Invalid-value behavior | settled |
|
|
120
|
+
| FR7 | Open placement puts the region's top-left corner at (`x`, `y`) (`component:Overlay/DEC-6`). On an axis with a size, the region MUST be exactly that fraction of the surface, extending right or down, and MUST sit wholly inside it: its start is clamped between 0 and one minus the size. On an axis without a size, the region MUST fit its content starting at the coordinate, and any overhang past the container's edge is clipped, not shifted inward. | `component:Overlay/DEC-5` | settled |
|
|
121
|
+
| FR8 | When the surface changes size, every region MUST keep its spot: presets by their insets, open placement by its fractional position and size, without a re-render. | `component:Overlay/DEC-1` | settled |
|
|
122
|
+
| FR9 | A region MUST appear, hide, and become inert with the overlay content that contains it, following Overlay's `showOn` and `isOpen`, and MUST inherit the scrim's media theme. It adds no visibility rule of its own. | Overlay's existing reveal behavior | settled |
|
|
123
|
+
| FR10 | A placed region MUST paint above the fill surface's in-flow overlay content, including positioned in-flow content written after it, and a later placed region MUST paint above an earlier one. A placed region inside a wrapper that forms a stacking context is ordered only among that wrapper's content. An Overlay or `useOverlay` container with a placed region anywhere inside it MUST own an isolated stacking context; a container with no region inside it keeps its stacking unchanged. A placed region MUST NOT take part in the scrim's flow layout. | Absolute positioning within the surface; `spec:AST-027/FR2` | settled |
|
|
124
|
+
| FR11 | Overlay and `OverlayRegion` MUST NOT move, drag, or resize a region, and MUST NOT attach pointer, keyboard, or gesture handlers to it. A region moves only when its caller passes a new `position`. | `component:Overlay/DEC-3` | settled |
|
|
125
|
+
|
|
126
|
+
### Allowed variation
|
|
127
|
+
|
|
128
|
+
- **AV1 — Content.** Any content may sit in a region; on an open region each
|
|
129
|
+
axis is sized or content-sized independently (FR7).
|
|
130
|
+
- **AV2 — Surface size and shape.** The surface may be any size or aspect ratio,
|
|
131
|
+
and may change size at any time (FR8).
|
|
132
|
+
- **AV3 — Mixed content.** In-flow overlay content, preset regions, and open
|
|
133
|
+
regions may share one surface (FR10).
|
|
134
|
+
|
|
135
|
+
### Representative states
|
|
136
|
+
|
|
137
|
+
| State | Required invariant | Allowed variation |
|
|
138
|
+
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------ |
|
|
139
|
+
| Two captions, `top` and `bottom` presets | Each spans the inset width at its edge, like Overlay's own strips. | Caption text. |
|
|
140
|
+
| `top-start` badge, left-to-right and right-to-left | Top-left corner in left-to-right; top-right corner in right-to-left. | Badge content. |
|
|
141
|
+
| Open sized caption near the right edge | Box is the given fraction of the image and stays wholly inside, flush with that edge. | Which edge. |
|
|
142
|
+
| Open content-sized pin | Pin starts at its point and extends right and down; overhang past the container is clipped. | Pin content. |
|
|
143
|
+
| Open region, right-to-left page | Same spot on the media as left-to-right; the region's text reads right-to-left. | Writing direction. |
|
|
144
|
+
| Surface resized from 640px to 320px wide | Every region keeps its spot, with no re-render. | Any surface size. |
|
|
145
|
+
| Region in an Overlay's `children` or outside any Overlay | Is not placed: its content flows where it stands, and it warns once in development. | Location. |
|
|
146
|
+
| Region in a `position="bottom"` Overlay | Is not placed: its content flows in the strip like other strip content, and it warns once in development. | Strip content. |
|
|
147
|
+
| `showOn="hover"` overlay at rest | Regions are hidden with the rest of the overlay content. | Reveal mode. |
|
|
148
|
+
|
|
149
|
+
### Transformation and precedence order
|
|
150
|
+
|
|
151
|
+
- **ORD1 — Placement.** Resolve the reference box (FR1, FR2) → for a preset,
|
|
152
|
+
place it in the content inset and resolve start/end against direction (FR3,
|
|
153
|
+
FR4); for open placement, clamp values (FR6) and resolve each axis as sized or
|
|
154
|
+
content-sized in physical space (FR5, FR7) → express the result as insets and
|
|
155
|
+
fractions of the box (FR8). Styling inputs compose after placement in the order
|
|
156
|
+
`architecture:public-component-api/INV6` gives; a caller who overrides inset or
|
|
157
|
+
size properties owns the result.
|
|
158
|
+
|
|
159
|
+
### Performance and resources
|
|
160
|
+
|
|
161
|
+
- **PR1 — CSS geometry only.** Placement, clamping, and scaling MUST be achieved
|
|
162
|
+
with arithmetic on the given values and CSS: no measurement, observer,
|
|
163
|
+
listener, state, Effect, or extra render pass. The fill-only check adds no
|
|
164
|
+
measurement, state, or extra render pass.
|
|
165
|
+
|
|
166
|
+
## Accessibility contract
|
|
167
|
+
|
|
168
|
+
- **AR1 — Presentation only.** `OverlayRegion` MUST add no role, accessible name,
|
|
169
|
+
description, focus stop, or text. Its content keeps its own semantics.
|
|
170
|
+
- **AR2 — Source order is reading order.** Assistive technology and keyboard
|
|
171
|
+
focus follow the regions' source order, not their position on the surface. A
|
|
172
|
+
caller renders regions in the order a reader should meet them.
|
|
173
|
+
- **AR3 — Movement offers non-drag paths.** Overlay's documented move pattern
|
|
174
|
+
for open regions pairs pointer dragging with a single-pointer path that needs
|
|
175
|
+
no drag, where activating a spot on the media moves the region there, and a
|
|
176
|
+
keyboard path that moves a region whose content has focus by a fixed step per
|
|
177
|
+
arrow key, with arrow directions matching the visual axes
|
|
178
|
+
(`design:bidirectionality/DR6`).
|
|
179
|
+
|
|
180
|
+
## Design relationships
|
|
181
|
+
|
|
182
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
183
|
+
| -------------------- | ------------------------------------------------------------ | ----------------------------- | -------------- | ------------------ |
|
|
184
|
+
| Preset region | Reads as Overlay's own places, with Overlay's content inset. | Owner direction | Supporting | FR3 |
|
|
185
|
+
| Corner preset in RTL | Leading and trailing corners follow reading order. | `design:bidirectionality/DR3` | Supporting | FR4 |
|
|
186
|
+
| Open region | Stays on the same spot of the media at every size. | Owner direction | Supporting | FR7, FR8 |
|
|
187
|
+
| Open region in RTL | Media is physical content and does not mirror. | `design:bidirectionality/DR5` | Supporting | FR5 |
|
|
188
|
+
|
|
189
|
+
## Family and system relationships
|
|
190
|
+
|
|
191
|
+
- `design:bidirectionality` owns direction behavior: FR4 applies DR3 to corner
|
|
192
|
+
presets, FR5 applies DR5 to open placement, and AR3 applies DR6 to movement.
|
|
193
|
+
- `architecture:component-theming-surface/INV4` keeps a non-painting box out of
|
|
194
|
+
the theme surface: the region is a geometry-only box that paints nothing, so it
|
|
195
|
+
has no theme target and its content owns its paint.
|
|
196
|
+
- `architecture:public-component-api` owns prop admission and styling-input
|
|
197
|
+
order; ORD1 applies INV6.
|
|
198
|
+
- `spec:AST-027/FR2` puts the isolation boundary on the surface; FR10 makes the
|
|
199
|
+
Overlay or `useOverlay` container that holds a region that owner.
|
|
200
|
+
- `spec:AST-017/FR5` classifies this as nonbreaking: it adds a new component,
|
|
201
|
+
changes no default, and a caller who never renders a region sees no change.
|
|
202
|
+
|
|
203
|
+
## Verification map
|
|
204
|
+
|
|
205
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
206
|
+
| ----------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
|
|
207
|
+
| FR1, FR2, FR6, FR11 | `Overlay.test.tsx` region suite | Region in Overlay and `useOverlay` content; in a `bottom` Overlay; outside any Overlay; out-of-range and non-finite input; pointer and key events | Unclamped values, a region outside the scrim, a placed region in a strip or a missing warning, or a region that moves on its own fails. | `audit:Overlay/api` |
|
|
208
|
+
| FR3, FR4, FR5, FR7, FR8, FR10 | Real-Chromium assertions in the Overlay region story's play function | Every preset; open sized, edge-clamped, and content-sized; resized surface; RTL; a region before a positioned control | A region off its spot by more than 0.5px, a corner that does not flip or an open region that does, the in-flow control hit at a region's center, or wrong isolation, fails. | `audit:Overlay/layout` |
|
|
209
|
+
| FR9 | `Overlay.test.tsx` reveal case with a region | `isOpen={false}` | A region outside the scrim, or a closed scrim that is not inert, fails. | `audit:Overlay/api` |
|
|
210
|
+
| AR1, AR2 | `Overlay.test.tsx` DOM check of the region element | Text content | Any attribute other than `class` and `style` on the region element, or text it adds, fails. | `audit:Overlay/a11y` |
|
|
211
|
+
| AR3 | Region move story's play function | Arrow key and single activation on the media, LTR and RTL | ArrowRight moving a region left, or an activation that does not move the region to the activated spot, fails. | `audit:Overlay/a11y` |
|
|
212
|
+
| PR1 | Source review of `OverlayRegion` | All inputs | A ref read, observer, listener, or Effect added for placement fails review. | `audit:Overlay/layout` |
|
|
213
|
+
|
|
214
|
+
## Decision log
|
|
215
|
+
|
|
216
|
+
<!-- Record the boundary or requirement, not how it was reached. A rejected alternative is at most one line here, kept only when it is consequential and likely to recur. -->
|
|
217
|
+
|
|
218
|
+
### DEC-1 — Several placed items are Overlay regions with one `position` prop
|
|
219
|
+
|
|
220
|
+
**Reference:** `component:Overlay/DEC-1`
|
|
221
|
+
**Decider:** cixzhang, 2026-10-22
|
|
222
|
+
|
|
223
|
+
Overlay places one content block, so a surface with several spots needs a child
|
|
224
|
+
that places itself. `OverlayRegion` inside Overlay's content inherits Overlay's
|
|
225
|
+
surface, reveal, and media theme with no new Overlay prop, and one `position`
|
|
226
|
+
prop covers both kinds of spot: a named place or open placement. Sizes live
|
|
227
|
+
inside the open placement object, so a region never carries `width` or `height`
|
|
228
|
+
props whose numbers mean fractions while the same names mean pixels on Card,
|
|
229
|
+
Button, and Grid.
|
|
230
|
+
|
|
231
|
+
Rejected: an object form of Overlay's own `position`, because it still places
|
|
232
|
+
only one block.
|
|
233
|
+
|
|
234
|
+
### DEC-2 — Presets reuse Overlay's places and add the center and corners
|
|
235
|
+
|
|
236
|
+
**Reference:** `component:Overlay/DEC-2`
|
|
237
|
+
**Decider:** cixzhang, 2026-10-22
|
|
238
|
+
|
|
239
|
+
`fill`, `top`, and `bottom` mean on a region what they mean on Overlay, so a
|
|
240
|
+
builder reads one vocabulary. Static badges and labels need the center and the
|
|
241
|
+
corners, which Overlay's single block cannot hold beside other content. Corners
|
|
242
|
+
use `start` and `end` because a leading badge leads in either direction.
|
|
243
|
+
|
|
244
|
+
### DEC-3 — Moving a region stays at the callsite
|
|
245
|
+
|
|
246
|
+
**Reference:** `component:Overlay/DEC-3`
|
|
247
|
+
**Decider:** cixzhang, 2026-10-22
|
|
248
|
+
|
|
249
|
+
What moving a region means differs by product: whether the position commits on
|
|
250
|
+
release or live, the keyboard step, snapping, which regions move, and what
|
|
251
|
+
happens while a new rendering of the media loads. Overlay therefore never moves
|
|
252
|
+
a region; the caller converts pointer and keyboard input into new fractions
|
|
253
|
+
measured against the region's reference box and passes them back.
|
|
254
|
+
|
|
255
|
+
Rejected: a drag hook on Overlay, because a shared drag primitive needs its own
|
|
256
|
+
record and more than one caller's needs.
|
|
257
|
+
|
|
258
|
+
### DEC-4 — Regions belong to fill overlays
|
|
259
|
+
|
|
260
|
+
**Reference:** `component:Overlay/DEC-4`
|
|
261
|
+
**Decider:** cixzhang, 2026-10-22
|
|
262
|
+
|
|
263
|
+
A spot on the media is meaningless inside a strip whose height follows its own
|
|
264
|
+
content and which slides in and out. A strip is also the containing block of
|
|
265
|
+
everything inside it, so CSS cannot place a region against the media from
|
|
266
|
+
there. In a strip overlay a region therefore renders its content as ordinary
|
|
267
|
+
strip content and warns in development, so the content stays visible. The
|
|
268
|
+
public type cannot enforce the rule, because regions arrive through `content`.
|
|
269
|
+
The same fallback applies outside any fill host, where there is no surface to
|
|
270
|
+
measure against and nothing to isolate a region's paint order.
|
|
271
|
+
|
|
272
|
+
Rejected: placing a strip's regions against the container, because escaping the
|
|
273
|
+
strip needs a second rendering layer that duplicates Overlay's reveal.
|
|
274
|
+
|
|
275
|
+
### DEC-5 — Open sized axes are clamped inside; content-sized axes are not
|
|
276
|
+
|
|
277
|
+
**Reference:** `component:Overlay/DEC-5`
|
|
278
|
+
**Decider:** cixzhang, 2026-10-22
|
|
279
|
+
|
|
280
|
+
A sized region can be kept inside the surface with arithmetic on the given
|
|
281
|
+
fractions, so it never spills off the media however far it is moved. Keeping a
|
|
282
|
+
content-sized region inside would require measuring it, which PR1 forbids, so
|
|
283
|
+
the container clips any overhang.
|
|
284
|
+
|
|
285
|
+
### DEC-6 — Open placement names the region's top-left corner
|
|
286
|
+
|
|
287
|
+
**Reference:** `component:Overlay/DEC-6`
|
|
288
|
+
**Decider:** cixzhang, 2026-10-22
|
|
289
|
+
|
|
290
|
+
`x` and `y` are the region's top-left corner, and `width` and `height` extend
|
|
291
|
+
right and down. That matches how servers and editors report boxes (left, top,
|
|
292
|
+
width, height) and how CSS insets read, so a reported box passes through with no
|
|
293
|
+
conversion and is clamped with plain arithmetic.
|
|
294
|
+
|
|
295
|
+
Rejected: the region's center, because every reported box would need converting.
|
|
296
|
+
|
|
297
|
+
## Open questions
|
|
298
|
+
|
|
299
|
+
None.
|
|
300
|
+
|
|
301
|
+
## Content boundary
|
|
302
|
+
|
|
303
|
+
This file does not duplicate consumer prop tables/examples, current audit
|
|
304
|
+
results, implementation steps, or family/system rules. It links to their owners.
|
|
@@ -38,7 +38,12 @@ describe('Thumbnail', () => {
|
|
|
38
38
|
);
|
|
39
39
|
const img = screen.getByRole('img');
|
|
40
40
|
expect(img).toHaveAttribute('src', '/local.jpg');
|
|
41
|
-
|
|
41
|
+
// The upload Spinner is decorative; the thumbnail announces its busy
|
|
42
|
+
// start instead (spec:AST-086 FR8).
|
|
43
|
+
expect(document.querySelector('.astryx-spinner')).toHaveAttribute(
|
|
44
|
+
'aria-hidden',
|
|
45
|
+
'true',
|
|
46
|
+
);
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
it('exposes the label as an accessible name via a valid group role', () => {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* - /packages/cli/assets/templates/blocks/components/Thumbnail/ (showcase blocks)
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import {useState} from 'react';
|
|
28
|
+
import {useRef, useState} from 'react';
|
|
29
29
|
import * as stylex from '@stylexjs/stylex';
|
|
30
30
|
import {
|
|
31
31
|
colorVars,
|
|
@@ -40,6 +40,7 @@ import {Skeleton} from '../Skeleton';
|
|
|
40
40
|
import {Spinner} from '../Spinner';
|
|
41
41
|
import {Tooltip} from '../Tooltip/Tooltip';
|
|
42
42
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
43
|
+
import {useBusyAnnouncement} from '../hooks/useBusyAnnouncement';
|
|
43
44
|
import {useContainerReveal} from '../hooks/useContainerReveal';
|
|
44
45
|
import type {BaseProps} from '../BaseProps';
|
|
45
46
|
import {mergeProps} from '../utils';
|
|
@@ -352,6 +353,14 @@ export function Thumbnail({
|
|
|
352
353
|
const showSkeleton = isLoading && !hasSrc;
|
|
353
354
|
const showImage = hasSrc && !showSkeleton && !hasError;
|
|
354
355
|
const showUploadOverlay = isLoading && hasSrc;
|
|
356
|
+
const uploadOverlayRef = useRef<HTMLDivElement>(null);
|
|
357
|
+
// The upload Spinner is decorative; the thumbnail announces its busy start
|
|
358
|
+
// instead (spec:AST-086 FR8).
|
|
359
|
+
useBusyAnnouncement(
|
|
360
|
+
showUploadOverlay,
|
|
361
|
+
t('@astryx.spinner.loading'),
|
|
362
|
+
uploadOverlayRef,
|
|
363
|
+
);
|
|
355
364
|
const showPlaceholder = (!isLoading && !hasSrc) || hasError;
|
|
356
365
|
const isInteractive = onClick != null && !isDisabled && !isLoading;
|
|
357
366
|
const hasRemove = onRemove != null && !isDisabled;
|
|
@@ -472,8 +481,8 @@ export function Thumbnail({
|
|
|
472
481
|
)}
|
|
473
482
|
{showImage && <div {...stylex.props(styles.insetBorder)} />}
|
|
474
483
|
{showUploadOverlay && (
|
|
475
|
-
<div {...stylex.props(styles.uploadOverlay)}>
|
|
476
|
-
<Spinner size="sm" shade="onMedia" />
|
|
484
|
+
<div ref={uploadOverlayRef} {...stylex.props(styles.uploadOverlay)}>
|
|
485
|
+
<Spinner size="sm" shade="onMedia" aria-hidden="true" />
|
|
477
486
|
</div>
|
|
478
487
|
)}
|
|
479
488
|
{removeButtonEl}
|